Files
semantica/tests/utils/test_skos.py
T
KaifAhmad1andmikemikimike bc75768afe Fix two review findings in SKOS cycle validation
- validate_skos_hierarchy() re-walked every existing hierarchy edge in
  the graph on each write, so one pre-existing cycle anywhere would
  block all unrelated future SKOS writes. It now only traverses
  concepts touched by the edges actually being written, while still
  checking those against existing edges for cross-boundary cycles.
- In /api/ontology/load, `except HTTPException: raise` sat after a
  broader `except Exception` clause that already matched HTTPException,
  so a 422 raised after a successful OntologyIngestor parse was
  silently swallowed and retried via the fallback RDF parser instead of
  reaching the caller. Reordered the except clauses.

Co-authored-by: mikemikimike <13286568797@163.com>
2026-08-01 11:46:13 +05:30

65 lines
2.6 KiB
Python

import unittest
from semantica.utils.skos import is_skos_hierarchy_edge, validate_skos_hierarchy
class TestIsSkosHierarchyEdge(unittest.TestCase):
def test_recognizes_broader_and_narrower(self):
self.assertTrue(is_skos_hierarchy_edge({"source": "A", "target": "B", "type": "skos:broader"}))
self.assertTrue(is_skos_hierarchy_edge({"source": "A", "target": "B", "type": "skos:narrower"}))
def test_ignores_other_edge_types(self):
self.assertFalse(is_skos_hierarchy_edge({"source": "A", "target": "B", "type": "rdfs:subClassOf"}))
def test_ignores_non_mapping(self):
self.assertFalse(is_skos_hierarchy_edge("not-a-dict"))
class TestValidateSkosHierarchy(unittest.TestCase):
def test_accepts_acyclic_chain(self):
validate_skos_hierarchy([
{"source": "A", "target": "B", "type": "skos:broader"},
{"source": "B", "target": "C", "type": "skos:broader"},
])
def test_rejects_self_loop(self):
with self.assertRaisesRegex(ValueError, "cycle"):
validate_skos_hierarchy([{"source": "A", "target": "A", "type": "skos:broader"}])
def test_rejects_direct_two_node_cycle(self):
with self.assertRaisesRegex(ValueError, "cycle"):
validate_skos_hierarchy([
{"source": "A", "target": "B", "type": "skos:broader"},
{"source": "B", "target": "A", "type": "skos:broader"},
])
def test_rejects_cycle_spanning_existing_and_new_edges(self):
existing = [
{"source": "A", "target": "B", "type": "skos:broader"},
{"source": "B", "target": "C", "type": "skos:broader"},
]
with self.assertRaisesRegex(ValueError, "cycle"):
validate_skos_hierarchy([{"source": "C", "target": "A", "type": "skos:broader"}], existing)
def test_preexisting_unrelated_cycle_does_not_block_new_write(self):
"""A cycle already persisted elsewhere in the graph (e.g. legacy data
written before cycle detection existed) must not poison unrelated
writes for concepts it doesn't touch."""
existing = [
{"source": "X", "target": "Y", "type": "skos:broader"},
{"source": "Y", "target": "X", "type": "skos:broader"},
]
validate_skos_hierarchy([{"source": "C", "target": "D", "type": "skos:broader"}], existing)
def test_none_and_blank_endpoints_are_ignored(self):
validate_skos_hierarchy([
{"source": None, "target": "B", "type": "skos:broader"},
{"source": " ", "target": "B", "type": "skos:broader"},
])
if __name__ == "__main__":
unittest.main()