← Files Marketing CouncilARCHIVED FILE
tests/test_documentation_lifecycle.py
7.21 KB · Oct 2, 2026 · 00:31 UTC
import importlib.util
import json
import re
import subprocess
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REGISTRY = ROOT / "docs" / "document-registry.json"
VALIDATOR = ROOT / "scripts" / "validate_documentation.py"
ALLOWED = {
"ACTIVE",
"ONGOING",
"HISTORICAL_COMPLETED",
"HISTORICAL_SUPERSEDED",
"REFERENCE",
"DEPRECATED",
}
class DocumentationLifecycleTests(unittest.TestCase):
def validator_module(self):
spec = importlib.util.spec_from_file_location("documentation_validator_under_test", VALIDATOR)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def registry(self):
self.assertTrue(REGISTRY.is_file(), "docs/document-registry.json must exist")
return json.loads(REGISTRY.read_text(encoding="utf-8"))
def test_registry_covers_all_markdown_under_docs(self):
payload = self.registry()
entries = {item["path"]: item for item in payload["documents"]}
actual = {
path.relative_to(ROOT).as_posix()
for path in (ROOT / "docs").rglob("*.md")
}
self.assertEqual(set(entries), actual)
self.assertEqual(len(entries), len(payload["documents"]))
def test_registry_status_and_source_of_truth_invariants(self):
payload = self.registry()
self.assertEqual(payload["version"], 1)
for item in payload["documents"]:
self.assertIn(item["status"], ALLOWED, item["path"])
self.assertIsInstance(item["source_of_truth"], bool, item["path"])
self.assertRegex(item["last_reviewed"], r"^\d{4}-\d{2}-\d{2}$")
path = ROOT / item["path"]
self.assertTrue(path.is_file(), item["path"])
text = path.read_text(encoding="utf-8")
self.assertIn(f"doc_status: {item['status']}", text, item["path"])
self.assertIn(
f"source_of_truth: {str(item['source_of_truth']).lower()}",
text,
item["path"],
)
if item["status"].startswith("HISTORICAL_"):
self.assertFalse(item["source_of_truth"], item["path"])
self.assertIn("Current source of truth", text, item["path"])
def test_superseded_documents_name_resolving_replacements(self):
payload = self.registry()
for item in payload["documents"]:
if item["status"] != "HISTORICAL_SUPERSEDED":
continue
replacements = item.get("superseded_by", [])
self.assertTrue(replacements, item["path"])
text = (ROOT / item["path"]).read_text(encoding="utf-8")
self.assertIn("Do not execute this", text, item["path"])
for replacement in replacements:
self.assertTrue((ROOT / replacement).is_file(), replacement)
def test_ongoing_documents_expose_operational_state(self):
payload = self.registry()
required = [
"Status",
"Current state",
"Completed work",
"Remaining work",
"Blockers",
"Next verification step",
"Source-of-truth references",
"Last reviewed date",
]
for item in payload["documents"]:
if item["status"] != "ONGOING":
continue
text = (ROOT / item["path"]).read_text(encoding="utf-8")
for heading in required:
self.assertIn(heading, text, f"{item['path']}: missing {heading}")
def test_documentation_index_is_canonical_map(self):
text = (ROOT / "docs" / "README.md").read_text(encoding="utf-8")
for heading in [
"Start here",
"Current product documentation",
"Ongoing work",
"Architecture and contracts",
"Release and distribution",
"Historical specs",
"Historical implementation plans",
]:
self.assertIn(heading, text)
for status in ["ACTIVE", "ONGOING", "HISTORICAL_COMPLETED", "HISTORICAL_SUPERSEDED"]:
self.assertIn(status, text)
def test_internal_relative_markdown_links_resolve(self):
payload = self.registry()
link_re = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
for item in payload["documents"]:
path = ROOT / item["path"]
text = path.read_text(encoding="utf-8")
for raw in link_re.findall(text):
target = raw.split("#", 1)[0]
if not target or "://" in target or target.startswith("#"):
continue
resolved = (path.parent / target).resolve()
self.assertTrue(
resolved.is_file() or resolved.is_dir(),
f"{item['path']}: broken relative link {raw}",
)
def test_submission_readme_is_ongoing_and_operational(self):
path = ROOT / "submission" / "README.md"
text = path.read_text(encoding="utf-8")
self.assertIn("doc_status: ONGOING", text)
self.assertIn("source_of_truth: false", text)
for field in [
"Status",
"Current state",
"Completed work",
"Remaining work",
"Blockers",
"Next verification step",
"Source-of-truth references",
"Last reviewed date",
]:
self.assertIn(field, text)
def test_plugin_ci_has_named_documentation_lifecycle_gate(self):
workflow = (ROOT / ".github" / "workflows" / "plugin-ci.yml").read_text(encoding="utf-8")
self.assertIn("Validate documentation lifecycle", workflow)
self.assertIn("python scripts/validate_documentation.py --json", workflow)
def test_review_dates_are_iso_formatted_and_match_document_metadata_without_fixed_day(self):
module = self.validator_module()
self.assertTrue(module.valid_review_date("2027-01-15"))
self.assertFalse(module.valid_review_date("2027/01/15"))
metadata = module.parse_document_metadata(
"<!--\ndoc_status: ACTIVE\nlast_reviewed: 2027-01-15\nsource_of_truth: true\n-->"
)
self.assertEqual(metadata["last_reviewed"], "2027-01-15")
self.assertEqual(
module.review_date_errors(
{"path": "docs/example.md", "last_reviewed": "2027-01-15"},
metadata,
),
[],
)
errors = module.review_date_errors(
{"path": "docs/example.md", "last_reviewed": "2027-01-16"},
metadata,
)
self.assertTrue(any("does not match document metadata" in error for error in errors))
def test_repo_validator_accepts_documentation_lifecycle(self):
self.assertTrue(VALIDATOR.is_file(), "scripts/validate_documentation.py must exist")
result = subprocess.run(
[sys.executable, str(VALIDATOR), "--json"],
cwd=ROOT,
text=True,
capture_output=True,
)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
payload = json.loads(result.stdout)
self.assertTrue(payload["ok"], payload)
self.assertGreaterEqual(payload["document_count"], 10)
if __name__ == "__main__":
unittest.main()
SHA-256: 74da4c8deb62effe0d38165d6af1e00d8d6683f141f3513d366ed1e3020fc46a