feat(slice4): metadata sidecar git read/write helpers — dual-read + lazy-migrate ops (§22.4a)
apply_values, EntryGitState, read_entry_from_git, write_entry_files, sidecar_path_for. Plus the SLICE-4 implementation plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -197,3 +197,27 @@ def test_sidecar_filename_helpers():
|
||||
assert metadata.is_sidecar("view-metrics.meta.yaml") is True
|
||||
assert metadata.is_sidecar("view-metrics.md") is False
|
||||
assert metadata.slug_of_sidecar("view-metrics.meta.yaml") == "view-metrics"
|
||||
|
||||
|
||||
# ---- SLICE-4: sidecar_path_for + apply_values ----
|
||||
|
||||
def test_sidecar_path_for_derives_sibling():
|
||||
assert metadata.sidecar_path_for("rfcs/alpha.md") == "rfcs/alpha.meta.yaml"
|
||||
assert metadata.sidecar_path_for("x/y/beta.md") == "x/y/beta.meta.yaml"
|
||||
|
||||
|
||||
def test_apply_values_updates_known_and_extra_fields():
|
||||
e = entry_mod.parse(LEGACY_MD) # has tags + extra priority/owner
|
||||
e2 = metadata.apply_values(e, {"tags": ["x"], "priority": "P0", "owner": "sam"})
|
||||
assert e2.tags == ["x"]
|
||||
assert e2.extra["priority"] == "P0"
|
||||
assert e2.extra["owner"] == "sam"
|
||||
# body preserved unchanged
|
||||
assert e2.body == e.body
|
||||
|
||||
|
||||
def test_apply_values_preserves_unspecified_keys():
|
||||
e = entry_mod.parse(LEGACY_MD)
|
||||
e2 = metadata.apply_values(e, {"priority": "P0"})
|
||||
assert e2.tags == e.tags # untouched
|
||||
assert e2.extra["owner"] == "hasan" # untouched
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"""SLICE-4 — git-aware sidecar read/write helpers.
|
||||
|
||||
Uses the FakeGitea from the propose-vertical fixtures (no network).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import yaml
|
||||
|
||||
from app import gitea as gitea_mod, metadata
|
||||
from app.config import load_config
|
||||
|
||||
from test_propose_vertical import app_with_fake_gitea, tmp_env # noqa: F401
|
||||
|
||||
LEGACY = """---
|
||||
slug: alpha
|
||||
title: Alpha
|
||||
state: active
|
||||
owners:
|
||||
- ben.stull
|
||||
tags:
|
||||
- one
|
||||
priority: P1
|
||||
---
|
||||
|
||||
Alpha body.
|
||||
"""
|
||||
|
||||
MIGRATED_MD = "Alpha body.\n"
|
||||
MIGRATED_SIDECAR = """slug: alpha
|
||||
title: Alpha
|
||||
state: active
|
||||
owners:
|
||||
- ben.stull
|
||||
tags:
|
||||
- one
|
||||
priority: P1
|
||||
"""
|
||||
|
||||
|
||||
def _gitea():
|
||||
return gitea_mod.Gitea(load_config())
|
||||
|
||||
|
||||
def test_read_entry_from_git_legacy(app_with_fake_gitea):
|
||||
_app, fake = app_with_fake_gitea
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")] = {
|
||||
"content": LEGACY, "sha": "s1"}
|
||||
st = asyncio.run(metadata.read_entry_from_git(
|
||||
_gitea(), "wiggleverse", "meta", "rfcs/alpha.md"))
|
||||
assert st is not None
|
||||
assert st.entry.slug == "alpha"
|
||||
assert st.entry.extra["priority"] == "P1"
|
||||
assert st.sidecar_sha is None # no sidecar yet
|
||||
assert st.malformed is False
|
||||
|
||||
|
||||
def test_read_entry_from_git_migrated(app_with_fake_gitea):
|
||||
_app, fake = app_with_fake_gitea
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")] = {
|
||||
"content": MIGRATED_MD, "sha": "s1"}
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")] = {
|
||||
"content": MIGRATED_SIDECAR, "sha": "s2"}
|
||||
st = asyncio.run(metadata.read_entry_from_git(
|
||||
_gitea(), "wiggleverse", "meta", "rfcs/alpha.md"))
|
||||
assert st.entry.extra["priority"] == "P1" # from sidecar
|
||||
assert st.entry.body == "Alpha body.\n"
|
||||
assert st.sidecar_sha == "s2"
|
||||
|
||||
|
||||
def test_read_entry_from_git_missing(app_with_fake_gitea):
|
||||
st = asyncio.run(metadata.read_entry_from_git(
|
||||
_gitea(), "wiggleverse", "meta", "rfcs/nope.md"))
|
||||
assert st is None
|
||||
|
||||
|
||||
def test_write_entry_files_lazy_migrates_legacy(app_with_fake_gitea):
|
||||
_app, fake = app_with_fake_gitea
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")] = {
|
||||
"content": LEGACY, "sha": "s1"}
|
||||
st = asyncio.run(metadata.read_entry_from_git(
|
||||
_gitea(), "wiggleverse", "meta", "rfcs/alpha.md"))
|
||||
e2 = metadata.apply_values(st.entry, {"priority": "P0"})
|
||||
ops = metadata.write_entry_files("rfcs/alpha.md", e2, st)
|
||||
paths = {o["path"]: o for o in ops}
|
||||
# sidecar created, .md rewritten body-only
|
||||
assert "rfcs/alpha.meta.yaml" in paths
|
||||
assert paths["rfcs/alpha.meta.yaml"]["operation"] == "create"
|
||||
assert paths["rfcs/alpha.md"]["operation"] == "update"
|
||||
assert "---" not in paths["rfcs/alpha.md"]["content"] # INV-2 clean body
|
||||
assert yaml.safe_load(paths["rfcs/alpha.meta.yaml"]["content"])["priority"] == "P0"
|
||||
|
||||
|
||||
def test_write_entry_files_already_migrated_touches_sidecar_only(app_with_fake_gitea):
|
||||
_app, fake = app_with_fake_gitea
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.md")] = {
|
||||
"content": MIGRATED_MD, "sha": "s1"}
|
||||
fake.files[("wiggleverse", "meta", "main", "rfcs/alpha.meta.yaml")] = {
|
||||
"content": MIGRATED_SIDECAR, "sha": "s2"}
|
||||
st = asyncio.run(metadata.read_entry_from_git(
|
||||
_gitea(), "wiggleverse", "meta", "rfcs/alpha.md"))
|
||||
e2 = metadata.apply_values(st.entry, {"priority": "P0"})
|
||||
ops = metadata.write_entry_files("rfcs/alpha.md", e2, st)
|
||||
paths = {o["path"]: o for o in ops}
|
||||
assert set(paths) == {"rfcs/alpha.meta.yaml"} # .md untouched
|
||||
assert paths["rfcs/alpha.meta.yaml"]["operation"] == "update"
|
||||
assert paths["rfcs/alpha.meta.yaml"]["sha"] == "s2"
|
||||
Reference in New Issue
Block a user