"""End-to-end integration test for the §19.2 "cache bootstrap from a pre-existing meta repo" candidate that Slice 8 settles. Per §4.1, the cache is rebuildable from Gitea. Per §15.9, the cache resolves the actor on a PR by joining against the §6.5 `actions` log, falling back to the `On-behalf-of:` trailer in the PR body, then to the raw Gitea login as last resort. Slice 1 chose this fallback chain and Slice 8 exercises it against history the bot did not author — the disaster-recovery / transferred-meta-repo case. The tests prove: * When `actions` carries a matching row, the audit log wins — `opened_by` reads the on-behalf-of login regardless of the bot's appearance as Gitea opener. * When `actions` is empty but the PR body carries `On-behalf-of: Name `, the trailer parses cleanly. * When both are absent, the raw Gitea opener wins (and if even that is the bot, the bot login is what surfaces — the v1 fallback, honest about who Gitea sees). * The §15.9 framing holds: the bot login surfaces only in the Git log and the trailer, never inside the audit-log path. """ from __future__ import annotations import json import pytest from test_propose_vertical import ( # noqa: F401 FakeGitea, app_with_fake_gitea, provision_user_row, sign_in_as, tmp_env, ) def _seed_meta_pr_directly_in_fake( fake: FakeGitea, *, slug: str, pr_number: int, head_branch: str, title: str, body: str, gitea_opener_login: str, ) -> None: """Push a PR into FakeGitea as if it existed before the app cache was bootstrapped — no propose endpoint, no audit log row, just a pull on the meta repo with a chosen `user.login`. The reconciler pulls this on its first sweep.""" pr = { "number": pr_number, "title": title, "body": body, "head": {"ref": head_branch, "sha": "sha-bootstrap"}, "base": {"ref": "main"}, "state": "open", "merged": False, "merged_at": None, "closed_at": None, "created_at": "2026-05-23T00:00:00Z", "user": {"login": gitea_opener_login}, } fake.pulls.setdefault(("wiggleverse", "meta"), []).append(pr) # Create a corresponding branch so refresh_meta_branches sees it. fake.branches[("wiggleverse", "meta")][head_branch] = { "sha": "sha-bootstrap", "ts": "2026-05-23T00:00:00Z", } def test_audit_log_first_wins_over_bot_opener(app_with_fake_gitea): """If `actions` carries a row for this PR, that's the authoritative actor — even when Gitea reports the bot as the opener. This is the common steady-state path (the bot opened on behalf of a human, the audit log captured it).""" from fastapi.testclient import TestClient from app import cache as cache_mod, db app, fake = app_with_fake_gitea with TestClient(app) as client: # Seed a cached RFC and an `actions` row for the PR before # the reconciler runs. db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('alpha', 'Alpha', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) db.conn().execute( """ INSERT INTO actions (actor_user_id, on_behalf_of, action_kind, rfc_slug, pr_number) VALUES (NULL, 'alice', 'propose_rfc', 'alpha', 99) """ ) # Seed the fake's PR as if Gitea reports the bot as opener. _seed_meta_pr_directly_in_fake( fake, slug="alpha", pr_number=99, head_branch="propose/alpha", title="Propose: Alpha", body="Some idea\n", gitea_opener_login="rfc-bot", ) import asyncio asyncio.run(cache_mod.refresh_meta_pulls(app.state.config, app.state.gitea)) row = db.conn().execute( "SELECT opened_by FROM cached_prs WHERE pr_number = 99" ).fetchone() assert row is not None # Audit-log row wins. assert row["opened_by"] == "alice" def test_trailer_parses_when_audit_log_missing(app_with_fake_gitea): """The cache-bootstrap-against-history-the-bot-did-not-author case: no `actions` rows, but the PR body carries the §6.5 `On-behalf-of:` trailer. The trailer parses cleanly and the right actor surfaces.""" from fastapi.testclient import TestClient from app import cache as cache_mod, db app, fake = app_with_fake_gitea with TestClient(app) as client: db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('beta', 'Beta', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) # No actions row. PR body carries the trailer. _seed_meta_pr_directly_in_fake( fake, slug="beta", pr_number=42, head_branch="propose/beta", title="Propose: Beta", body="A new framing\n\nOn-behalf-of: Charlie ", gitea_opener_login="rfc-bot", ) import asyncio asyncio.run(cache_mod.refresh_meta_pulls(app.state.config, app.state.gitea)) row = db.conn().execute( "SELECT opened_by FROM cached_prs WHERE pr_number = 42" ).fetchone() assert row["opened_by"] == "charlie" def test_raw_gitea_login_used_when_audit_and_trailer_both_absent(app_with_fake_gitea): """When neither the audit log nor the trailer carries an actor, the raw Gitea login is the last-resort fallback per §15.9 / Slice 1. A non-bot login surfaces as the actor; a bot login surfaces as the bot (honest about what Gitea sees — the v1 contract).""" from fastapi.testclient import TestClient from app import cache as cache_mod, db app, fake = app_with_fake_gitea with TestClient(app) as client: db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('gamma', 'Gamma', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) # No audit row, no trailer — but Gitea reports a real user as opener. _seed_meta_pr_directly_in_fake( fake, slug="gamma", pr_number=7, head_branch="propose/gamma", title="Propose: Gamma", body="No trailer here\n", gitea_opener_login="dana", # a real human, not the bot ) import asyncio asyncio.run(cache_mod.refresh_meta_pulls(app.state.config, app.state.gitea)) row = db.conn().execute( "SELECT opened_by FROM cached_prs WHERE pr_number = 7" ).fetchone() assert row["opened_by"] == "dana" def test_full_reconciler_sweep_resolves_actors_via_fallback_chain(app_with_fake_gitea): """End-to-end: a clean `cached_*` set plus a meta repo that has history with no audit-log rows. The reconciler's first sweep must bring everything up correctly — every PR resolves an actor through the fallback chain without crashing.""" from fastapi.testclient import TestClient from app import cache as cache_mod, db app, fake = app_with_fake_gitea with TestClient(app) as client: # Three PRs, three fallback paths exercised. # 1. With audit-log row. db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('alpha', 'Alpha', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) db.conn().execute( """ INSERT INTO actions (actor_user_id, on_behalf_of, action_kind, rfc_slug, pr_number) VALUES (NULL, 'alice', 'propose_rfc', 'alpha', 1) """ ) _seed_meta_pr_directly_in_fake( fake, slug="alpha", pr_number=1, head_branch="propose/alpha", title="A", body="b", gitea_opener_login="rfc-bot", ) # 2. Trailer only. db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('beta', 'Beta', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) _seed_meta_pr_directly_in_fake( fake, slug="beta", pr_number=2, head_branch="propose/beta", title="B", body="On-behalf-of: Bob ", gitea_opener_login="rfc-bot", ) # 3. Gitea opener as last resort. db.conn().execute( """ INSERT INTO cached_rfcs (slug, title, state, owners_json, arbiters_json, tags_json, body) VALUES ('gamma', 'Gamma', 'super-draft', '[]', '[]', '[]', 'pitch') """ ) _seed_meta_pr_directly_in_fake( fake, slug="gamma", pr_number=3, head_branch="propose/gamma", title="G", body="naked", gitea_opener_login="carol", ) import asyncio asyncio.run(cache_mod.refresh_meta_pulls(app.state.config, app.state.gitea)) prs = { r["pr_number"]: r["opened_by"] for r in db.conn().execute( "SELECT pr_number, opened_by FROM cached_prs ORDER BY pr_number" ) } assert prs[1] == "alice" # audit log wins assert prs[2] == "bob" # trailer wins assert prs[3] == "carol" # raw Gitea opener wins