Implement whole-Knowledge-Base validation per MASTER_SPEC §15.10, §26.4: compiler/common/kb_validation.py (importable by the loader) plus a CLI at scripts/validate_knowledge_base.py. validate_knowledge_base(entries) collects every problem (never stops at the first); each Message carries a code, the offending id, and a description: - duplicate canonical id -> SC0005 - invalid category -> SC0008 (drawn from the shared 19-category constant) - circular expansion (incl. self-expansion) -> SC0003, via DFS cycle detection - duplicate alias, alias colliding with a canonical id (no chains), missing expansion target, and per-entry schema nonconformance (empty tags, unknown fields, ...) -> umbrella SC0004 Findings are deterministically ordered. The CLI loads every *.json (array of entries) from a directory, reports invalid JSON / non-array files as SC0004, writes JSON findings to stdout, and exits non-zero when invalid. Tests: a passing fixture plus a fixture per failure mode, multi-problem reporting, determinism, and the CLI on valid/invalid/broken-JSON inputs. Co-authored-by: kunail0804 <kunail0804@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
"""Tests for the Knowledge Base validation CLI (issue #9)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
SCRIPT = REPO_ROOT / "scripts" / "validate_knowledge_base.py"
|
|
|
|
|
|
def _load_cli():
|
|
spec = importlib.util.spec_from_file_location("kb_cli", SCRIPT)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module) # type: ignore[union-attr]
|
|
return module
|
|
|
|
|
|
def test_cli_passes_on_valid_kb(tmp_path, capsys) -> None:
|
|
(tmp_path / "hair.json").write_text(
|
|
json.dumps([{"id": "long_hair", "tags": ["long hair"], "category": "hair"}]),
|
|
encoding="utf-8",
|
|
)
|
|
exit_code = _load_cli().main([str(tmp_path)])
|
|
report = json.loads(capsys.readouterr().out)
|
|
assert exit_code == 0
|
|
assert report["valid"] is True
|
|
assert report["entry_count"] == 1
|
|
|
|
|
|
def test_cli_fails_on_invalid_kb(tmp_path, capsys) -> None:
|
|
(tmp_path / "bad.json").write_text(
|
|
json.dumps([{"id": "x", "tags": ["x"], "category": "nope"}]),
|
|
encoding="utf-8",
|
|
)
|
|
exit_code = _load_cli().main([str(tmp_path)])
|
|
report = json.loads(capsys.readouterr().out)
|
|
assert exit_code == 1
|
|
assert report["valid"] is False
|
|
assert any(f["code"] == "SC0008" for f in report["findings"])
|
|
|
|
|
|
def test_cli_reports_invalid_json(tmp_path, capsys) -> None:
|
|
(tmp_path / "broken.json").write_text("{not json", encoding="utf-8")
|
|
exit_code = _load_cli().main([str(tmp_path)])
|
|
report = json.loads(capsys.readouterr().out)
|
|
assert exit_code == 1
|
|
assert any(f["code"] == "SC0004" for f in report["findings"])
|