diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 502e953da..618904258 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -4257,6 +4257,60 @@ def test_other_corp_private_post_chat_is_forbidden(client, demo_analyst_token, s assert listed.status_code == 403 +def test_other_corp_private_post_content_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + response = client.get( + f"/api/posts/{seeded_db['other_private_post_id']}/content", + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 403 + + +def test_other_corp_private_post_knowledge_graph_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + response = client.get( + f"/api/posts/{seeded_db['other_private_post_id']}/knowledge-graph", + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 403 + + +def test_other_corp_private_post_evaluation_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + response = client.get( + f"/api/posts/{seeded_db['other_private_post_id']}/evaluation", + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 403 + + +def test_other_corp_private_post_five_w1h_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + response = client.get( + f"/api/posts/{seeded_db['other_private_post_id']}/five-w1h", + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 403 + + +def test_other_corp_private_post_lineage_endpoint_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + """The per-post lineage endpoint (direct/indirect links), not the + corpus-wide /api/lineage graph -- a separate ABAC-gated route.""" + response = client.get( + f"/api/posts/{seeded_db['other_private_post_id']}/lineage", + headers={"Authorization": f"Bearer {demo_analyst_token}"}, + ) + assert response.status_code == 403 + + +def test_other_corp_private_post_bookmark_is_forbidden(client, demo_analyst_token, seeded_db) -> None: + headers = {"Authorization": f"Bearer {demo_analyst_token}"} + read = client.get(f"/api/posts/{seeded_db['other_private_post_id']}/bookmark", headers=headers) + assert read.status_code == 403 + written = client.post( + f"/api/posts/{seeded_db['other_private_post_id']}/bookmark", + json={"bookmarked": True}, + headers=headers, + ) + assert written.status_code == 403 + + @pytest.mark.skipif( not (_ORCHESTRATOR_BASE_URL and _ORCHESTRATOR_API_KEY), reason="set LINEAGEWEAVE_TEST_ORCHESTRATOR_BASE_URL and LINEAGEWEAVE_TEST_ORCHESTRATOR_API_KEY to run", diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 57117254a..d68474b66 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -256,7 +256,7 @@ adapter, fixture, or HTTP-shaped test double never upgrades a row to | Noto Sans, palette, table/form/button conventions, modal 50% mask and keyboard semantics | ADR 0118, token CSS, popup dialog implementation, frontend tests | source + unit | | Keyverse/OIDC login with real account | `auth.py`, OIDC discovery/JWKS boundary, local redirect check | source + local-integration; Keyverse open | | Authenticated corp/PU attributes | `/api/me` returns DB-backed codes; backend integration test covers `TEST-CORP`/`TEST-PU`, the GNB disclosure is covered by `App.test.tsx`, and desktop/390px Playwright QA verifies the rendered scope | source + unit + local-integration + browser-mocked | -| RBAC/ABAC, public/private visibility, tenant isolation | `_can_see_post` plus W author/admin raw-source exception, analysis eligibility excluding W, API authorization tests, aggregate-only runtime checks | source + local-integration | +| RBAC/ABAC, public/private visibility, tenant isolation | `_can_see_post` plus W author/admin raw-source exception, analysis eligibility excluding W, API authorization tests (extended in this worktree: `/content`, `/knowledge-graph`, `/evaluation`, `/five-w1h`, per-post `/lineage`, `/bookmark` each now have a dedicated other-corp-403 regression test -- all were already correctly gated via `_load_visible_post`, so this closes a test-coverage gap, not a bug), aggregate-only runtime checks | source + local-integration | | React product surface and PostgreSQL boundary | React routes/components, asyncpg API, Compose stack | source + local-integration | | Authorized PostgreSQL export import mapping | `scripts/import_postgresql_posts.py`, ADR 0121, hash-verified RFC 2557 MHTML resolver, and synthetic preflight/import tests; authorized relation has artifact-path metadata but no body/content/HTML field | source + unit + local-integration partial; operator artifact files and authorized live import open | | Bounded large-body search migration | `0035_body_search_prefix.sql`, `0036_normalized_body_search.sql`; live replay completed after bounded rendered-text indexing | source + local-integration |