From 8db593a54401299ce59982a088483bcb1c6108e6 Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 16 Jun 2026 12:06:56 -0400 Subject: [PATCH] test(api): repair pre-existing rotted tests (SSE ticket flow, password policy) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These had been red since the changes they cover landed — invisible because the pre-commit gate runs mypy/ruff/bandit/pip-audit but NOT pytest, so failing tests don't block commits and quietly accumulate. - SSE stream/events auth migrated from ?token= to a single-use ?ticket= (commit efb4e49d). Three tests still passed a raw JWT as ?token= and got 401. Updated to mint a ticket via POST /auth/sse-ticket and pass ?ticket= (attacker events, topology events, /stream). - The user-creation password policy is min_length=12; the RBAC admin-access test still used a 10-char password and was rejected. Bumped to a valid one. --- tests/api/attackers/test_events_stream.py | 10 +++++++++- tests/api/stream/test_stream_events.py | 13 +++++++++++-- tests/api/test_rbac.py | 2 +- tests/api/topology/test_events_stream.py | 10 +++++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/tests/api/attackers/test_events_stream.py b/tests/api/attackers/test_events_stream.py index 05c824c2..efb5e105 100644 --- a/tests/api/attackers/test_events_stream.py +++ b/tests/api/attackers/test_events_stream.py @@ -106,9 +106,17 @@ async def test_events_missing_attacker_404(auth_token, _fake_app_bus): async with httpx.AsyncClient( transport=httpx.ASGITransport(app=app), base_url="http://test", ) as ac: + # SSE auth is a single-use ?ticket= minted from the JWT (EventSource + # can't set headers); a raw ?token= is no longer accepted. + tr = await ac.post( + "/api/v1/auth/sse-ticket", + headers={"Authorization": f"Bearer {auth_token}"}, + ) + assert tr.status_code == 200, tr.text + ticket = tr.json()["ticket"] r = await ac.get( f"{_V1}/{_OTHER_UUID}/events", - params={"token": auth_token}, + params={"ticket": ticket}, ) assert r.status_code == 404 diff --git a/tests/api/stream/test_stream_events.py b/tests/api/stream/test_stream_events.py index d66e2f97..147a05ac 100644 --- a/tests/api/stream/test_stream_events.py +++ b/tests/api/stream/test_stream_events.py @@ -50,12 +50,21 @@ class TestStreamEvents: assert resp.status_code in (200, 500) @pytest.mark.asyncio - async def test_stream_with_query_token(self, client: httpx.AsyncClient, auth_token: str): + async def test_stream_with_query_ticket(self, client: httpx.AsyncClient, auth_token: str): + # EventSource can't set headers, so the stream authenticates via a + # single-use ?ticket= minted from the JWT; a raw ?token= is no longer + # accepted (it would leak the bearer into proxy/access logs). with patch("decnet.web.router.stream.api_stream_events.repo") as mock_repo: _mock_repo_prefetch(mock_repo) + tr = await client.post( + "/api/v1/auth/sse-ticket", + headers={"Authorization": f"Bearer {auth_token}"}, + ) + assert tr.status_code == 200, tr.text + ticket = tr.json()["ticket"] resp = await client.get( "/api/v1/stream", - params={"token": auth_token, "lastEventId": "0"}, + params={"ticket": ticket, "lastEventId": "0"}, ) assert resp.status_code in (200, 500) diff --git a/tests/api/test_rbac.py b/tests/api/test_rbac.py index f7087632..f866d7af 100644 --- a/tests/api/test_rbac.py +++ b/tests/api/test_rbac.py @@ -39,7 +39,7 @@ async def test_admin_can_access_read_endpoints(client, auth_token, method, path) _ADMIN_ENDPOINTS = [ ("PUT", "/api/v1/config/deployment-limit", {"deployment_limit": 5}), ("PUT", "/api/v1/config/global-mutation-interval", {"global_mutation_interval": "1d"}), - ("POST", "/api/v1/config/users", {"username": "rbac-test", "password": "pass123456", "role": "viewer"}), + ("POST", "/api/v1/config/users", {"username": "rbac-test", "password": "pass-123456789", "role": "viewer"}), ] diff --git a/tests/api/topology/test_events_stream.py b/tests/api/topology/test_events_stream.py index b10bfae7..aaa2a027 100644 --- a/tests/api/topology/test_events_stream.py +++ b/tests/api/topology/test_events_stream.py @@ -66,9 +66,17 @@ async def test_events_missing_topology_404(auth_token, _fake_app_bus): async with httpx.AsyncClient( transport=httpx.ASGITransport(app=app), base_url="http://test", ) as ac: + # SSE auth is a single-use ?ticket= minted from the JWT (EventSource + # can't set headers); a raw ?token= is no longer accepted. + tr = await ac.post( + "/api/v1/auth/sse-ticket", + headers={"Authorization": f"Bearer {auth_token}"}, + ) + assert tr.status_code == 200, tr.text + ticket = tr.json()["ticket"] r = await ac.get( f"{_V1}/nope/events", - params={"token": auth_token}, + params={"ticket": ticket}, ) assert r.status_code == 404