cc-os/plugins/os-doc-hygiene/tests/test_rules_file_writer.py

304 lines
11 KiB
Python
Raw Normal View History

"""
Tests for `RulesFileWriter` in scripts/calibrate_helpers.py.
Covers tasks 1.1/1.2 of the calibrate-assessment-inventory change: the
single canonical serialization path for `.dochygiene-rules.json`
- canonical ordering: rules grouped delete-once-served -> temporary -> keep,
glob-sorted within each group; nominations after rules; consults before
rejected, each glob-sorted
- idempotent round-trip (byte-identical rewrite of an already-canonical file)
- hand-edit re-canonicalization (out-of-order input is rewritten canonical)
- unknown-field warn-and-round-trip, for both rules and nomination entries
sys.path is patched here (no shared conftest), matching the existing
tests/test_calibrate_helpers.py pattern.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
_SCRIPTS_DIR = Path(__file__).parent.parent / "scripts"
if str(_SCRIPTS_DIR) not in sys.path:
sys.path.insert(0, str(_SCRIPTS_DIR))
from calibrate_helpers import RulesFileWriter # noqa: E402
def _rule(glob, lifetime, **extra):
d = {"glob": glob, "lifetime": lifetime, "confirmed_by": "human", "confirmed_on": "2026-07-15"}
d.update(extra)
return d
class TestCanonicalOrdering:
def test_rules_grouped_by_tier_in_order_and_glob_sorted(self):
data = {
"schema_version": 1,
"rules": [
_rule("z/keep.md", "keep"),
_rule("a/keep.md", "keep"),
_rule("b/temp/*.md", "temporary"),
_rule("a/temp/*.md", "temporary"),
_rule("z/served.md", "delete-once-served"),
_rule("a/served.md", "delete-once-served"),
],
}
writer = RulesFileWriter()
canonical, _warnings = writer.canonicalize(data)
globs = [r["glob"] for r in canonical["rules"]]
assert globs == [
"a/served.md",
"z/served.md",
"a/temp/*.md",
"b/temp/*.md",
"a/keep.md",
"z/keep.md",
]
def test_nominations_after_rules_consults_before_rejected_sorted(self):
data = {
"schema_version": 1,
"rules": [_rule("a.md", "keep")],
"nominations": {
"rejected": [
{
"glob": "z/reject.md",
"lifetime": "temporary",
"why": "why z",
"rejected_by": "judge",
"judged_on": "2026-07-15",
},
{
"glob": "a/reject.md",
"lifetime": "temporary",
"why": "why a",
"rejected_by": "judge",
"judged_on": "2026-07-15",
},
],
"consults": [
{
"glob": "z/consult.md",
"question": "q z",
"evidence": "e",
"cluster_key": "k",
"asked_on": "2026-07-15",
},
{
"glob": "a/consult.md",
"question": "q a",
"evidence": "e",
"cluster_key": "k",
"asked_on": "2026-07-15",
},
],
},
}
writer = RulesFileWriter()
canonical, _warnings = writer.canonicalize(data)
keys = list(canonical.keys())
assert keys.index("rules") < keys.index("nominations")
nomination_keys = list(canonical["nominations"].keys())
assert nomination_keys.index("consults") < nomination_keys.index("rejected")
assert [c["glob"] for c in canonical["nominations"]["consults"]] == [
"a/consult.md",
"z/consult.md",
]
assert [r["glob"] for r in canonical["nominations"]["rejected"]] == [
"a/reject.md",
"z/reject.md",
]
def test_nominations_omitted_when_absent(self):
data = {"schema_version": 1, "rules": [_rule("a.md", "keep")]}
writer = RulesFileWriter()
canonical, _warnings = writer.canonicalize(data)
assert "nominations" not in canonical
def test_nominations_omitted_when_present_but_empty(self):
data = {
"schema_version": 1,
"rules": [_rule("a.md", "keep")],
"nominations": {"consults": [], "rejected": []},
}
writer = RulesFileWriter()
canonical, _warnings = writer.canonicalize(data)
assert "nominations" not in canonical
class TestIdempotentRoundTrip:
def test_writing_an_already_canonical_file_is_byte_identical(self, tmp_path):
data = {
"schema_version": 1,
"rules": [
_rule("a/served.md", "delete-once-served"),
_rule("a/temp/*.md", "temporary"),
_rule("a/keep.md", "keep"),
],
"nominations": {
"consults": [
{
"glob": "a/consult.md",
"question": "q",
"evidence": "e",
"cluster_key": "k",
"asked_on": "2026-07-15",
}
],
"rejected": [
{
"glob": "a/reject.md",
"lifetime": "temporary",
"why": "why",
"rejected_by": "judge",
"judged_on": "2026-07-15",
}
],
},
}
writer = RulesFileWriter()
path = tmp_path / ".dochygiene-rules.json"
writer.write(path, data)
first_bytes = path.read_bytes()
loaded, _warnings = writer.load(path)
writer.write(path, loaded)
second_bytes = path.read_bytes()
assert first_bytes == second_bytes
class TestHandEditRecanonicalization:
def test_hand_edited_out_of_order_file_recanonicalizes_on_next_write(self, tmp_path):
# Hand-authored file: keep rule listed before temporary/served rules,
# rejected before consults -- out of canonical order.
hand_edited = {
"schema_version": 1,
"rules": [
_rule("a/keep.md", "keep"),
_rule("a/served.md", "delete-once-served"),
_rule("a/temp/*.md", "temporary"),
],
"nominations": {
"rejected": [
{
"glob": "a/reject.md",
"lifetime": "temporary",
"why": "why",
"rejected_by": "judge",
"judged_on": "2026-07-15",
}
],
"consults": [
{
"glob": "a/consult.md",
"question": "q",
"evidence": "e",
"cluster_key": "k",
"asked_on": "2026-07-15",
}
],
},
}
path = tmp_path / ".dochygiene-rules.json"
path.write_text(json.dumps(hand_edited, indent=2), encoding="utf-8")
writer = RulesFileWriter()
loaded, _warnings = writer.load(path)
writer.write(path, loaded)
on_disk = json.loads(path.read_text(encoding="utf-8"))
assert [r["glob"] for r in on_disk["rules"]] == [
"a/served.md",
"a/temp/*.md",
"a/keep.md",
]
nomination_keys = list(on_disk["nominations"].keys())
assert nomination_keys.index("consults") < nomination_keys.index("rejected")
class TestUnknownFieldsWarnAndRoundtrip:
def test_unknown_rule_field_warns_and_is_preserved(self):
data = {
"schema_version": 1,
"rules": [_rule("a.md", "keep", mystery_field="surprise")],
}
writer = RulesFileWriter()
canonical, warnings = writer.canonicalize(data)
assert canonical["rules"][0]["mystery_field"] == "surprise"
assert any("mystery_field" in w for w in warnings)
def test_unknown_consult_field_warns_and_is_preserved(self):
data = {
"schema_version": 1,
"rules": [],
"nominations": {
"consults": [
{
"glob": "a/consult.md",
"question": "q",
"evidence": "e",
"cluster_key": "k",
"asked_on": "2026-07-15",
"extra_field": "unexpected",
}
],
"rejected": [],
},
}
writer = RulesFileWriter()
canonical, warnings = writer.canonicalize(data)
assert canonical["nominations"]["consults"][0]["extra_field"] == "unexpected"
assert any("extra_field" in w for w in warnings)
def test_unknown_rejected_field_warns_and_is_preserved(self):
data = {
"schema_version": 1,
"rules": [],
"nominations": {
"consults": [],
"rejected": [
{
"glob": "a/reject.md",
"lifetime": "temporary",
"why": "why",
"rejected_by": "judge",
"judged_on": "2026-07-15",
"surprise_field": "oops",
}
],
},
}
writer = RulesFileWriter()
canonical, warnings = writer.canonicalize(data)
assert canonical["nominations"]["rejected"][0]["surprise_field"] == "oops"
assert any("surprise_field" in w for w in warnings)
class TestLoad:
def test_load_reads_file_and_returns_canonical_data(self, tmp_path):
data = {
"schema_version": 1,
"rules": [_rule("a/keep.md", "keep")],
}
path = tmp_path / ".dochygiene-rules.json"
path.write_text(json.dumps(data), encoding="utf-8")
writer = RulesFileWriter()
loaded, warnings = writer.load(path)
assert loaded["rules"][0]["glob"] == "a/keep.md"
assert warnings == []
def test_load_missing_file_returns_empty_envelope(self, tmp_path):
path = tmp_path / "does-not-exist.json"
writer = RulesFileWriter()
loaded, warnings = writer.load(path)
assert loaded["rules"] == []
assert "nominations" not in loaded
assert warnings == []