def test_unauthenticated_dashboard_is_rejected(client): response = client.get("/api/v1/dashboard") assert response.status_code == 401 assert response.json()["error"]["code"] == "401" def test_oidc_status_is_disabled_without_configuration(client): assert client.get("/api/v1/auth/oidc/status").json() == { "enabled": False, "provider_name": None, } assert client.get("/api/v1/auth/oidc/login").status_code == 404 def test_oidc_callback_auto_provisions_and_logs_in(client, monkeypatch): import app.api.routers.auth as auth_router class FakeClient: async def authorize_access_token(self, _request): return { "userinfo": { "sub": "external-subject-1", "email": "oidc.user@example.test", "email_verified": True, "name": "OIDC User", } } class FakeOAuth: def create_client(self, _name): return FakeClient() monkeypatch.setattr(auth_router.settings, "oidc_enabled", True) monkeypatch.setattr(auth_router.settings, "oidc_issuer_url", "https://id.example.test") monkeypatch.setattr(auth_router.settings, "oidc_client_id", "client") monkeypatch.setattr(auth_router.settings, "oidc_client_secret", "secret") monkeypatch.setattr(auth_router.settings, "oidc_allowed_email_domains", "example.test") monkeypatch.setattr(auth_router, "oauth", FakeOAuth()) response = client.get("/api/v1/auth/oidc/callback", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"].endswith("/dashboard") session = client.get("/api/v1/auth/session") assert session.status_code == 200 assert session.json()["display_name"] == "OIDC User" assert session.json()["role"] == "rental_employee" def test_demo_login_grants_access(ops_client): response = ops_client.get("/api/v1/dashboard") assert response.status_code == 200 def test_rental_employee_cannot_reset_demo(employee_client): response = employee_client.post("/api/v1/demo/reset") assert response.status_code == 403 def test_operations_manager_can_reset_demo(ops_client): response = ops_client.post("/api/v1/demo/reset") assert response.status_code == 200 body = response.json() assert body["counts"]["vehicles"] == 50 assert body["anchor_date"] assert body["seeded_at"] assert body["scenario_integrity"]["all_ready"] is True assert body["scenario_integrity"]["not_ready"] == [] def test_reset_is_rejected_when_demo_allow_reset_is_disabled(ops_client, monkeypatch): import app.api.routers.demo as demo_router monkeypatch.setattr(demo_router.settings, "demo_allow_reset", False) response = ops_client.post("/api/v1/demo/reset") assert response.status_code == 403 # Restore real demo data: this test intentionally disabled reset, so a following test # module must not inherit a database left mid-mutation by an earlier test. monkeypatch.setattr(demo_router.settings, "demo_allow_reset", True) assert ops_client.post("/api/v1/demo/reset").status_code == 200 def test_session_endpoint_requires_authentication(client): response = client.get("/api/v1/demo/session") assert response.status_code == 401 def test_session_endpoint_confirms_logged_in_user(ops_client): response = ops_client.get("/api/v1/demo/session") assert response.status_code == 200 body = response.json() assert body["role"] == "operations_manager" assert body["public_ref"] == "USR-OPS" def test_logout_invalidates_session(ops_client): confirmed = ops_client.get("/api/v1/demo/session") assert confirmed.status_code == 200 logout = ops_client.post("/api/v1/demo/logout") assert logout.status_code == 200 after = ops_client.get("/api/v1/demo/session") assert after.status_code == 401 def test_logout_rejects_a_cookie_even_if_the_browser_retains_it(ops_client): from app.core.config import get_settings cookie_name = get_settings().session_cookie_name stolen_token = ops_client.cookies.get(cookie_name) assert stolen_token logout = ops_client.post("/api/v1/auth/logout") assert logout.status_code == 200 # Simulate the browser cookie race (or a copied cookie): server-side revocation is # authoritative and must reject the original signed token independently of deletion. ops_client.cookies.set(cookie_name, stolen_token) assert ops_client.get("/api/v1/auth/session").status_code == 401 # Remove the deliberately injected hostless cookie before exercising a normal browser # login. Otherwise httpx sends it alongside the real testserver cookie, which is not a # state a browser can create for the same origin/path pair. ops_client.cookies.clear() # A fresh login in the same second receives a distinct signed token and remains valid. fresh_login = ops_client.post("/api/v1/demo/login", json={"role": "operations_manager"}) assert fresh_login.status_code == 200 assert ops_client.get("/api/v1/auth/session").status_code == 200 def test_logout_without_a_session_is_safe(client): response = client.post("/api/v1/demo/logout") assert response.status_code == 200 def test_demo_reset_preserves_integration_telemetry(ops_client, client): from app.core.config import get_settings settings = get_settings() probe = client.get( "/api/v1/integrations/mcp/operations-summary", headers={ "X-Service-Token": settings.mcp_hub_service_token, "X-Client-Id": "itworx-mcp-hub:mobilityops:reset-probe", }, ) assert probe.status_code == 200 assert ops_client.post("/api/v1/demo/reset").status_code == 200 assert client.post("/api/v1/demo/login", json={"role": "operations_manager"}).status_code == 200 events = client.get("/api/v1/audit", params={"action": "mcp_tool_request"}).json() assert any(event["actor_label"].endswith(":reset-probe") for event in events)