Files
rfc-app/backend/tests/test_project_scoped_serving.py
Ben Stull 9ca07a3f81 §22 S1: thread collection_id through backend + update tests (N=1 unchanged, 454 green)
- collections.py resolution helpers (default_collection_id, type, initial_state)
- registry mirror writes project grouping fields + default-collection corpus fields
- auth.project_of_rfc joins collections; project_member_role reads memberships
- cache/api_*/funder writers+readers re-keyed to collection_id (cached_prs + denormalised
  project_id tags unchanged); api_deployment reads type/initial_state from the default collection
- projects.py restamp detects bootstrap via collections; initial_state via collection
- tests updated to the three-tier schema; test_migration_028 retired (superseded by 029)
- add @S1 acceptance test (collection grain + N=1 serving)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 08:26:14 -07:00

75 lines
3.1 KiB
Python

"""§22.4 (Plan B) — per-project RFC serving. A second project's corpus renders
under its own slug namespace via /api/projects/{pid}/rfcs[/{slug}], isolated
from the default project and gated by §22.5 visibility."""
from __future__ import annotations
from fastapi.testclient import TestClient
from test_propose_vertical import ( # noqa: F401
app_with_fake_gitea, tmp_env, provision_user_row, sign_in_as,
)
def _add_project(pid, name, vis="public"):
# §22 three-tier: a project + its default collection (keyed by the project
# id in tests, so default_collection_id(pid) == pid).
from app import db
db.conn().execute(
"INSERT OR IGNORE INTO projects (id, name, content_repo, visibility) VALUES (?, ?, ?, ?)",
(pid, name, pid + "-content", vis),
)
db.conn().execute(
"INSERT OR IGNORE INTO collections (id, project_id, type, subfolder, initial_state, visibility, name) "
"VALUES (?, ?, 'document', '', 'super-draft', ?, ?)",
(pid, pid, vis, name),
)
def _add_rfc(slug, title, pid, state="active"):
from app import db
# entries key by the project's default collection (id == pid in these tests)
db.conn().execute(
"INSERT INTO cached_rfcs (slug, title, state, collection_id) VALUES (?, ?, ?, ?)",
(slug, title, state, pid),
)
def test_catalog_scoped_to_one_project(app_with_fake_gitea):
app, _ = app_with_fake_gitea
with TestClient(app) as client:
_add_project("ecomm", "Ecomm")
_add_rfc("intro", "Default Intro", "default")
_add_rfc("intro", "Ecomm Intro", "ecomm")
_add_rfc("only-ecomm", "Ecomm Only", "ecomm")
d = client.get("/api/projects/default/rfcs").json()["items"]
e = client.get("/api/projects/ecomm/rfcs").json()["items"]
d_slugs = {i["slug"] for i in d}
e_slugs = {i["slug"] for i in e}
assert "intro" in d_slugs and "only-ecomm" not in d_slugs
assert {"intro", "only-ecomm"} <= e_slugs
def test_entry_is_isolated_by_project(app_with_fake_gitea):
app, _ = app_with_fake_gitea
with TestClient(app) as client:
_add_project("ecomm", "Ecomm")
_add_rfc("intro", "Default Intro", "default")
_add_rfc("intro", "Ecomm Intro", "ecomm")
_add_rfc("only-ecomm", "Ecomm Only", "ecomm")
assert client.get("/api/projects/default/rfcs/intro").json()["title"] == "Default Intro"
assert client.get("/api/projects/ecomm/rfcs/intro").json()["title"] == "Ecomm Intro"
# a slug that exists only in ecomm 404s under default
assert client.get("/api/projects/ecomm/rfcs/only-ecomm").status_code == 200
assert client.get("/api/projects/default/rfcs/only-ecomm").status_code == 404
def test_gated_project_catalog_404s_for_anon(app_with_fake_gitea):
app, _ = app_with_fake_gitea
with TestClient(app) as client:
_add_project("secret", "Secret", vis="gated")
_add_rfc("hush", "Hush", "secret")
assert client.get("/api/projects/secret/rfcs").status_code == 404
assert client.get("/api/projects/secret/rfcs/hush").status_code == 404