124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
|
|
def _load_module():
|
|
script_path = Path(__file__).resolve().parents[1] / "scripts" / "validate_docs_sync.py"
|
|
spec = importlib.util.spec_from_file_location("validate_docs_sync", script_path)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_collect_command_endpoints_parses_markdown_table_rows() -> None:
|
|
module = _load_module()
|
|
text = "\n".join(
|
|
[
|
|
"| Endpoint | Description |",
|
|
"|----------|-------------|",
|
|
"| `GET /api/status` | status |",
|
|
"| `POST /api/run` | run |",
|
|
"| not-a-row | ignored |",
|
|
]
|
|
)
|
|
endpoints = module.collect_command_endpoints(text)
|
|
assert endpoints == ["GET /api/status", "POST /api/run"]
|
|
|
|
|
|
def test_validate_links_resolve_detects_absolute_and_broken_links(tmp_path) -> None:
|
|
module = _load_module()
|
|
doc = tmp_path / "doc.md"
|
|
existing = tmp_path / "ok.md"
|
|
existing.write_text("# ok\n", encoding="utf-8")
|
|
doc.write_text(
|
|
"\n".join(
|
|
[
|
|
"[ok](./ok.md)",
|
|
"[abs](/tmp/nowhere.md)",
|
|
"[broken](./missing.md)",
|
|
]
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
errors: list[str] = []
|
|
module.validate_links_resolve(doc, doc.read_text(encoding="utf-8"), errors)
|
|
|
|
assert any("absolute link is forbidden" in err for err in errors)
|
|
assert any("broken link" in err for err in errors)
|
|
|
|
|
|
def test_validate_summary_docs_reference_core_docs(monkeypatch) -> None:
|
|
module = _load_module()
|
|
errors: list[str] = []
|
|
fake_docs = {
|
|
str(module.REQUIRED_FILES["README.md"]): (
|
|
"docs/workflow.md docs/commands.md docs/testing.md"
|
|
),
|
|
str(module.REQUIRED_FILES["CLAUDE.md"]): "docs/workflow.md docs/commands.md",
|
|
}
|
|
|
|
def fake_read(path: Path) -> str:
|
|
return fake_docs[str(path)]
|
|
|
|
monkeypatch.setattr(module, "_read", fake_read)
|
|
module.validate_summary_docs_reference_core_docs(errors)
|
|
assert errors == []
|
|
|
|
|
|
def test_validate_summary_docs_reference_core_docs_reports_missing_links(
|
|
monkeypatch,
|
|
) -> None:
|
|
module = _load_module()
|
|
errors: list[str] = []
|
|
fake_docs = {
|
|
str(module.REQUIRED_FILES["README.md"]): "docs/workflow.md",
|
|
str(module.REQUIRED_FILES["CLAUDE.md"]): "docs/workflow.md",
|
|
}
|
|
|
|
def fake_read(path: Path) -> str:
|
|
return fake_docs[str(path)]
|
|
|
|
monkeypatch.setattr(module, "_read", fake_read)
|
|
module.validate_summary_docs_reference_core_docs(errors)
|
|
|
|
assert any("README.md" in err and "docs/commands.md" in err for err in errors)
|
|
assert any("README.md" in err and "docs/testing.md" in err for err in errors)
|
|
assert any("CLAUDE.md" in err and "docs/commands.md" in err for err in errors)
|
|
|
|
|
|
def test_validate_commands_endpoint_duplicates_reports_duplicates(monkeypatch) -> None:
|
|
module = _load_module()
|
|
errors: list[str] = []
|
|
text = "\n".join(
|
|
[
|
|
"| `GET /api/status` | status |",
|
|
"| `GET /api/status` | duplicate |",
|
|
]
|
|
)
|
|
|
|
def fake_read(path: Path) -> str:
|
|
assert path == module.REQUIRED_FILES["commands"]
|
|
return text
|
|
|
|
monkeypatch.setattr(module, "_read", fake_read)
|
|
module.validate_commands_endpoint_duplicates(errors)
|
|
assert errors
|
|
assert "duplicated API endpoint row -> GET /api/status" in errors[0]
|
|
|
|
|
|
def test_validate_testing_doc_has_dynamic_count_guidance(monkeypatch) -> None:
|
|
module = _load_module()
|
|
errors: list[str] = []
|
|
|
|
def fake_read(path: Path) -> str:
|
|
assert path == module.REQUIRED_FILES["testing"]
|
|
return "Use pytest --collect-only -q for dynamic counts."
|
|
|
|
monkeypatch.setattr(module, "_read", fake_read)
|
|
module.validate_testing_doc_has_dynamic_count_guidance(errors)
|
|
assert errors == []
|