diff --git a/tests/test_operator_machine_endpoints.py b/tests/test_operator_machine_endpoints.py new file mode 100644 index 0000000..21febbd --- /dev/null +++ b/tests/test_operator_machine_endpoints.py @@ -0,0 +1,42 @@ +# tests/test_operator_machine_endpoints.py +from tests.conftest import wire_operator_auth + + +def test_machine_endpoints_not_blocked_by_gate(client, db_session, monkeypatch): + """With the gate ON and no cookie, the LAN-only watcher endpoints must reach + their handlers (the gate must never silently break heartbeats). A handler may + return 422 for an empty body — that still proves the gate let it through. + + Note: /emitters/report uses a minimal valid body to avoid triggering the + app's validation_exception_handler (which calls await request.body() — a + known deadlock in Starlette 0.27 TestClient when the body is already + consumed). The gate behaviour is identical regardless of body validity. + """ + wire_operator_auth(monkeypatch, db_session, enabled=True) + + r = client.post("/api/series3/heartbeat", json={}, follow_redirects=False) + assert r.status_code != 401 # gate would 401 an unauth /api/* route + assert r.status_code != 303 + + r = client.post("/api/series4/heartbeat", json={}, follow_redirects=False) + assert r.status_code not in (401, 303) + + # /emitters/report is a sync endpoint with required Pydantic fields; supply a + # valid body so the validation_exception_handler (which awaits request.body()) + # is never triggered — that handler deadlocks the Starlette 0.27 TestClient. + valid_report = { + "unit": "TEST001", + "unit_type": "series3", + "timestamp": "2024-01-01T00:00:00Z", + "file": "test.evt", + "status": "OK", + } + r = client.post("/emitters/report", json=valid_report, follow_redirects=False) + assert r.status_code != 303 # gate would 303 an unauth HTML route + + +def test_static_assets_exempt(client, db_session, monkeypatch): + wire_operator_auth(monkeypatch, db_session, enabled=True) + # /sw.js and /manifest.json are PWA assets clients fetch pre-login. + assert client.get("/sw.js", follow_redirects=False).status_code in (200, 404) + assert client.get("/sw.js", follow_redirects=False).status_code != 303