Spaces:
Running
Running
Download backend/tests/api/test_session_isolation.py from Aynkhn/Face-Expression-Analyzer: direct link, hf CLI and curl.
- Browser
- Download file 5.93 kB
-
https://huggingface.co/spaces/Aynkhn/Face-Expression-Analyzer/resolve/main/backend/tests/api/test_session_isolation.py
- Command line
-
hf download hf://spaces/Aynkhn/Face-Expression-Analyzer/backend/tests/api/test_session_isolation.py
-
curl -L -o test_session_isolation.py https://huggingface.co/spaces/Aynkhn/Face-Expression-Analyzer/resolve/main/backend/tests/api/test_session_isolation.py
5.93 kB
| """Each browser session sees only its own records. | |
| These are the tests that make the per-visitor dashboard trustworthy. Without | |
| them, a scoping mistake in a query would silently expose every visitor's history | |
| to every other visitor, and nothing else in the suite would notice. | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| BODY = {"dominant_emotion": "Happy", "duration_seconds": 10} | |
| READ_ROUTES = ["/api/logs", "/api/logs/stats"] | |
| def _post(client, headers, emotion="Happy"): | |
| return client.post( | |
| "/api/logs", | |
| json={"dominant_emotion": emotion, "duration_seconds": 10}, | |
| headers=headers, | |
| ) | |
| class TestSessionHeaderRequired: | |
| def test_reads_require_a_session_id(self, client, path): | |
| assert client.get(path).status_code == 400 | |
| def test_writes_require_a_session_id(self, client): | |
| response = client.post("/api/logs", json=BODY, headers={"X-API-Key": "test-ingest-key"}) | |
| assert response.status_code == 400 | |
| def test_malformed_session_ids_are_rejected(self, client, bad): | |
| response = client.get("/api/logs", headers={"X-Session-Id": bad}) | |
| assert response.status_code == 400 | |
| class TestReadIsolation: | |
| def test_a_session_sees_only_its_own_entries( | |
| self, client, ingest_headers, other_session_headers, session_headers | |
| ): | |
| _post(client, ingest_headers, "Happy") | |
| _post(client, ingest_headers, "Sad") | |
| _post(client, other_session_headers, "Angry") | |
| body = client.get("/api/logs", headers=session_headers).json() | |
| assert body["total"] == 2 | |
| assert {item["dominant_emotion"] for item in body["items"]} == {"Happy", "Sad"} | |
| def test_the_other_session_sees_only_its_own( | |
| self, client, ingest_headers, other_session_headers | |
| ): | |
| _post(client, ingest_headers, "Happy") | |
| _post(client, other_session_headers, "Angry") | |
| body = client.get( | |
| "/api/logs", headers={"X-Session-Id": other_session_headers["X-Session-Id"]} | |
| ).json() | |
| assert body["total"] == 1 | |
| assert body["items"][0]["dominant_emotion"] == "Angry" | |
| def test_stats_are_scoped_too( | |
| self, client, ingest_headers, other_session_headers, session_headers | |
| ): | |
| for _ in range(3): | |
| _post(client, ingest_headers, "Happy") | |
| _post(client, other_session_headers, "Sad") | |
| stats = client.get("/api/logs/stats", headers=session_headers).json() | |
| assert stats["total_count"] == 3 | |
| assert {e["emotion"] for e in stats["by_emotion"]} == {"Happy"} | |
| def test_an_empty_session_sees_nothing(self, client, ingest_headers): | |
| _post(client, ingest_headers, "Happy") | |
| body = client.get("/api/logs", headers={"X-Session-Id": "a-brand-new-session-99"}).json() | |
| assert body["total"] == 0 | |
| class TestCrossSessionAccessIsDenied: | |
| def test_cannot_fetch_another_sessions_entry_by_id( | |
| self, client, ingest_headers, other_session_headers | |
| ): | |
| created = _post(client, other_session_headers).json() | |
| response = client.get( | |
| f"/api/logs/{created['id']}", | |
| headers={"X-Session-Id": ingest_headers["X-Session-Id"]}, | |
| ) | |
| # 404 rather than 403: an id you do not own is indistinguishable from one | |
| # that does not exist, so the endpoint cannot be used to probe. | |
| assert response.status_code == 404 | |
| def test_cannot_delete_another_sessions_entry( | |
| self, client, ingest_headers, other_session_headers, session_headers | |
| ): | |
| created = _post(client, other_session_headers).json() | |
| response = client.delete(f"/api/logs/{created['id']}", headers=session_headers) | |
| assert response.status_code == 404 | |
| # And it is still there for its owner. | |
| owner = client.get( | |
| "/api/logs", headers={"X-Session-Id": other_session_headers["X-Session-Id"]} | |
| ).json() | |
| assert owner["total"] == 1 | |
| def test_clear_all_only_clears_the_callers_records( | |
| self, client, ingest_headers, other_session_headers, session_headers | |
| ): | |
| _post(client, ingest_headers, "Happy") | |
| _post(client, ingest_headers, "Sad") | |
| _post(client, other_session_headers, "Angry") | |
| response = client.delete("/api/logs?confirm=true", headers=session_headers) | |
| assert response.json()["deleted"] == 2 | |
| survivor = client.get( | |
| "/api/logs", headers={"X-Session-Id": other_session_headers["X-Session-Id"]} | |
| ).json() | |
| assert survivor["total"] == 1 | |
| class TestCorsAllowsTheSessionHeader: | |
| def test_preflight_permits_x_session_id(self, client): | |
| """A header missing from allow_headers is blocked before it is sent. | |
| curl ignores CORS, so this failure mode is invisible outside a browser: | |
| every request simply never leaves the page. | |
| """ | |
| response = client.options( | |
| "/api/logs", | |
| headers={ | |
| "Origin": "http://localhost:3000", | |
| "Access-Control-Request-Method": "GET", | |
| "Access-Control-Request-Headers": "x-session-id", | |
| }, | |
| ) | |
| allowed = response.headers.get("access-control-allow-headers", "").lower() | |
| assert "x-session-id" in allowed | |
| class TestNoOperatorKeyRequired: | |
| def test_reading_needs_no_api_key(self, client, ingest_headers, session_headers): | |
| _post(client, ingest_headers) | |
| # A plain session id is enough; there is no operator credential. | |
| assert "X-API-Key" not in session_headers | |
| assert client.get("/api/logs", headers=session_headers).status_code == 200 | |
| def test_response_does_not_echo_the_session_id(self, client, ingest_headers): | |
| body = _post(client, ingest_headers).json() | |
| assert "session_id" not in body | |