"""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 '' in html or '' 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