import re import unittest from pathlib import Path from unittest.mock import patch from urllib.parse import parse_qs, urlsplit from fastapi import FastAPI from fastapi.testclient import TestClient from app.frontend.router import router from app.identity import ( get_current_user, router as identity_router, safe_next_path, install_identity_middleware, protected_page, required_page_permission, ) ROUTES = ( "/dashboard", "/monitoring/map", "/operations/repairs", "/assets/miners", ) STATIC_ROOT = Path(__file__).parents[1] / "app" / "frontend" / "static" class FrontendShellTests(unittest.TestCase): def setUp(self): self.app = FastAPI() self.app.include_router(router) self.client = TestClient(self.app) def tearDown(self): self.client.close() def authenticate(self, permissions=None, role="Read Only"): self.app.dependency_overrides[get_current_user] = lambda: { "id": "00000000-0000-0000-0000-000000000321", "username": "operator", "display_name": "Floor Operator", "role": role, "permissions": list( permissions or ( "dashboard.view", "locations.view", "repairs.view", "miners.view", ) ), } def test_routes_require_authentication(self): for path in ROUTES: with self.subTest(path=path): self.assertEqual(self.client.get(path).status_code, 401) def test_all_direct_routes_render_authenticated_shell(self): self.authenticate() for path in ROUTES: with self.subTest(path=path): response = self.client.get(path) self.assertEqual(response.status_code, 200) self.assertIn("MinerOps", response.text) self.assertIn( "Operational Intelligence for Mining", response.text, ) self.assertIn("Floor Operator", response.text) self.assertIn( 'aria-label="Primary navigation"', response.text, ) def test_navigation_connects_all_internal_alpha_views(self): self.authenticate() response = self.client.get("/dashboard") for path in ROUTES: self.assertIn(f'href="{path}"', response.text) self.assertIn('aria-current="page"', response.text) def test_each_route_requires_its_specific_permission(self): cases = ( ("/dashboard", "dashboard.view"), ("/monitoring/map", "locations.view"), ("/operations/repairs", "repairs.view"), ("/assets/miners", "miners.view"), ) for path, permission in cases: with self.subTest(path=path): self.authenticate(permissions=("unrelated.permission",)) self.assertEqual(self.client.get(path).status_code, 403) self.authenticate(permissions=(permission,)) self.assertEqual(self.client.get(path).status_code, 200) def test_main_authentication_gate_redirects_to_existing_login(self): app = FastAPI() app.include_router(router) install_identity_middleware(app) with TestClient(app) as client, patch( "app.identity.request_user_or_none", return_value=None, ): for path in ROUTES: with self.subTest(path=path): response = client.get(path, follow_redirects=False) self.assertEqual(response.status_code, 303) self.assertTrue( response.headers["location"].startswith( "/login?next=" ) ) def test_page_permission_mapping_covers_new_routes(self): expected = { "/dashboard": "dashboard.view", "/monitoring/map": "locations.view", "/operations/repairs": "repairs.view", "/assets/miners": "miners.view", } for path, permission in expected.items(): with self.subTest(path=path): self.assertTrue(protected_page(path)) self.assertEqual( required_page_permission(path), permission, ) def test_dashboard_does_not_fabricate_operational_values(self): self.authenticate() response = self.client.get("/dashboard") for value in ( 'id="dashboardFleetTotal"', 'id="dashboardActiveRepairs"', 'id="dashboardSites"', "/assets/minerops-dashboard.js", "Runtime and lifecycle are separate dimensions", ): self.assertIn(value, response.text) self.assertIn("Loading operational overview", response.text) self.assertNotIn("Live health summary not yet connected", response.text) self.assertNotIn("100 TH/s", response.text) def test_dashboard_html_omits_unauthorized_sections_and_links(self): self.authenticate(permissions=("dashboard.view", "locations.view")) response = self.client.get("/dashboard") self.assertIn("Locations", response.text) self.assertIn("Open Mine Map", response.text) self.assertNotIn("Fleet overview", response.text) self.assertNotIn("Needs attention", response.text) self.assertNotIn("Open Fleet", response.text) self.assertNotIn("Active repair records", response.text) self.assertNotIn("Open Repair Center", response.text) self.assertNotIn("Mapped miners", response.text) self.assertNotIn("Unmapped miners", response.text) def test_dashboard_script_dom_contract_matches_authorized_page(self): self.authenticate() response = self.client.get("/dashboard") javascript = (STATIC_ROOT / "dashboard.js").read_text(encoding="utf-8") declared_ids = re.search(r"const ids = \[(.*?)\];", javascript, re.DOTALL) self.assertIsNotNone(declared_ids) required = set(re.findall(r'"([A-Za-z][A-Za-z0-9]+)"', declared_ids.group(1))) rendered = set(re.findall(r'id="([A-Za-z][A-Za-z0-9]+)"', response.text)) self.assertEqual(required - rendered, set()) def test_map_is_a_structured_operational_view(self): self.authenticate() response = self.client.get("/monitoring/map") for label in ( "Site", "Room", "Operational Status", "Miner details", "Find miner", ): self.assertIn(label, response.text) for value in ( 'id="mapSite"', 'id="mapRoom"', 'id="mapLayer"', 'id="mapProblemsOnly"', 'id="mapViewport"', 'id="mapInspector"', '/assets/minerops-mine-map.js', ): self.assertIn(value, response.text) self.assertNotIn("Live layout and telemetry data are not", response.text) def test_map_script_dom_contract_matches_rendered_page(self): self.authenticate() response = self.client.get("/monitoring/map") javascript = (STATIC_ROOT / "mine-map.js").read_text(encoding="utf-8") declared_ids = re.search( r"const ids = \[(.*?)\];", javascript, re.DOTALL, ) self.assertIsNotNone(declared_ids) required = set( re.findall( r'"([A-Za-z][A-Za-z0-9]+)"', declared_ids.group(1), ) ) required.update( re.findall( r'document\.getElementById\("([^"]+)"\)', javascript, ) ) rendered = set(re.findall(r'\bid="([^"]+)"', response.text)) self.assertEqual(required - rendered, set()) def test_data_views_include_all_request_states(self): self.authenticate() for path in ("/operations/repairs", "/assets/miners"): with self.subTest(path=path): response_text = self.client.get(path).text for state in ( "loading", "empty", "error", "denied", "ready", ): self.assertIn( f'data-view-state="{state}"', response_text, ) def test_frontend_assets_are_responsive_and_accessible(self): css = (STATIC_ROOT / "app.css").read_text(encoding="utf-8") javascript = (STATIC_ROOT / "app.js").read_text( encoding="utf-8" ) self.assertIn("@media (max-width: 560px)", css) self.assertIn(":focus-visible", css) self.assertIn(".skip-link", css) repair_center = (STATIC_ROOT / "repair-center.js").read_text( encoding="utf-8" ) self.assertIn('response.status === 401', repair_center) self.assertIn('response.status === 403', repair_center) self.assertIn('credentials: "same-origin"', javascript) def test_static_assets_are_served_without_an_api_route(self): css_response = self.client.get("/assets/minerops-app.css") script_response = self.client.get("/assets/minerops-app.js") self.assertEqual(css_response.status_code, 200) self.assertEqual( css_response.headers["content-type"], "text/css; charset=utf-8", ) self.assertEqual(script_response.status_code, 200) paths = {route.path for route in router.routes} self.assertFalse(any(path.startswith("/api/") for path in paths)) def test_live_app_prefers_new_shell_and_preserves_legacy_pages(self): from app.main import app from app.frontend.router import dashboard_page first_handlers = {} for route in app.routes: if route.path not in first_handlers: first_handlers[route.path] = route.endpoint for path in ROUTES: with self.subTest(path=path): self.assertEqual( first_handlers[path].__module__, "app.frontend.router", ) for path in ( "/repairs", "/locations", "/miners/{miner_id}/detail", ): with self.subTest(legacy_path=path): self.assertIn(path, first_handlers) dashboard_handlers = [ route.endpoint for route in app.routes if route.path == "/dashboard" and "GET" in (route.methods or set()) ] self.assertIs(dashboard_handlers[0], dashboard_page) self.assertEqual(dashboard_handlers.count(dashboard_page), 1) def test_live_dashboard_excludes_legacy_dashboard_injections(self): from app.main import app current_user = { "id": "00000000-0000-0000-0000-000000000654", "username": "dashboard-reader", "display_name": "Dashboard Reader", "role": "Read Only", "permissions": [ "dashboard.view", "miners.view", "locations.view", "repairs.view", ], } app.dependency_overrides[get_current_user] = lambda: current_user try: with patch( "app.identity.request_user_or_none", return_value=current_user, ): client = TestClient(app) try: response = client.get("/dashboard") finally: client.close() finally: app.dependency_overrides.pop(get_current_user, None) self.assertEqual(response.status_code, 200) self.assertIn("