"""End-to-end integration test for the §19.2 "branch-name path routing" candidate that Slice 8 settles. Slice 2's `branches/` endpoints used FastAPI's default `{branch}` matcher, which refuses slashes. Slice 2 worked around it with dash-separated branch names (`-draft-`); Slice 8 converts every `branches/` to `{branch:path}` and reorders the routes so the bare GET is declared last — the more-specific `threads` and `threads/{thread_id}/messages` GETs match first. The tests prove: * A branch with a slash in the name reads correctly via `GET /api/rfcs//branches/`. * The deeper threads GET still works for unslashed branches. * The deeper threads GET still works for slashed branches — the ordering discipline holds. * The POST routes for slashed branches still resolve (visibility, chat-seen, threads). """ from __future__ import annotations import pytest from test_propose_vertical import ( # noqa: F401 FakeGitea, app_with_fake_gitea, provision_user_row, sign_in_as, tmp_env, ) from test_rfc_view_vertical import SEED_BODY, seed_active_rfc # noqa: F401 def _seed_slashed_branch(fake: FakeGitea, *, slug: str, branch: str, body: str) -> None: """Seed a branch with a slash in the name directly on FakeGitea + cached_branches so the GET / threads / etc. endpoints can read it.""" from app import db repo_full = f"wiggleverse/rfc-0001-{slug}" owner, repo = repo_full.split("/", 1) sha = fake._next_sha() fake.branches[(owner, repo)][branch] = {"sha": sha, "ts": "2026-05-23T00:00:00Z"} fake.files[(owner, repo, branch, "RFC.md")] = {"content": body, "sha": sha} db.conn().execute( """ INSERT OR IGNORE INTO cached_branches (rfc_slug, branch_name, head_sha, state, last_commit_at) VALUES (?, ?, ?, 'open', datetime('now')) """, (slug, branch, sha), ) def test_get_branch_view_reads_slashed_branch_name(app_with_fake_gitea): """The §19.1 brief's headline assertion: a branch with a slash in the name reads correctly via the `{branch:path}` route shape.""" from fastapi.testclient import TestClient app, fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY) _seed_slashed_branch( fake, slug="ohm", branch="alice/feature-rename", body="# Slashed branch body\n", ) sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/rfcs/ohm/branches/alice/feature-rename") assert r.status_code == 200, r.text view = r.json() assert view["branch_name"] == "alice/feature-rename" assert "Slashed branch body" in view["body"] def test_deeper_threads_get_still_routes_for_unslashed_branch(app_with_fake_gitea): """Ordering discipline: declaring the bare GET last means the `threads` GET still wins for unslashed `branches/foo/threads`.""" from fastapi.testclient import TestClient app, fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY) _seed_slashed_branch( fake, slug="ohm", branch="plain-branch", body="# plain\n", ) sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/rfcs/ohm/branches/plain-branch/threads") assert r.status_code == 200, r.text # Empty until a thread is posted, but the route fired. assert "items" in r.json() def test_deeper_threads_get_still_routes_for_slashed_branch(app_with_fake_gitea): """The branch name carries a slash; the threads GET must still match (branch={branch:path} captures `alice/feature`, threads is the literal anchor).""" from fastapi.testclient import TestClient app, fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY) _seed_slashed_branch( fake, slug="ohm", branch="alice/feature", body="# slashed\n", ) sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.get("/api/rfcs/ohm/branches/alice/feature/threads") assert r.status_code == 200, r.text assert "items" in r.json() def test_post_visibility_resolves_for_slashed_branch(app_with_fake_gitea): """The §11.1 visibility POST must also route correctly for a slashed branch — proves the {branch:path} matcher applies to the write paths too.""" from fastapi.testclient import TestClient app, fake = app_with_fake_gitea with TestClient(app) as client: provision_user_row(user_id=2, login="alice", role="contributor") seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY) _seed_slashed_branch( fake, slug="ohm", branch="alice/private-work", body="# private\n", ) # Materialize the creator row so alice can flip her own branch. from app import db db.conn().execute( """ INSERT OR IGNORE INTO branch_visibility (rfc_slug, branch_name, read_public, contribute_mode) VALUES ('ohm', 'alice/private-work', 1, 'just-me') """ ) db.conn().execute( """ INSERT OR IGNORE INTO actions (actor_user_id, on_behalf_of, action_kind, rfc_slug, branch_name) VALUES (2, 'alice', 'create_branch', 'ohm', 'alice/private-work') """ ) sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor") r = client.post( "/api/rfcs/ohm/branches/alice/private-work/visibility", json={"read_public": False, "contribute_mode": "just-me"}, ) assert r.status_code == 200, r.text assert r.json()["ok"] is True