1. `date.fromisoformat` is not a YYYY-MM-DD validator. On Python >= 3.11 it also
accepts ISO basic format ("20270101") and week dates ("2027-W01-1"), so a
malformed-looking value passed the blocking check — and which forms parse
depends on the interpreter, meaning the same corpus could validate differently
on a dev machine and on the runner (pr-checks.yml pins only python-version
'3.x'). Knock-on: the catalog's Review-due section sorts on the raw STRING, so
an accepted "20270101" sorted AFTER "2027-01-15" ('-' < '0'), contradicting the
section's own "sorted soonest-first" text. Gate on ^\d{4}-\d{2}-\d{2}$ first,
which fixes both — a fixed-width zero-padded form makes string sort == date sort.
2. A present-but-empty `stale-after:` was collapsed to None by `or None` in the
parser and then skipped by a truthiness guard in the validator, so it passed as
"absent" — a field that silently never fires, which is the exact failure mode
the blocking check exists to prevent. Keep "" distinct from None and test with
`is not None`.
3. `test_catalog_is_date_independent` was partly vacuous: with no date in either
render, both sides were trivially equal after the .replace(). It did still catch
an injected clock-derived marker, but it passed with the feature deleted. Assert
the dates are present.
4. The malformed-date check ran only over the active set, exempting archive
records. Staleness is moot there, but a typo is still a typo — check both wings.
Adds regression tests for each, plus a Review-due row for a topic-file record
(pinning the `../decisions.md` vs bare-filename link forms).
62 lines
1.9 KiB
Python
62 lines
1.9 KiB
Python
from pathlib import Path
|
|
|
|
import scripts.decisions_lib as dl
|
|
|
|
FIX = Path(__file__).parent / "fixtures" / "sample_decisions.md"
|
|
|
|
|
|
def test_parses_migrated_record():
|
|
recs = dl.parse_file(FIX)
|
|
migrated = [r for r in recs if r.key == "ci.runner-placement"]
|
|
assert len(migrated) == 1
|
|
r = migrated[0]
|
|
assert r.status == "active"
|
|
assert r.since == "2026-07-17"
|
|
assert r.supersedes == "none"
|
|
assert r.superseded_by == "none"
|
|
assert r.rule == "Every CI services container gets an explicit cap."
|
|
assert r.signals is not None
|
|
assert "issues: #390 #406" in r.signals
|
|
assert r.mechanics is not None
|
|
assert r.mechanics.startswith("docs/ci-cd.md")
|
|
|
|
|
|
def test_legacy_record_is_unmigrated():
|
|
recs = dl.parse_file(FIX)
|
|
legacy = [r for r in recs if r.heading.endswith("(#231)")]
|
|
assert len(legacy) == 1
|
|
assert legacy[0].key is None
|
|
assert legacy[0].status == "legacy-unmigrated"
|
|
|
|
|
|
def test_index_section_parses_as_heading():
|
|
recs = dl.parse_file(FIX)
|
|
assert any(r.heading == "Index" for r in recs)
|
|
|
|
|
|
def test_parses_optional_stale_after_and_sources():
|
|
recs = dl.parse_file(FIX)
|
|
r = next(r for r in recs if r.key == "ci.peak-anon-measurement")
|
|
assert r.stale_after == "2027-01-15"
|
|
assert r.sources is not None
|
|
assert "gitea run 4471" in r.sources
|
|
|
|
|
|
def test_optional_fields_default_to_none_when_absent():
|
|
recs = dl.parse_file(FIX)
|
|
r = next(r for r in recs if r.key == "ci.runner-placement")
|
|
assert r.stale_after is None
|
|
assert r.sources is None
|
|
|
|
|
|
def test_empty_stale_after_parses_as_empty_string_not_none():
|
|
"""Absent vs present-but-empty must stay distinguishable for the validator."""
|
|
text = (
|
|
"## H\n"
|
|
"`key: a.b` · `status: active` · `since: 2026-01-01` · `stale-after:` "
|
|
"· `supersedes: none` · `superseded-by: none`\n"
|
|
"**Rule:** r\n"
|
|
)
|
|
r = dl.parse_text(text, Path("fake.md"))[0]
|
|
assert r.stale_after == ""
|