"""§22.4 (Plan B write): proposing a new entry into a *specific* project lands it in that project's content repo and surfaces under that project's proposals, isolated from the default project.""" 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 _register_ecomm(fake): from app import db db.conn().execute( "INSERT OR IGNORE INTO projects (id, name, content_repo, visibility) " "VALUES ('ecomm', 'Ecomm', 'ecomm-content', 'public')" ) db.conn().execute( "INSERT OR IGNORE INTO collections (id, project_id, type, subfolder, initial_state, visibility, name) " "VALUES ('ecomm', 'ecomm', 'document', '', 'super-draft', 'public', 'Ecomm')" ) fake._seed_repo("wiggleverse", "ecomm-content") def test_propose_into_second_project_lands_scoped(app_with_fake_gitea): app, fake = app_with_fake_gitea with TestClient(app) as client: _register_ecomm(fake) provision_user_row(user_id=3, login="alice", role="contributor") # §22 S3: the grandfathered implicit-public baseline covers only the N=1 # `default` collection; a second project requires an explicit scope grant # to write. Grant alice contributor at the ecomm project. from app import db db.conn().execute( "INSERT OR REPLACE INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('project', 'ecomm', 3, 'contributor')") sign_in_as(client, user_id=3, gitea_login="alice", display_name="Alice", role="contributor", email="alice@test") r = client.post("/api/projects/ecomm/rfcs/propose", json={ "title": "Cart", "slug": "cart", "pitch": "why a cart", "tags": [], }) assert r.status_code == 200, r.text # The idea PR shows under ecomm's proposals, not the default's. e = {i["slug"] for i in client.get("/api/projects/ecomm/proposals").json()["items"]} d = {i["slug"] for i in client.get("/api/projects/default/proposals").json()["items"]} assert "cart" in e assert "cart" not in d # It landed in ecomm's content repo, not the default 'meta' repo. assert ("wiggleverse", "ecomm-content") in { (o, rp) for (o, rp) in fake.branches if rp == "ecomm-content" } assert any( br.startswith("propose/cart") for br in fake.branches.get(("wiggleverse", "ecomm-content"), {}) ) def test_propose_into_gated_project_404s_for_non_member(app_with_fake_gitea): app, _ = app_with_fake_gitea from app import db with TestClient(app) as client: db.conn().execute( "INSERT OR IGNORE INTO projects (id, name, content_repo, visibility) " "VALUES ('secret', 'Secret', 'secret-content', 'gated')" ) db.conn().execute( "INSERT OR IGNORE INTO collections (id, project_id, type, subfolder, initial_state, visibility, name) " "VALUES ('secret', 'secret', 'document', '', 'super-draft', 'gated', 'Secret')" ) provision_user_row(user_id=4, login="bob", role="contributor") sign_in_as(client, user_id=4, gitea_login="bob", display_name="Bob", role="contributor", email="bob@test") r = client.post("/api/projects/secret/rfcs/propose", json={ "title": "X", "slug": "x", "pitch": "p", "tags": [], }) assert r.status_code == 404