198 lines
6.8 KiB
Python
198 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from tools.check_documentation import (
|
|
COMMAND_REFERENCE_NOTICE,
|
|
MAX_DIAGNOSTICS,
|
|
DocumentationPolicy,
|
|
check_documentation,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCHEMA = ROOT / "schemas/reference-adapter.schema.json"
|
|
|
|
|
|
class DocumentationCheckTests(unittest.TestCase):
|
|
def repository(self) -> Path:
|
|
root = Path(self.enterContext(tempfile.TemporaryDirectory()))
|
|
(root / "docs").mkdir()
|
|
(root / "schemas").mkdir()
|
|
shutil.copyfile(SCHEMA, root / "schemas/reference-adapter.schema.json")
|
|
(root / "pending.txt").write_text("", encoding="utf-8")
|
|
return root
|
|
|
|
@staticmethod
|
|
def policy(*required: str) -> DocumentationPolicy:
|
|
return DocumentationPolicy(
|
|
required_pages=tuple(Path(item) for item in required),
|
|
pending_inventory=Path("pending.txt"),
|
|
)
|
|
|
|
@staticmethod
|
|
def command_reference() -> str:
|
|
return f"# DocForge command reference\n\n{COMMAND_REFERENCE_NOTICE}\n\n## CLI commands\n"
|
|
|
|
def test_valid_graph_h1_anchors_and_reference_adapter_toml_pass(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text(
|
|
"# Documentation\n\n"
|
|
"[Guide](docs/GUIDE.md#adapter-setup) and "
|
|
"[commands](docs/COMMAND_REFERENCE.md). [Top](#documentation)\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/GUIDE.md").write_text(
|
|
"# Guide\n\n"
|
|
"## Adapter *setup*\n\n"
|
|
"Reference adapter configuration `.docforge/reference-adapter.toml`:\n\n"
|
|
"```toml\n"
|
|
"schema_version = 1\n"
|
|
'project_id = "example-project"\n'
|
|
'title = "Example"\n'
|
|
'language = "python"\n'
|
|
'source_roots = ["src"]\n'
|
|
"```\n\n"
|
|
"[Back](../README.md#documentation)\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/COMMAND_REFERENCE.md").write_text(
|
|
self.command_reference(),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
report = check_documentation(
|
|
root,
|
|
policy=self.policy(
|
|
"README.md",
|
|
"docs/GUIDE.md",
|
|
"docs/COMMAND_REFERENCE.md",
|
|
),
|
|
)
|
|
|
|
self.assertTrue(report.ok, [item.render() for item in report.diagnostics])
|
|
self.assertEqual(3, report.markdown_pages)
|
|
self.assertEqual(1, report.reference_adapter_examples)
|
|
|
|
def test_links_anchors_h1_and_generated_notice_fail_closed(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text(
|
|
"# Documentation\n\n"
|
|
"[bad anchor](docs/GUIDE.md#missing) "
|
|
"[missing file](docs/ABSENT.md) "
|
|
"[commands](docs/COMMAND_REFERENCE.md).\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/GUIDE.md").write_text(
|
|
"# Guide\n\n# Duplicate\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/COMMAND_REFERENCE.md").write_text(
|
|
"# DocForge command reference\n\n## CLI commands\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
report = check_documentation(
|
|
root,
|
|
policy=self.policy(
|
|
"README.md",
|
|
"docs/GUIDE.md",
|
|
"docs/COMMAND_REFERENCE.md",
|
|
),
|
|
)
|
|
|
|
self.assertFalse(report.ok)
|
|
self.assertEqual(
|
|
{"DOC016", "DOC017", "DOC020", "DOC028"},
|
|
{item.code for item in report.diagnostics},
|
|
)
|
|
|
|
def test_only_prior_milestone_records_are_reachability_exempt(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text("# Documentation\n", encoding="utf-8")
|
|
(root / "docs/MILESTONE_2_BASELINE.md").write_text(
|
|
"# Historical evidence\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/ORPHAN.md").write_text("# Orphan\n", encoding="utf-8")
|
|
|
|
report = check_documentation(
|
|
root,
|
|
policy=self.policy("README.md"),
|
|
)
|
|
|
|
orphans = [item.path for item in report.diagnostics if item.code == "DOC023"]
|
|
self.assertEqual(["docs/ORPHAN.md"], orphans)
|
|
|
|
def test_pending_page_becomes_a_hard_failure_as_soon_as_it_exists(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text("# Documentation\n", encoding="utf-8")
|
|
(root / "pending.txt").write_text("docs/FUTURE.md\n", encoding="utf-8")
|
|
policy = self.policy("README.md", "docs/FUTURE.md")
|
|
|
|
pending = check_documentation(root, policy=policy)
|
|
|
|
self.assertTrue(pending.ok)
|
|
self.assertEqual(1, pending.pending_pages)
|
|
|
|
(root / "docs/FUTURE.md").write_text("# Future\n", encoding="utf-8")
|
|
stale = check_documentation(root, policy=policy)
|
|
|
|
self.assertIn("DOC014", {item.code for item in stale.diagnostics})
|
|
self.assertIn("DOC023", {item.code for item in stale.diagnostics})
|
|
|
|
def test_unknown_pending_page_and_invalid_toml_are_reported(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text(
|
|
"# Documentation\n\n[Adapter](docs/ADAPTER.md)\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "docs/ADAPTER.md").write_text(
|
|
"# Adapter\n\n"
|
|
"```toml reference-adapter\n"
|
|
"schema_version = 1\n"
|
|
'project_id = "cpp-example"\n'
|
|
'title = "C++"\n'
|
|
'language = "cpp"\n'
|
|
'source_roots = ["src"]\n'
|
|
"```\n",
|
|
encoding="utf-8",
|
|
)
|
|
(root / "pending.txt").write_text("docs/UNKNOWN.md\n", encoding="utf-8")
|
|
|
|
report = check_documentation(
|
|
root,
|
|
policy=self.policy("README.md", "docs/ADAPTER.md"),
|
|
)
|
|
|
|
self.assertIn("DOC012", {item.code for item in report.diagnostics})
|
|
schema_failures = [item for item in report.diagnostics if item.code == "DOC026"]
|
|
self.assertEqual(1, len(schema_failures))
|
|
self.assertIn("compilation_database", schema_failures[0].message)
|
|
|
|
def test_diagnostics_are_deterministically_bounded(self) -> None:
|
|
root = self.repository()
|
|
(root / "README.md").write_text("# Documentation\n", encoding="utf-8")
|
|
for index in range(MAX_DIAGNOSTICS + 7):
|
|
(root / "docs" / f"ORPHAN_{index:03d}.md").write_text(
|
|
f"# Orphan {index}\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
report = check_documentation(
|
|
root,
|
|
policy=self.policy("README.md"),
|
|
)
|
|
|
|
self.assertEqual(MAX_DIAGNOSTICS, len(report.diagnostics))
|
|
self.assertEqual(7, report.omitted_diagnostics)
|
|
self.assertEqual(
|
|
sorted(report.diagnostics),
|
|
list(report.diagnostics),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|