content-explorer/tests/test_contract.py

148 lines
5.9 KiB
Python
Raw Normal View History

"""Contract checks against the shipped validator, not a copy of it."""
from __future__ import annotations
import copy
import json
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
import sys
sys.path.insert(0, str(ROOT))
from content_explorer.contract import ( # noqa: E402
resolve_claims,
validate_dataspace,
validate_documents,
)
from content_explorer.render import render_explorer # noqa: E402
EXAMPLES = ROOT / "examples" / "dataspaces"
RENDERER = ROOT / "content_explorer" / "render.py"
def load_pair(name: str) -> tuple[dict, dict]:
directory = EXAMPLES / name
snapshot = json.loads((directory / "snapshot.json").read_text(encoding="utf-8"))
manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8"))
return snapshot, manifest
class PublishedFixtures(unittest.TestCase):
def test_each_example_passes_the_file_entry_point(self) -> None:
names = sorted(path.name for path in EXAMPLES.iterdir() if (path / "snapshot.json").is_file())
self.assertGreaterEqual(len(names), 2)
for name in names:
errors = validate_dataspace(EXAMPLES / name, require_size=True)
self.assertEqual(errors, [], name)
def test_renderer_switches_with_the_manifest(self) -> None:
systems, systems_manifest = load_pair("systems-thinking")
software, software_manifest = load_pair("software-architecture")
systems_view = render_explorer(systems, systems_manifest)
software_view = render_explorer(software, software_manifest)
switched = render_explorer(systems, software_manifest)
self.assertIn(systems_manifest["home"]["heading"], systems_view)
self.assertIn(software_manifest["home"]["heading"], software_view)
self.assertIn(software_manifest["home"]["heading"], switched)
self.assertNotIn(systems_manifest["home"]["heading"], switched)
self.assertNotIn(software_manifest["home"]["heading"], systems_view)
def test_renderer_source_has_no_domain_vocabulary(self) -> None:
source = RENDERER.read_text(encoding="utf-8").lower()
for word in (
"cancer",
"oncolog",
"feedback",
"coupling",
"ashby",
"meadows",
"alexander",
"systems-thinking",
"software-architecture",
"causal",
"cohesion",
):
self.assertNotIn(word, source, word)
class NegativeGraph(unittest.TestCase):
def setUp(self) -> None:
self.snapshot, self.manifest = load_pair("systems-thinking")
def test_duplicate_entity_id(self) -> None:
snapshot = copy.deepcopy(self.snapshot)
snapshot["entities"].append(copy.deepcopy(snapshot["entities"][0]))
errors = validate_documents(snapshot, self.manifest)
self.assertTrue(any("duplicate entity id" in error for error in errors))
def test_dangling_relation(self) -> None:
snapshot = copy.deepcopy(self.snapshot)
snapshot["relations"][0]["to"] = "urn:ce:systems-thinking:concept:missing"
errors = validate_documents(snapshot, self.manifest)
self.assertTrue(any("dangling to" in error for error in errors))
def test_missing_source(self) -> None:
snapshot = copy.deepcopy(self.snapshot)
snapshot["entities"][0]["sourceRefs"] = ["src:missing"]
errors = validate_documents(snapshot, self.manifest)
self.assertTrue(any("missing source src:missing" in error for error in errors))
def test_taxonomic_cycle_fails_and_other_cycles_do_not(self) -> None:
snapshot = copy.deepcopy(self.snapshot)
left = snapshot["entities"][0]["id"]
right = snapshot["entities"][1]["id"]
source = snapshot["sources"][0]["id"]
snapshot["relations"].extend([
{
"id": "cycle-a",
"predicate": "related-to",
"from": left,
"to": right,
"sourceRefs": [source],
},
{
"id": "cycle-b",
"predicate": "related-to",
"from": right,
"to": left,
"sourceRefs": [source],
},
])
manifest = copy.deepcopy(self.manifest)
manifest["relationLabels"]["related-to"] = {"forward": "Related to", "inverse": "Related to"}
manifest["acyclicPredicates"] = []
open_cycle = validate_documents(snapshot, manifest)
self.assertFalse(any("taxonomic cycle" in error for error in open_cycle))
manifest["acyclicPredicates"] = ["related-to"]
closed = validate_documents(snapshot, manifest)
self.assertTrue(any("taxonomic cycle" in error for error in closed))
class FactPolicy(unittest.TestCase):
def test_missing_unknown_and_conflict(self) -> None:
claims = [
{"key": "measure", "status": "unknown", "value": None},
{"key": "name", "status": "asserted", "value": "one"},
{"key": "name", "status": "asserted", "value": "two"},
]
self.assertEqual(resolve_claims(claims, "absent", "show-all")["state"], "missing")
self.assertEqual(resolve_claims(claims, "measure", "show-all")["state"], "unknown")
shown = resolve_claims(claims, "name", "show-all")
self.assertEqual(shown["state"], "contradicted")
self.assertEqual(shown["values"], ["one", "two"])
withheld = resolve_claims(claims, "name", "withhold")
self.assertEqual(withheld["values"], [])
preferred_claims = [
{"key": "name", "status": "asserted", "value": "one", "preferred": True},
{"key": "name", "status": "asserted", "value": "two"},
]
chosen = resolve_claims(preferred_claims, "name", "preferred")
self.assertEqual(chosen, {"state": "present", "values": ["one"]})
if __name__ == "__main__":
unittest.main()