mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
- 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>
65 lines
2.6 KiB
Python
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()
|