mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-11 04:01:32 +00:00
161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
"""Tests for vocabulary explorer routes."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from semantica.explorer.dependencies import get_session
|
|
from semantica.explorer.routes.vocabulary import router
|
|
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
mock_session = MagicMock()
|
|
app.dependency_overrides[get_session] = lambda: mock_session
|
|
client = TestClient(app)
|
|
|
|
|
|
MINIMAL_TTL = b"""
|
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
@prefix ex: <http://example.org/> .
|
|
ex:S a skos:ConceptScheme ; skos:prefLabel \"Scheme\" .
|
|
ex:A a skos:Concept ; skos:prefLabel \"Alpha\" ; skos:inScheme ex:S .
|
|
"""
|
|
|
|
|
|
MINIMAL_RDF_XML = b"""<?xml version=\"1.0\"?>
|
|
<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
|
|
xmlns:skos=\"http://www.w3.org/2004/02/skos/core#\"
|
|
xmlns:ex=\"http://example.org/\">
|
|
<skos:ConceptScheme rdf:about=\"http://example.org/SX\">
|
|
<skos:prefLabel xml:lang=\"en\">Scheme X</skos:prefLabel>
|
|
</skos:ConceptScheme>
|
|
</rdf:RDF>
|
|
"""
|
|
|
|
|
|
def setup_function():
|
|
mock_session.reset_mock()
|
|
|
|
|
|
def test_list_schemes_returns_correct_shape():
|
|
mock_session.get_nodes.return_value = ([
|
|
{
|
|
"id": "http://example.org/Scheme1",
|
|
"type": "skos:ConceptScheme",
|
|
"properties": {"content": "My Test Scheme", "description": "A scheme for testing"},
|
|
}
|
|
], 1)
|
|
|
|
response = client.get("/api/vocabulary/schemes")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload[0]["uri"] == "http://example.org/Scheme1"
|
|
assert payload[0]["label"] == "My Test Scheme"
|
|
assert payload[0]["description"] == "A scheme for testing"
|
|
|
|
|
|
def test_list_concepts_returns_flat_membership_list():
|
|
mock_session.get_nodes.return_value = ([
|
|
{"id": "http://example.org/A", "type": "skos:Concept", "properties": {"content": "Alpha", "alt_labels": ["A"]}},
|
|
{"id": "http://example.org/B", "type": "skos:Concept", "properties": {"content": "Beta"}},
|
|
], 2)
|
|
mock_session.get_edges.return_value = ([
|
|
{"source": "http://example.org/A", "target": "http://example.org/S", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/B", "target": "http://example.org/S", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/B", "target": "http://example.org/A", "type": "skos:broader"},
|
|
], 3)
|
|
|
|
response = client.get("/api/vocabulary/concepts?scheme=http://example.org/S")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert len(payload) == 2
|
|
alpha = next(item for item in payload if item["uri"] == "http://example.org/A")
|
|
beta = next(item for item in payload if item["uri"] == "http://example.org/B")
|
|
assert alpha["parent_uri"] is None
|
|
assert beta["parent_uri"] == "http://example.org/A"
|
|
assert alpha["alt_labels"] == ["A"]
|
|
|
|
|
|
def test_hierarchy_parent_child_via_broader():
|
|
mock_session.get_nodes.return_value = ([
|
|
{"id": "http://example.org/Parent", "type": "skos:Concept", "properties": {"content": "Parent Node"}},
|
|
{"id": "http://example.org/Child", "type": "skos:Concept", "properties": {"content": "Child Node"}},
|
|
], 2)
|
|
mock_session.get_edges.return_value = ([
|
|
{"source": "http://example.org/Parent", "target": "http://example.org/Scheme1", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/Child", "target": "http://example.org/Scheme1", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/Child", "target": "http://example.org/Parent", "type": "skos:broader"},
|
|
], 3)
|
|
|
|
response = client.get("/api/vocabulary/hierarchy?scheme=http://example.org/Scheme1")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert len(payload) == 1
|
|
assert payload[0]["uri"] == "http://example.org/Parent"
|
|
assert payload[0]["children"][0]["uri"] == "http://example.org/Child"
|
|
|
|
|
|
def test_hierarchy_cycle_does_not_hang():
|
|
mock_session.get_nodes.return_value = ([
|
|
{"id": "http://example.org/A", "type": "skos:Concept", "properties": {"content": "A"}},
|
|
{"id": "http://example.org/B", "type": "skos:Concept", "properties": {"content": "B"}},
|
|
], 2)
|
|
mock_session.get_edges.return_value = ([
|
|
{"source": "http://example.org/A", "target": "http://example.org/S", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/B", "target": "http://example.org/S", "type": "skos:inScheme"},
|
|
{"source": "http://example.org/A", "target": "http://example.org/B", "type": "skos:broader"},
|
|
{"source": "http://example.org/B", "target": "http://example.org/A", "type": "skos:broader"},
|
|
], 4)
|
|
|
|
response = client.get("/api/vocabulary/hierarchy?scheme=http://example.org/S")
|
|
assert response.status_code == 200
|
|
assert isinstance(response.json(), list)
|
|
|
|
|
|
def test_import_ttl_success():
|
|
mock_session.add_nodes.return_value = 2
|
|
mock_session.add_edges.return_value = 1
|
|
|
|
response = client.post(
|
|
"/api/vocabulary/import",
|
|
files={"file": ("vocab.ttl", MINIMAL_TTL, "text/turtle")},
|
|
)
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["status"] == "success"
|
|
assert payload["format"] == "turtle"
|
|
assert payload["nodes_added"] == 2
|
|
|
|
|
|
def test_import_raw_text_success():
|
|
mock_session.add_nodes.return_value = 1
|
|
mock_session.add_edges.return_value = 0
|
|
|
|
response = client.post(
|
|
"/api/vocabulary/import",
|
|
data={"text": MINIMAL_TTL.decode("utf-8"), "format": "turtle"},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["status"] == "success"
|
|
|
|
|
|
def test_import_rdf_xml_success():
|
|
mock_session.add_nodes.return_value = 1
|
|
mock_session.add_edges.return_value = 0
|
|
|
|
response = client.post(
|
|
"/api/vocabulary/import",
|
|
files={"file": ("vocab.rdf", MINIMAL_RDF_XML, "application/rdf+xml")},
|
|
)
|
|
assert response.status_code == 200
|
|
assert response.json()["format"] == "xml"
|
|
|
|
|
|
def test_import_invalid_file_returns_422():
|
|
response = client.post(
|
|
"/api/vocabulary/import",
|
|
files={"file": ("bad.ttl", b"this is not valid RDF!", "text/turtle")},
|
|
)
|
|
assert response.status_code == 422
|