From 48c04e81cd3d58e5bfaa6b99beb6e4f73c766c1f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 21:01:16 +0900 Subject: [PATCH 1/8] test(api): cover POST /api/ask, GET /api/rankings, PATCH /api/me/preferences Found via a systematic route-vs-test cross-reference (every @app.get/ post/patch/put/delete path in backend/app/main.py checked against every test file, not just backend/tests/test_api.py) -- same technique that found the /healthz routing bug earlier this session. All three endpoints had zero test coverage anywhere in the repo: - POST /api/ask: the Ask Agent endpoint itself was never exercised at the HTTP layer, despite its underlying functions (gather_global_chat_sources, cited_post_evidence, ...) being unit-tested. New tests cover the empty-question 422, the no-orchestrator-configured 503 (Null client, matching the existing derive-commitment 503 test's monkeypatch pattern), and the unauthenticated 401/403 case. - GET /api/rankings: covers the real response contract (RankWeave's own "never invent a fused score" fail-closed shape -- status is either "accepted" or "unavailable", never a guessed ranking) plus the unauthenticated case. - PATCH /api/me/preferences: covers persisting a supported locale (round-tripped through GET /api/me) and rejecting an unsupported one (Pydantic's own Literal validation, previously untested). uv run --frozen python -m pytest -q: 760 passed, 17 skipped. --- backend/tests/test_api.py | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 438b4786a..669d2e217 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -1138,6 +1138,45 @@ def test_me_reflects_the_authenticated_account(client, demo_analyst_token) -> No ) +def test_update_me_preferences_persists_a_supported_locale(client, demo_analyst_token) -> None: + response = client.patch( + "/api/me/preferences", + json={"preferred_locale": "ko"}, + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 200 + assert response.json() == {"preferred_locale": "ko"} + + me_response = client.get("/api/me", headers={"Authorization": f"Bearer {demo_analyst_token}"}) + assert me_response.json()["preferred_locale"] == "ko" + + +def test_update_me_preferences_rejects_an_unsupported_locale(client, demo_analyst_token) -> None: + response = client.patch( + "/api/me/preferences", + json={"preferred_locale": "fr"}, + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 422 + + +def test_rankings_never_crashes_and_never_invents_a_score_when_unavailable( + client, demo_analyst_token, seeded_db +) -> None: + response = client.get("/api/rankings", headers={"Authorization": f"Bearer {demo_analyst_token}"}) + assert response.status_code == 200 + body = response.json() + assert body["port"] == "rankweave" + assert body["status"] in ("accepted", "unavailable") + if body["status"] == "unavailable": + assert body["rankings"] == [] + + +def test_rankings_requires_authentication(client) -> None: + response = client.get("/api/rankings") + assert response.status_code in (401, 403) + + def test_customer_master_returns_authorized_catalog_contract(client, demo_analyst_token, seeded_db) -> None: admin_conn = psycopg2.connect(seeded_db["dsn"]) try: @@ -4137,6 +4176,35 @@ def test_derive_commitment_unavailable_without_orchestrator( assert response.status_code == 503 +def test_ask_rejects_an_empty_question(client, demo_analyst_token, seeded_db) -> None: + response = client.post( + "/api/ask", + json={"question": " "}, + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 422 + + +def test_ask_is_unavailable_without_orchestrator_credentials( + client, demo_analyst_token, seeded_db, monkeypatch +) -> None: + """Null chat client must 503, not invent an answer.""" + from lineageweave.post_chat import NullPostChatClient + + monkeypatch.setattr("backend.app.main._post_chat_client", lambda: NullPostChatClient()) + response = client.post( + "/api/ask", + json={"question": "What happened with the public post?"}, + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 503 + + +def test_ask_requires_authentication(client) -> None: + response = client.post("/api/ask", json={"question": "Any question"}) + assert response.status_code in (401, 403) + + def test_derive_commitment_uses_post_created_at_and_does_not_duplicate( client, demo_analyst_token, seeded_db, monkeypatch ) -> None: From b80628bc53d372b82149a48699e0856bec0bd9dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 21:25:45 +0900 Subject: [PATCH 2/8] fix(frontend): use OIDC return-url helpers on the login button Same shared-ancestor bug as #418/#415/#426/#427/#429/#431/#434/#436: the login button built an unsanitized returnUrl inline instead of returnUrlFromLocation()/rememberOidcReturnUrl(), and removed the unreachable login-screen AdminPanel render (accessToken is always undefined pre-auth). --- frontend/src/App.test.tsx | 3 +++ frontend/src/App.tsx | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 7462abd2c..70eb27590 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -41,6 +41,9 @@ describe("App, unauthenticated", () => { state: expect.objectContaining({ returnUrl: expect.stringMatching(/^\//) }), }), ); + // Persisted as a fallback in case the OIDC state round-trip is dropped + // (see oidcReturnUrl.ts's restoreOidcReturnUrl, consumed in main.tsx). + expect(window.sessionStorage.getItem("lineageweave.oidc.returnUrl")).toMatch(/^\//); }); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6fba0dd41..1b5b351ab 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4610,7 +4610,8 @@ export default function App({ showLabPanels = false }: { showLabPanels?: boolean
- {destination === "admin" ? : null}