This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""Admin panel — access control, dashboard rendering, and template integrity.
|
||||
|
||||
ADMIN-004. The admin panel at /admin must only be reachable by admins,
|
||||
must render all expected sections, and must serve static assets correctly.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.extensions import db
|
||||
from app.models import User
|
||||
|
||||
|
||||
NON_ADMIN_ROLES = ['player', 'coach', 'manager', 'scout']
|
||||
|
||||
ADMIN_MUTATION_ROUTES = [
|
||||
'/admin/backup/create',
|
||||
'/admin/toggle-tryouts',
|
||||
'/admin/season/start',
|
||||
'/admin/season/end',
|
||||
'/admin/teams/wipe',
|
||||
]
|
||||
|
||||
|
||||
def _redirected(response):
|
||||
return response.status_code in (301, 302)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Access control
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestAdminAccessControl:
|
||||
@pytest.mark.parametrize('role', NON_ADMIN_ROLES)
|
||||
def test_non_admin_is_redirected_from_dashboard(self, client, as_role, role):
|
||||
as_role(role)
|
||||
response = client.get('/admin', follow_redirects=False)
|
||||
assert _redirected(response), f'{role} reached /admin'
|
||||
|
||||
def test_admin_reaches_dashboard(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
assert response.status_code == 200
|
||||
|
||||
@pytest.mark.parametrize('route', ADMIN_MUTATION_ROUTES)
|
||||
@pytest.mark.parametrize('role', NON_ADMIN_ROLES)
|
||||
def test_non_admin_cannot_post_to_admin_routes(self, client, as_role, role, route):
|
||||
as_role(role)
|
||||
response = client.post(route, data={}, follow_redirects=False)
|
||||
assert _redirected(response), f'{role} reached {route}'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dashboard rendering
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestAdminDashboardRendering:
|
||||
def test_dashboard_shows_stats(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
assert 'Total Users' in html
|
||||
assert 'Players' in html
|
||||
assert 'Org Teams' in html
|
||||
assert 'Active Tryouts' in html
|
||||
|
||||
def test_dashboard_shows_tryout_status(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
# Either OPEN or CLOSED badge must be present
|
||||
assert 'OPEN' in html or 'CLOSED' in html
|
||||
|
||||
def test_dashboard_shows_season_info(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
assert 'Season Management' in html
|
||||
assert 'Name:' in html
|
||||
|
||||
def test_dashboard_shows_audit_log_section(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
assert 'Audit Log' in html
|
||||
|
||||
def test_dashboard_shows_backup_section(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
assert 'Manual Backup' in html
|
||||
assert 'Backup History' in html
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Template integrity
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestAdminTemplateIntegrity:
|
||||
def test_page_returns_200(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_page_has_html_doctype(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
assert '<!DOCTYPE html>' in html or '<!doctype html>' in html.lower()
|
||||
|
||||
def test_all_buttons_are_present(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
# Key buttons that must exist
|
||||
assert 'Create Backup Now' in html
|
||||
assert 'Wipe Team Rosters' in html
|
||||
# Toggle button text depends on state
|
||||
assert 'Tryouts' in html or 'tryouts' in html.lower()
|
||||
|
||||
def test_all_forms_have_csrf_tokens(self, client, as_role):
|
||||
as_role('admin')
|
||||
response = client.get('/admin')
|
||||
html = response.data.decode()
|
||||
|
||||
# Count <form> tags and csrf_token occurrences
|
||||
form_count = html.count('<form ')
|
||||
csrf_count = html.count('csrf_token')
|
||||
assert form_count > 0, 'No forms found on admin page'
|
||||
assert csrf_count >= form_count, (
|
||||
f'Found {form_count} forms but only {csrf_count} csrf_token(s)'
|
||||
)
|
||||
|
||||
def test_static_css_loads(self, client):
|
||||
response = client.get('/static/css/style.css')
|
||||
assert response.status_code == 200
|
||||
assert 'text/css' in response.content_type
|
||||
|
||||
def test_static_js_loads(self, client):
|
||||
response = client.get('/static/js/main.js')
|
||||
assert response.status_code == 200
|
||||
assert 'javascript' in response.content_type or 'text/' in response.content_type
|
||||
Reference in New Issue
Block a user