241 lines
9.3 KiB
Python
241 lines
9.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from govoplan_core.core.modules import DocumentationTopic, ModuleManifest
|
|
from govoplan_docs.backend.manifest import get_manifest as get_docs_manifest
|
|
from govoplan_docs.public_export import (
|
|
ManifestSource,
|
|
build_public_catalog,
|
|
catalog_matches_sources,
|
|
coverage_baseline,
|
|
coverage_regression_issues,
|
|
documentation_coverage_markdown,
|
|
write_public_catalog,
|
|
)
|
|
|
|
|
|
class PublicDocumentationExportTests(unittest.TestCase):
|
|
def test_docs_manifest_has_complete_german_public_coverage(self) -> None:
|
|
module = build_public_catalog(
|
|
(ManifestSource(get_docs_manifest()),)
|
|
)["modules"][0]
|
|
|
|
self.assertEqual(10, module["coverage"]["topic_count"])
|
|
self.assertEqual(0, module["coverage"]["missing_german_topic_count"])
|
|
self.assertEqual([], module["coverage"]["missing_german_topic_ids"])
|
|
self.assertEqual(2, module["coverage"]["structured_localizable_topic_count"])
|
|
self.assertEqual(
|
|
2, module["coverage"]["german_structured_complete_topic_count"]
|
|
)
|
|
self.assertEqual(
|
|
[], module["coverage"]["missing_german_structured_topic_ids"]
|
|
)
|
|
self.assertEqual([], module["coverage"]["missing"])
|
|
|
|
def test_catalog_projects_localized_manifest_topics_and_gaps(self) -> None:
|
|
manifest = ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.2.3",
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="example.workflow",
|
|
title="Use example",
|
|
summary="Perform the example workflow.",
|
|
body="Open and finish it.",
|
|
documentation_types=("user", "admin"),
|
|
metadata={
|
|
"kind": "workflow",
|
|
"steps": ["Review", "Execute"],
|
|
"verification": "Confirm completion.",
|
|
},
|
|
translations={
|
|
"de": {
|
|
"title": "Beispiel verwenden",
|
|
"summary": "Den Beispielablauf durchführen.",
|
|
"body": "Öffnen und abschließen.",
|
|
}
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"steps": ["Prüfen", "Ausführen"],
|
|
"verification": "Den Abschluss bestätigen.",
|
|
}
|
|
},
|
|
),
|
|
),
|
|
)
|
|
|
|
catalog = build_public_catalog(
|
|
(ManifestSource(manifest, "https://example.invalid/repository"),)
|
|
)
|
|
|
|
topic = catalog["modules"][0]["topics"][0]
|
|
self.assertEqual("Beispiel verwenden", topic["localizations"]["de"]["title"])
|
|
self.assertTrue(topic["localizations"]["de"]["complete"])
|
|
self.assertEqual("workflow", topic["kind"])
|
|
self.assertEqual(["Review", "Execute"], topic["content"]["steps"])
|
|
self.assertEqual(
|
|
["Prüfen", "Ausführen"],
|
|
topic["content_localizations"]["de"]["content"]["steps"],
|
|
)
|
|
self.assertTrue(topic["content_localizations"]["de"]["complete"])
|
|
self.assertIn(
|
|
"field/consequence reference",
|
|
catalog["modules"][0]["coverage"]["missing"],
|
|
)
|
|
|
|
def test_digest_check_ignores_generation_timestamp(self) -> None:
|
|
source = ManifestSource(
|
|
ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="example.reference",
|
|
title="Reference",
|
|
summary="Reference summary",
|
|
metadata={"kind": "reference"},
|
|
),
|
|
),
|
|
)
|
|
)
|
|
first = build_public_catalog((source,))
|
|
second = build_public_catalog((source,))
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
output = Path(directory) / "catalog.json"
|
|
write_public_catalog(output, first)
|
|
self.assertTrue(catalog_matches_sources(output, second))
|
|
parsed = json.loads(output.read_text(encoding="utf-8"))
|
|
parsed["source_digest"] = "stale"
|
|
output.write_text(json.dumps(parsed), encoding="utf-8")
|
|
self.assertFalse(catalog_matches_sources(output, second))
|
|
|
|
def test_coverage_report_identifies_generated_source(self) -> None:
|
|
catalog = build_public_catalog(
|
|
(
|
|
ManifestSource(
|
|
ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(),
|
|
)
|
|
),
|
|
)
|
|
)
|
|
report = documentation_coverage_markdown(catalog)
|
|
self.assertIn("Public documentation coverage", report)
|
|
self.assertIn("`example`", report)
|
|
self.assertIn("Edit module manifests", report)
|
|
|
|
def test_catalog_preserves_structured_static_documentation(self) -> None:
|
|
manifest = ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="example.workflow",
|
|
title="Run example",
|
|
summary="Run it safely.",
|
|
metadata={
|
|
"kind": "workflow",
|
|
"prerequisites": ["Approved input"],
|
|
"steps": ["Review", "Execute"],
|
|
"fields": [
|
|
{
|
|
"label": "Mode",
|
|
"user_description": "Selected behavior.",
|
|
}
|
|
],
|
|
},
|
|
),
|
|
),
|
|
)
|
|
|
|
topic = build_public_catalog((ManifestSource(manifest),))["modules"][0]["topics"][0]
|
|
|
|
self.assertEqual(["Review", "Execute"], topic["content"]["steps"])
|
|
self.assertEqual("Mode", topic["content"]["fields"][0]["label"])
|
|
|
|
def test_catalog_preserves_specialized_documentation_kinds(self) -> None:
|
|
manifest = ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="example.runbook",
|
|
title="Recover example",
|
|
summary="Restore the example safely.",
|
|
metadata={"kind": "operator_workflow"},
|
|
),
|
|
),
|
|
)
|
|
|
|
topic = build_public_catalog((ManifestSource(manifest),))["modules"][0]["topics"][0]
|
|
|
|
self.assertEqual("operator-workflow", topic["kind"])
|
|
|
|
def test_coverage_baseline_rejects_text_and_structured_regressions(self) -> None:
|
|
healthy = build_public_catalog(
|
|
(
|
|
ManifestSource(
|
|
ModuleManifest(
|
|
id="example",
|
|
name="Example",
|
|
version="1.0.0",
|
|
documentation=(
|
|
DocumentationTopic(
|
|
id="example.reference",
|
|
title="Reference",
|
|
summary="Reference summary.",
|
|
body="Reference body.",
|
|
translations={
|
|
"de": {
|
|
"title": "Referenz",
|
|
"summary": "Referenzzusammenfassung.",
|
|
"body": "Referenzinhalt.",
|
|
}
|
|
},
|
|
metadata={
|
|
"kind": "reference",
|
|
"limitations": ["Source limitation."],
|
|
},
|
|
structured_translation_version="1",
|
|
structured_translations={
|
|
"de": {
|
|
"limitations": [
|
|
"Einschränkung der Quelle."
|
|
]
|
|
}
|
|
},
|
|
),
|
|
),
|
|
)
|
|
),
|
|
)
|
|
)
|
|
baseline = coverage_baseline(healthy)
|
|
self.assertEqual((), coverage_regression_issues(healthy, baseline))
|
|
|
|
regressed = json.loads(json.dumps(healthy))
|
|
regressed["summary"]["german_complete_topic_count"] = 0
|
|
regressed["summary"]["german_structured_complete_topic_count"] = 0
|
|
issues = coverage_regression_issues(regressed, baseline)
|
|
self.assertTrue(any("german_complete_topic_count" in issue for issue in issues))
|
|
self.assertTrue(
|
|
any("german_structured_complete_topic_count" in issue for issue in issues)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|