§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>
This commit is contained in:
@@ -16,14 +16,18 @@ from test_propose_vertical import ( # noqa: F401
|
||||
tmp_env,
|
||||
)
|
||||
|
||||
# The 19 tables migration 026 threads project_id onto (docs/design/
|
||||
# multi-project-spec.md §5 amendment list).
|
||||
SLUG_TABLES = [
|
||||
"cached_rfcs", "cached_branches", "cached_prs", "branch_visibility",
|
||||
"branch_contribute_grants", "stars", "threads", "changes", "pr_seen",
|
||||
"branch_chat_seen", "watches", "notifications", "actions",
|
||||
"pr_resolution_branches", "funder_consents", "rfc_invitations",
|
||||
"rfc_collaborators", "proposed_use_cases", "contribution_requests",
|
||||
# §22 three-tier (migration 029): the entry-corpus grain is the collection, so
|
||||
# the 13 tables migration 028 keyed by project_id re-key to collection_id. The
|
||||
# remaining tables 026 tagged keep their denormalised project_id (project grain).
|
||||
COLLECTION_TABLES = [
|
||||
"cached_rfcs", "cached_branches", "branch_visibility",
|
||||
"branch_contribute_grants", "stars", "pr_seen", "branch_chat_seen",
|
||||
"watches", "funder_consents", "rfc_invitations", "rfc_collaborators",
|
||||
"proposed_use_cases", "contribution_requests",
|
||||
]
|
||||
PROJECT_TAG_TABLES = [
|
||||
"cached_prs", "threads", "changes", "notifications", "actions",
|
||||
"pr_resolution_branches",
|
||||
]
|
||||
|
||||
|
||||
@@ -45,25 +49,28 @@ def test_default_project_seeded_and_backfilled(app_with_fake_gitea):
|
||||
assert row["content_repo"] == "meta"
|
||||
|
||||
|
||||
def test_project_id_on_every_slug_table(app_with_fake_gitea):
|
||||
def test_grain_columns_on_every_slug_table(app_with_fake_gitea):
|
||||
from app import db
|
||||
|
||||
app, _ = app_with_fake_gitea
|
||||
with TestClient(app):
|
||||
for table in SLUG_TABLES:
|
||||
cols = {r["name"]: r for r in db.conn().execute(
|
||||
f"PRAGMA table_info({table})"
|
||||
)}
|
||||
assert "project_id" in cols, f"{table} missing project_id"
|
||||
col = cols["project_id"]
|
||||
# NOT NULL with the constant 'default' backfill default.
|
||||
assert col["notnull"] == 1, f"{table}.project_id should be NOT NULL"
|
||||
assert col["dflt_value"] == "'default'", f"{table}.project_id default"
|
||||
# The entry-corpus tables key on collection_id (NOT NULL, 'default').
|
||||
for table in COLLECTION_TABLES:
|
||||
cols = {r["name"]: r for r in db.conn().execute(f"PRAGMA table_info({table})")}
|
||||
assert "collection_id" in cols, f"{table} missing collection_id"
|
||||
assert "project_id" not in cols, f"{table} should no longer have project_id"
|
||||
col = cols["collection_id"]
|
||||
assert col["notnull"] == 1, f"{table}.collection_id should be NOT NULL"
|
||||
assert col["dflt_value"] == "'default'", f"{table}.collection_id default"
|
||||
# The project-tag tables keep their denormalised project_id.
|
||||
for table in PROJECT_TAG_TABLES:
|
||||
cols = {r["name"] for r in db.conn().execute(f"PRAGMA table_info({table})")}
|
||||
assert "project_id" in cols, f"{table} missing project_id tag"
|
||||
|
||||
|
||||
def test_existing_row_backfills_to_default(app_with_fake_gitea):
|
||||
"""A row inserted the old way (no project_id) lands in the default
|
||||
project — the trick that keeps every pre-multi-project INSERT working."""
|
||||
"""A row inserted the old way (no collection grain) lands in the default
|
||||
collection — the trick that keeps every pre-three-tier INSERT working."""
|
||||
from app import db
|
||||
|
||||
app, _ = app_with_fake_gitea
|
||||
@@ -73,35 +80,36 @@ def test_existing_row_backfills_to_default(app_with_fake_gitea):
|
||||
("human", "Human", "active"),
|
||||
)
|
||||
got = db.conn().execute(
|
||||
"SELECT project_id FROM cached_rfcs WHERE slug = 'human'"
|
||||
).fetchone()["project_id"]
|
||||
"SELECT collection_id FROM cached_rfcs WHERE slug = 'human'"
|
||||
).fetchone()["collection_id"]
|
||||
assert got == "default"
|
||||
|
||||
|
||||
def test_project_members_table_shape(app_with_fake_gitea):
|
||||
def test_memberships_table_shape(app_with_fake_gitea):
|
||||
from app import db
|
||||
|
||||
app, _ = app_with_fake_gitea
|
||||
with TestClient(app):
|
||||
cols = {r["name"] for r in db.conn().execute(
|
||||
"PRAGMA table_info(project_members)"
|
||||
)}
|
||||
assert cols == {"project_id", "user_id", "role", "granted_by", "granted_at"}
|
||||
# The role CHECK rejects an unknown role.
|
||||
# §22 three-tier: project_members generalised into memberships.
|
||||
assert db.conn().execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='project_members'"
|
||||
).fetchone() is None
|
||||
cols = {r["name"] for r in db.conn().execute("PRAGMA table_info(memberships)")}
|
||||
assert {"scope_type", "scope_id", "user_id", "role", "granted_by", "granted_at"} <= cols
|
||||
db.conn().execute(
|
||||
"INSERT INTO users (id, display_name, role) VALUES (1, 'Ben', 'owner')"
|
||||
)
|
||||
db.conn().execute(
|
||||
"INSERT INTO project_members (project_id, user_id, role) "
|
||||
"VALUES ('default', 1, 'project_admin')"
|
||||
"INSERT INTO memberships (scope_type, scope_id, user_id, role) "
|
||||
"VALUES ('collection', 'default', 1, 'owner')"
|
||||
)
|
||||
import sqlite3
|
||||
try:
|
||||
db.conn().execute(
|
||||
"INSERT INTO project_members (project_id, user_id, role) "
|
||||
"VALUES ('default', 1, 'nonsense')"
|
||||
"INSERT INTO memberships (scope_type, scope_id, user_id, role) "
|
||||
"VALUES ('collection', 'default', 1, 'nonsense')"
|
||||
)
|
||||
assert False, "CHECK should reject an unknown project role"
|
||||
assert False, "CHECK should reject an unknown role"
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user