aee9b582e5
graduate, claim, retire/unretire, _read_meta_entry, mark_entry_reviewed, body extract/wrap (api_branches + api_prs replay) now dual-read and write metadata to the sidecar via write_entry_files + bot.commit_entry_files/ open_entry_pr — a migrated body-only .md no longer crashes entry.parse or re-grows frontmatter; legacy entries lazy-migrate on first metadata write. Existing tests updated to assert the sidecar (INV-2 clean docs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
146 lines
6.3 KiB
Python
146 lines
6.3 KiB
Python
"""SLICE-4 — write paths are sidecar-aware: a migrated (body-only `.md` +
|
|
sidecar) entry never crashes `entry.parse` nor re-grows frontmatter."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from app import gitea as gitea_mod, metadata
|
|
from app.bot import Actor, Bot
|
|
from app.config import load_config
|
|
|
|
from test_propose_vertical import ( # noqa: F401
|
|
app_with_fake_gitea,
|
|
provision_user_row,
|
|
tmp_env,
|
|
)
|
|
|
|
BODY_ONLY = "Alpha prose body.\n"
|
|
SIDECAR = ("slug: alpha\ntitle: Alpha\nstate: active\n"
|
|
"owners:\n- ben.stull\ntags:\n- one\npriority: P1\n")
|
|
|
|
|
|
def _seed_migrated(fake, repo="meta"):
|
|
fake.files[("wiggleverse", repo, "main", "rfcs/alpha.md")] = {
|
|
"content": BODY_ONLY, "sha": "m1"}
|
|
fake.files[("wiggleverse", repo, "main", "rfcs/alpha.meta.yaml")] = {
|
|
"content": SIDECAR, "sha": "m2"}
|
|
|
|
|
|
def _actor():
|
|
return Actor(user_id=1, gitea_login="ben.stull", display_name="Ben", email="ben@x.io")
|
|
|
|
|
|
def test_mark_entry_reviewed_on_migrated_entry(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
|
|
app, fake = app_with_fake_gitea
|
|
_seed_migrated(fake)
|
|
gitea = gitea_mod.Gitea(load_config())
|
|
bot = Bot(gitea)
|
|
with TestClient(app):
|
|
provision_user_row(user_id=1, login="ben.stull", role="owner")
|
|
# Must not raise (legacy code parsed body-only .md → ValueError).
|
|
asyncio.run(bot.mark_entry_reviewed(
|
|
_actor(), org="wiggleverse", meta_repo="meta", slug="alpha",
|
|
reviewed_by="ben.stull", reviewed_at="2026-06-07"))
|
|
md = fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")]["content"]
|
|
sc = fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")]["content"]
|
|
assert "---" not in md # body stays clean (no re-grown FM)
|
|
assert "reviewed_by: ben.stull" in sc # review stamp landed in the sidecar
|
|
|
|
|
|
# ---- Task 3.2: body extract/wrap helpers (api_branches) ----
|
|
|
|
def test_extract_wrap_body_on_body_only_md():
|
|
from app import api_branches
|
|
rfc = {"state": "super-draft", "repo": None, "slug": "alpha", "collection_id": "default"}
|
|
body = api_branches._extract_body_pure(rfc, BODY_ONLY, "main", is_meta=True)
|
|
assert body == BODY_ONLY
|
|
wrapped = api_branches._wrap_body_pure(rfc, BODY_ONLY, "new body\n", "main", is_meta=True)
|
|
assert wrapped == "new body\n" # stays clean — no re-grown frontmatter
|
|
|
|
|
|
def test_wrap_body_preserves_legacy_frontmatter():
|
|
from app import api_branches
|
|
legacy = "---\nslug: alpha\ntitle: Alpha\nstate: active\n---\n\nold body\n"
|
|
rfc = {"state": "super-draft", "repo": None, "slug": "alpha", "collection_id": "default"}
|
|
wrapped = api_branches._wrap_body_pure(rfc, legacy, "new body\n", "main", is_meta=True)
|
|
assert wrapped.startswith("---") # legacy frontmatter preserved
|
|
assert "new body" in wrapped
|
|
|
|
|
|
# ---- Task 3.3: PR-replay body wrappers (api_prs) ----
|
|
|
|
def test_replay_wrappers_on_body_only():
|
|
from app import api_prs
|
|
assert api_prs._extract_body_for_replay(True, BODY_ONLY) == BODY_ONLY
|
|
out = api_prs._wrap_body_for_replay(True, BODY_ONLY, "new\n")
|
|
assert out == "new\n" # clean, no re-grown frontmatter
|
|
legacy = "---\nslug: a\ntitle: A\nstate: active\n---\n\nold\n"
|
|
out2 = api_prs._wrap_body_for_replay(True, legacy, "new\n")
|
|
assert out2.startswith("---") # legacy preserved
|
|
|
|
|
|
# ---- Task 3.4: retire a fully-migrated (body-only + sidecar) entry ----
|
|
|
|
def test_retire_already_migrated_entry_does_not_crash(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
from app import cache, db
|
|
from app.config import load_config
|
|
|
|
app, fake = app_with_fake_gitea
|
|
_seed_migrated(fake)
|
|
with TestClient(app) as client:
|
|
provision_user_row(user_id=1, login="ben", role="owner")
|
|
from test_propose_vertical import sign_in_as
|
|
sign_in_as(client, user_id=1, gitea_login="ben", display_name="Ben", role="owner")
|
|
# Ingest the migrated entry so the catalog/cache knows it.
|
|
asyncio.run(cache.refresh_meta_repo(load_config(), gitea_mod.Gitea(load_config())))
|
|
assert db.conn().execute(
|
|
"SELECT state FROM cached_rfcs WHERE slug='alpha'").fetchone()["state"] == "active"
|
|
r = client.post("/api/rfcs/alpha/retire")
|
|
assert r.status_code == 200, r.text
|
|
assert r.json()["state"] == "retired"
|
|
import yaml as _yaml
|
|
sc = _yaml.safe_load(
|
|
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")]["content"])
|
|
assert sc["state"] == "retired"
|
|
md = fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")]["content"]
|
|
assert "---" not in md # body stayed clean
|
|
|
|
|
|
# ---- Task 3.5: graduate a fully-migrated super-draft entry ----
|
|
|
|
SUPER_SIDECAR = ("slug: alpha\ntitle: Alpha\nstate: super-draft\n"
|
|
"owners:\n- ben\ntags:\n- one\npriority: P1\n")
|
|
|
|
|
|
def test_graduate_already_migrated_super_draft(app_with_fake_gitea):
|
|
from fastapi.testclient import TestClient
|
|
from app import cache, db
|
|
from app.config import load_config
|
|
|
|
app, fake = app_with_fake_gitea
|
|
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")] = {
|
|
"content": BODY_ONLY, "sha": "m1"}
|
|
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")] = {
|
|
"content": SUPER_SIDECAR, "sha": "m2"}
|
|
with TestClient(app) as client:
|
|
from test_propose_vertical import sign_in_as
|
|
provision_user_row(user_id=1, login="ben", role="owner")
|
|
sign_in_as(client, user_id=1, gitea_login="ben", display_name="Ben", role="owner")
|
|
asyncio.run(cache.refresh_meta_repo(load_config(), gitea_mod.Gitea(load_config())))
|
|
assert db.conn().execute(
|
|
"SELECT state FROM cached_rfcs WHERE slug='alpha'").fetchone()["state"] == "super-draft"
|
|
r = client.post("/api/rfcs/alpha/graduate?_sync=1",
|
|
json={"rfc_id": "RFC-0007", "owners": ["ben"]})
|
|
assert r.status_code == 200, r.text
|
|
import yaml as _yaml
|
|
sc = _yaml.safe_load(
|
|
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")]["content"])
|
|
assert sc["state"] == "active"
|
|
assert sc["id"] == "RFC-0007"
|
|
assert sc.get("priority") == "P1" # INV-7 carried through graduation
|
|
md = fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")]["content"]
|
|
assert "---" not in md # body stayed clean
|