diff --git a/ontology/uco/core/core.ttl b/ontology/uco/core/core.ttl index f0ecbace..9d59127b 100644 --- a/ontology/uco/core/core.ttl +++ b/ontology/uco/core/core.ttl @@ -484,6 +484,29 @@ core:hasFacet rdfs:range core:Facet ; . +core:hasFacet-shape + a sh:PropertyShape ; + sh:path core:hasFacet ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:message "hasFacet must not be used to link two objects to one Facet."@en ; + sh:select """ + PREFIX core: + PREFIX owl: + SELECT $this ?value + WHERE { + ?value core:hasFacet $this . + ?nOtherValue core:hasFacet $this . + FILTER ( ?value != ?nOtherValue ) + FILTER NOT EXISTS { + ?value owl:sameAs|^owl:sameAs ?nOtherValue . + } + } + """ ; + ] ; + sh:targetObjectsOf core:hasFacet ; + . + core:id a owl:DatatypeProperty ; rdfs:label "id"@en ; diff --git a/tests/examples/Makefile b/tests/examples/Makefile index 2af81c99..87d80235 100644 --- a/tests/examples/Makefile +++ b/tests/examples/Makefile @@ -23,6 +23,8 @@ all: \ action_result_PASS_validation.ttl \ co_PASS_validation.ttl \ co_XFAIL_validation.ttl \ + has_facet_inverse_functional_PASS_validation.ttl \ + has_facet_inverse_functional_XFAIL_validation.ttl \ hash_PASS_validation.ttl \ hash_XFAIL_validation.ttl \ location_PASS_validation.ttl \ @@ -84,6 +86,8 @@ check: \ action_result_PASS_validation.ttl \ co_PASS_validation.ttl \ co_XFAIL_validation.ttl \ + has_facet_inverse_functional_PASS_validation.ttl \ + has_facet_inverse_functional_XFAIL_validation.ttl \ hash_PASS_validation.ttl \ hash_XFAIL_validation.ttl \ location_PASS_validation.ttl \ diff --git a/tests/examples/has_facet_inverse_functional_PASS.json b/tests/examples/has_facet_inverse_functional_PASS.json new file mode 100644 index 00000000..573740fc --- /dev/null +++ b/tests/examples/has_facet_inverse_functional_PASS.json @@ -0,0 +1,30 @@ +{ + "@context": { + "kb": "http://example.org/kb/", + "core": "https://ontology.unifiedcyberontology.org/uco/core/", + "owl": "http://www.w3.org/2002/07/owl#" + }, + "@graph": [ + { + "@id": "kb:facet-1", + "@type": "core:Facet" + }, + { + "@id": "kb:object-1", + "@type": "core:UcoObject", + "core:hasFacet": { + "@id": "kb:facet-1" + }, + "owl:sameAs": { + "@id": "kb:object-2" + } + }, + { + "@id": "kb:object-2", + "@type": "core:UcoObject", + "core:hasFacet": { + "@id": "kb:facet-1" + } + } + ] +} diff --git a/tests/examples/has_facet_inverse_functional_XFAIL.json b/tests/examples/has_facet_inverse_functional_XFAIL.json new file mode 100644 index 00000000..46af64da --- /dev/null +++ b/tests/examples/has_facet_inverse_functional_XFAIL.json @@ -0,0 +1,26 @@ +{ + "@context": { + "kb": "http://example.org/kb/", + "core": "https://ontology.unifiedcyberontology.org/uco/core/" + }, + "@graph": [ + { + "@id": "kb:facet-1", + "@type": "core:Facet" + }, + { + "@id": "kb:object-1", + "@type": "core:UcoObject", + "core:hasFacet": { + "@id": "kb:facet-1" + } + }, + { + "@id": "kb:object-2", + "@type": "core:UcoObject", + "core:hasFacet": { + "@id": "kb:facet-1" + } + } + ] +} diff --git a/tests/examples/test_validation.py b/tests/examples/test_validation.py index 30addef8..8a6a0222 100644 --- a/tests/examples/test_validation.py +++ b/tests/examples/test_validation.py @@ -177,6 +177,21 @@ def test_action_result_PASS_validation(): g = load_validation_graph("action_result_PASS_validation.ttl", True) assert isinstance(g, rdflib.Graph) +def test_has_facet_inverse_functional_PASS() -> None: + confirm_validation_results( + "has_facet_inverse_functional_PASS_validation.ttl", + True + ) + +def test_has_facet_inverse_functional_XFAIL() -> None: + confirm_validation_results( + "has_facet_inverse_functional_XFAIL_validation.ttl", + False, + expected_focus_node_severities={ + ("http://example.org/kb/facet-1", str(NS_SH.Violation)) + } + ) + def test_hash_PASS() -> None: g = load_validation_graph("hash_PASS_validation.ttl", True) assert isinstance(g, rdflib.Graph)