Test coverage for C-31 work-record sidetrack detector (CUST-WP-0060 review)
16 tests: registry loading (env override, missing registry, unparseable registry -> None, no crash), detector behavior against a synthetic registry fixture (independent of the-custodian's live state, so stable across canon changes) -- rogue ids flagged, all registered kinds incl. grandfathered legacy patterns (AWQ-/DEC-/OH-/single-digit-task) pass, template placeholders and .git/history dirs skipped, malformed yaml doesn't crash the scan, dedup of repeated ids within a repo, no-op when no registry is reachable. Full consistency_check + consistency_sweep suite still green (128 tests, no regressions from the C-31 addition). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
59770145fd
commit
4541f1d6fc
1 changed files with 182 additions and 0 deletions
182
tests/test_work_record_check.py
Normal file
182
tests/test_work_record_check.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
"""Unit tests for C-31 (work-record sidetrack detector, CUST-WP-0060-T05).
|
||||
|
||||
Covers:
|
||||
- _load_work_record_patterns — registry discovery + parsing, missing/broken
|
||||
registry handling
|
||||
- _check_unregistered_work_records — rogue ids flagged, registered kinds
|
||||
(incl. grandfathered legacy patterns) pass, template placeholders and
|
||||
skip-dirs ignored, malformed yaml doesn't crash the scan
|
||||
|
||||
No network calls, no DB, no live API — these tests run fully offline and use
|
||||
a synthetic registry fixture rather than the live canon checkout, so they
|
||||
are stable independent of the-custodian's on-disk state.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
|
||||
|
||||
from consistency_check import ( # noqa: E402
|
||||
ConsistencyReport,
|
||||
_check_unregistered_work_records,
|
||||
_load_work_record_patterns,
|
||||
)
|
||||
|
||||
REGISTRY_YAML = textwrap.dedent(
|
||||
"""
|
||||
version: "0.1"
|
||||
status: active
|
||||
kinds:
|
||||
- kind: workplan
|
||||
id_patterns: ['^[A-Z]+-WP-[0-9]{4}$']
|
||||
- kind: task
|
||||
id_patterns: ['^[A-Z]+-WP-[0-9]{4}-T[0-9]{2,3}$']
|
||||
legacy_patterns:
|
||||
- pattern: '^[A-Z]+-WP-[0-9]{4}-T[0-9]$'
|
||||
source: single-digit legacy
|
||||
- kind: intake
|
||||
id_patterns: ['^[A-Z]+-IN-[0-9]{4}$']
|
||||
legacy_patterns:
|
||||
- pattern: '^AWQ-[0-9]{3}$'
|
||||
source: binky AutopilotWorkQueue
|
||||
- kind: decision
|
||||
id_patterns: ['^[A-Z]+-DEC-[0-9]{4}-[0-9]{3}$']
|
||||
legacy_patterns:
|
||||
- pattern: '^DEC-[0-9]{4}-[0-9]{3}$'
|
||||
source: binky DecisionQueue
|
||||
- kind: engagement
|
||||
id_patterns: ['^[A-Z]+-ENG-[0-9]{4}-[0-9]{3}$']
|
||||
legacy_patterns:
|
||||
- pattern: '^OH-[0-9]{4}-[0-9]{3}$'
|
||||
source: binky OfficeHourQueue
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def registry_file(tmp_path, monkeypatch):
|
||||
reg = tmp_path / "work-record-types.yaml"
|
||||
reg.write_text(REGISTRY_YAML, encoding="utf-8")
|
||||
monkeypatch.setenv("WORK_RECORD_REGISTRY", str(reg))
|
||||
return reg
|
||||
|
||||
|
||||
class TestLoadWorkRecordPatterns:
|
||||
def test_loads_patterns_from_env_override(self, registry_file):
|
||||
patterns = _load_work_record_patterns()
|
||||
assert patterns is not None
|
||||
assert any(p.match("AWQ-010") for p in patterns)
|
||||
assert any(p.match("CUST-WP-0060") for p in patterns)
|
||||
|
||||
def test_returns_none_when_no_registry_available(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("WORK_RECORD_REGISTRY", str(tmp_path / "missing.yaml"))
|
||||
monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome")
|
||||
assert _load_work_record_patterns() is None
|
||||
|
||||
def test_returns_none_on_unparseable_registry(self, tmp_path, monkeypatch):
|
||||
bad = tmp_path / "bad.yaml"
|
||||
bad.write_text("kinds: [unterminated", encoding="utf-8")
|
||||
monkeypatch.setenv("WORK_RECORD_REGISTRY", str(bad))
|
||||
assert _load_work_record_patterns() is None
|
||||
|
||||
|
||||
class TestCheckUnregisteredWorkRecords:
|
||||
def _report(self, repo_dir: Path) -> ConsistencyReport:
|
||||
report = ConsistencyReport(repo_slug="test", repo_path=str(repo_dir))
|
||||
_check_unregistered_work_records(repo_dir, report)
|
||||
return report
|
||||
|
||||
def test_flags_id_matching_no_registered_kind(self, tmp_path, registry_file):
|
||||
(tmp_path / "notes.md").write_text(
|
||||
"```yaml\nid: FOO-QX-001\ntitle: rogue species\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert [i.check_id for i in report.issues] == ["C-31"]
|
||||
assert "FOO-QX-001" in report.issues[0].message
|
||||
assert report.issues[0].severity == "WARN"
|
||||
assert report.issues[0].fixable is False
|
||||
|
||||
def test_passes_registered_kind_ids(self, tmp_path, registry_file):
|
||||
(tmp_path / "notes.md").write_text(
|
||||
"```yaml\nid: CUST-WP-0060\n```\n"
|
||||
"```yaml\nid: CUST-WP-0060-T01\n```\n"
|
||||
"```yaml\nid: BINKY-IN-0001\n```\n"
|
||||
"```yaml\nid: BINKY-DEC-2026-001\n```\n"
|
||||
"```yaml\nid: BINKY-ENG-2026-001\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == []
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"legacy_id",
|
||||
["AWQ-010", "DEC-2026-004", "OH-2026-003", "CUST-WP-0060-T1"],
|
||||
)
|
||||
def test_passes_grandfathered_legacy_ids(self, tmp_path, registry_file, legacy_id):
|
||||
(tmp_path / "queue.md").write_text(
|
||||
f"```yaml\nid: {legacy_id}\ntitle: legacy item\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == [], f"{legacy_id} should be grandfathered, got {report.issues}"
|
||||
|
||||
@pytest.mark.parametrize("placeholder", ["BINKY-WP-NNNN", "CUST-WP-0060-TNN"])
|
||||
def test_ignores_template_placeholders(self, tmp_path, registry_file, placeholder):
|
||||
(tmp_path / "AGENTS.md").write_text(
|
||||
f"```yaml\nid: {placeholder}\n```\n", encoding="utf-8"
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == []
|
||||
|
||||
def test_ignores_non_id_like_strings(self, tmp_path, registry_file):
|
||||
(tmp_path / "prose.md").write_text(
|
||||
"```yaml\nid: not-an-id-scheme\n```\n"
|
||||
"```yaml\nid: 12345\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == []
|
||||
|
||||
def test_deduplicates_repeated_ids_in_same_repo(self, tmp_path, registry_file):
|
||||
(tmp_path / "a.md").write_text("```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8")
|
||||
(tmp_path / "b.md").write_text("```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8")
|
||||
report = self._report(tmp_path)
|
||||
assert len(report.issues) == 1
|
||||
|
||||
def test_skips_git_and_history_directories(self, tmp_path, registry_file):
|
||||
skipped = tmp_path / ".git" / "nested"
|
||||
skipped.mkdir(parents=True)
|
||||
(skipped / "notes.md").write_text(
|
||||
"```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8"
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == []
|
||||
|
||||
def test_malformed_fenced_block_does_not_crash_scan(self, tmp_path, registry_file):
|
||||
(tmp_path / "broken.md").write_text(
|
||||
"```yaml\nid: [unterminated\n```\n"
|
||||
"```yaml\nid: FOO-QX-001\n```\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
# the malformed block is silently skipped by the id-line regex
|
||||
# (it never matches `id: <value>` on its own line); the well-formed
|
||||
# rogue id in the same file is still caught.
|
||||
assert [i.check_id for i in report.issues] == ["C-31"]
|
||||
assert "FOO-QX-001" in report.issues[0].message
|
||||
|
||||
def test_no_op_when_registry_unavailable(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("WORK_RECORD_REGISTRY", str(tmp_path / "missing.yaml"))
|
||||
monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome")
|
||||
(tmp_path / "notes.md").write_text(
|
||||
"```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8"
|
||||
)
|
||||
report = self._report(tmp_path)
|
||||
assert report.issues == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue