177 lines
6.7 KiB
Python
177 lines
6.7 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import tempfile
|
||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from tools.check_documentation import (
|
||
|
|
DocumentationPolicy,
|
||
|
|
check_documentation,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class DocumentationCheckTests(unittest.TestCase):
|
||
|
|
def repository(self, parent: Path) -> Path:
|
||
|
|
root = parent / "repository"
|
||
|
|
(root / "docs").mkdir(parents=True)
|
||
|
|
(root / "schemas").mkdir()
|
||
|
|
(root / "schemas" / "reference-adapter.schema.json").write_text(
|
||
|
|
json.dumps(
|
||
|
|
{
|
||
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
|
|
"type": "object",
|
||
|
|
"additionalProperties": False,
|
||
|
|
"required": [
|
||
|
|
"schema_version",
|
||
|
|
"project_id",
|
||
|
|
"title",
|
||
|
|
"language",
|
||
|
|
"source_roots",
|
||
|
|
],
|
||
|
|
"properties": {
|
||
|
|
"schema_version": {"const": 1},
|
||
|
|
"project_id": {"type": "string"},
|
||
|
|
"title": {"type": "string"},
|
||
|
|
"language": {"enum": ["python", "javascript", "typescript", "cpp"]},
|
||
|
|
"source_roots": {
|
||
|
|
"type": "array",
|
||
|
|
"minItems": 1,
|
||
|
|
"items": {"type": "string"},
|
||
|
|
},
|
||
|
|
"compilation_database": {"type": "string"},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
return root
|
||
|
|
|
||
|
|
def test_linked_pages_h1_anchors_and_reference_example_pass(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as directory:
|
||
|
|
root = self.repository(Path(directory))
|
||
|
|
(root / "README.md").write_text(
|
||
|
|
"# Project\n\n[Guide](docs/GUIDE.md#reference-adapter-configuration)\n",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(root / "docs" / "GUIDE.md").write_text(
|
||
|
|
"\n".join(
|
||
|
|
(
|
||
|
|
"# Guide",
|
||
|
|
"",
|
||
|
|
"## Reference adapter configuration",
|
||
|
|
"",
|
||
|
|
"`.docforge/reference-adapter.toml`:",
|
||
|
|
"",
|
||
|
|
"```toml",
|
||
|
|
"schema_version = 1",
|
||
|
|
'project_id = "fixture"',
|
||
|
|
'title = "Fixture"',
|
||
|
|
'language = "python"',
|
||
|
|
'source_roots = ["src"]',
|
||
|
|
"```",
|
||
|
|
"",
|
||
|
|
)
|
||
|
|
),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
report = check_documentation(
|
||
|
|
root,
|
||
|
|
policy=DocumentationPolicy(
|
||
|
|
required_pages=(Path("README.md"), Path("docs/GUIDE.md")),
|
||
|
|
pending_inventory=None,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
self.assertTrue(report.ok, [item.render() for item in report.diagnostics])
|
||
|
|
self.assertEqual(2, report.markdown_pages)
|
||
|
|
self.assertEqual(1, report.reference_adapter_examples)
|
||
|
|
|
||
|
|
def test_missing_anchor_orphan_and_duplicate_h1_fail_deterministically(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as directory:
|
||
|
|
root = self.repository(Path(directory))
|
||
|
|
(root / "README.md").write_text(
|
||
|
|
"# Project\n\n[Broken](docs/GUIDE.md#missing)\n",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(root / "docs" / "GUIDE.md").write_text(
|
||
|
|
"# Guide\n\n# Duplicate\n",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(root / "docs" / "ORPHAN.md").write_text("# Orphan\n", encoding="utf-8")
|
||
|
|
|
||
|
|
report = check_documentation(
|
||
|
|
root,
|
||
|
|
policy=DocumentationPolicy(
|
||
|
|
required_pages=(Path("README.md"), Path("docs/GUIDE.md")),
|
||
|
|
pending_inventory=None,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
self.assertFalse(report.ok)
|
||
|
|
codes = [item.code for item in report.diagnostics]
|
||
|
|
self.assertEqual(["DOC016", "DOC017", "DOC023"], sorted(codes))
|
||
|
|
|
||
|
|
def test_pending_inventory_allows_absence_but_rejects_existing_page(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as directory:
|
||
|
|
root = self.repository(Path(directory))
|
||
|
|
(root / "README.md").write_text("# Project\n", encoding="utf-8")
|
||
|
|
pending_path = root / "pending.txt"
|
||
|
|
pending_path.write_text("docs/FUTURE.md\n", encoding="utf-8")
|
||
|
|
policy = DocumentationPolicy(
|
||
|
|
required_pages=(Path("README.md"), Path("docs/FUTURE.md")),
|
||
|
|
pending_inventory=Path("pending.txt"),
|
||
|
|
)
|
||
|
|
|
||
|
|
missing = check_documentation(root, policy=policy)
|
||
|
|
self.assertTrue(missing.ok)
|
||
|
|
self.assertEqual(1, missing.pending_pages)
|
||
|
|
|
||
|
|
(root / "docs" / "FUTURE.md").write_text("# Future\n", encoding="utf-8")
|
||
|
|
stale = check_documentation(root, policy=policy)
|
||
|
|
self.assertEqual(["DOC014", "DOC023"], [item.code for item in stale.diagnostics])
|
||
|
|
|
||
|
|
def test_invalid_reference_example_reports_schema_path(self) -> None:
|
||
|
|
with tempfile.TemporaryDirectory() as directory:
|
||
|
|
root = self.repository(Path(directory))
|
||
|
|
(root / "README.md").write_text(
|
||
|
|
"# Project\n\n[Guide](docs/GUIDE.md)\n",
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
(root / "docs" / "GUIDE.md").write_text(
|
||
|
|
"\n".join(
|
||
|
|
(
|
||
|
|
"# Guide",
|
||
|
|
"",
|
||
|
|
"Reference adapter configuration:",
|
||
|
|
"",
|
||
|
|
"```toml",
|
||
|
|
'language = "python"',
|
||
|
|
'source_roots = ["src"]',
|
||
|
|
"```",
|
||
|
|
"",
|
||
|
|
)
|
||
|
|
),
|
||
|
|
encoding="utf-8",
|
||
|
|
)
|
||
|
|
|
||
|
|
report = check_documentation(
|
||
|
|
root,
|
||
|
|
policy=DocumentationPolicy(
|
||
|
|
required_pages=(Path("README.md"), Path("docs/GUIDE.md")),
|
||
|
|
pending_inventory=None,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
schema_errors = [item for item in report.diagnostics if item.code == "DOC026"]
|
||
|
|
self.assertEqual(3, len(schema_errors))
|
||
|
|
messages = " ".join(item.message for item in schema_errors)
|
||
|
|
self.assertIn("schema_version", messages)
|
||
|
|
self.assertIn("project_id", messages)
|
||
|
|
self.assertIn("title", messages)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|