Files
ro-crate-interoperability-p…/0.2.x/lib/python/lib-ro-crate-schema/tests/test_schema_facade.py
Pascal Su 9928ab797e Prototype Pydantic declarative scheme (#3)
* Update quickstart examples

* Implementation dump: Pydantic decorators but still java api compatible

* Cleanup for publishing
2025-10-31 17:12:10 +01:00

337 lines
12 KiB
Python

import unittest
import sys
import json
import tempfile
from pathlib import Path
# Add source to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from lib_ro_crate_schema.crate.schema_facade import SchemaFacade
from lib_ro_crate_schema.crate.type import Type
from lib_ro_crate_schema.crate.type_property import TypeProperty
from lib_ro_crate_schema.crate.literal_type import LiteralType
from lib_ro_crate_schema.crate.metadata_entry import MetadataEntry
from lib_ro_crate_schema.crate.restriction import Restriction
class TestSchemaFacade(unittest.TestCase):
"""Test cases for the SchemaFacade class"""
def setUp(self):
"""Set up test fixtures"""
# Create a basic schema with types and properties
self.name_property = TypeProperty(
id="name",
range_includes=[LiteralType.STRING],
required=True
)
self.age_property = TypeProperty(
id="age",
range_includes=[LiteralType.INTEGER],
required=False
)
self.person_type = Type(
id="Person",
rdfs_property=[self.name_property, self.age_property],
comment="A person entity",
label="Person"
)
self.person_entry = MetadataEntry(
id="person1",
class_id="Person",
properties={"name": "John Doe", "age": 30}
)
self.facade = SchemaFacade(
types=[self.person_type],
metadata_entries=[self.person_entry]
)
def test_facade_creation(self):
"""Test basic SchemaFacade creation"""
empty_facade = SchemaFacade()
self.assertEqual(len(empty_facade.types), 0)
self.assertEqual(len(empty_facade.metadata_entries), 0)
self.assertEqual(len(self.facade.types), 1)
self.assertEqual(len(self.facade.metadata_entries), 1)
def test_fluent_api(self):
"""Test fluent API methods"""
facade = SchemaFacade()
result = facade.addType(self.person_type).addEntry(self.person_entry)
# Check method chaining works
self.assertEqual(result, facade)
# Check items were added
self.assertIn(self.person_type, facade.types)
self.assertIn(self.person_entry, facade.metadata_entries)
def test_get_methods(self):
"""Test getter methods"""
# Test get_types
types = self.facade.get_types()
self.assertEqual(len(types), 1)
self.assertEqual(types[0].id, "Person")
# Test get_type
person_type = self.facade.get_type("Person")
self.assertIsNotNone(person_type)
self.assertEqual(person_type.id, "Person")
non_existent = self.facade.get_type("NonExistent")
self.assertIsNone(non_existent)
# Test get_entries
entries = self.facade.get_entries()
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].id, "person1")
# Test get_entry
person_entry = self.facade.get_entry("person1")
self.assertIsNotNone(person_entry)
self.assertEqual(person_entry.id, "person1")
# Test get_entries_by_class
person_entries = self.facade.get_entries_by_class("Person")
self.assertEqual(len(person_entries), 1)
self.assertEqual(person_entries[0].id, "person1")
def test_java_api_compatibility(self):
"""Test Java API compatibility methods"""
# Test property methods
properties = self.facade.get_property_types()
self.assertEqual(len(properties), 2)
property_ids = [prop.id for prop in properties]
self.assertIn("name", property_ids)
self.assertIn("age", property_ids)
# Test get_property_type
name_prop = self.facade.get_property_type("name")
self.assertIsNotNone(name_prop)
self.assertEqual(name_prop.id, "name")
# Test get_crate (basic functionality)
crate = self.facade.get_crate()
self.assertIsNotNone(crate)
def test_to_triples(self):
"""Test RDF triple generation"""
triples = list(self.facade.to_triples())
# Should generate triples for both types and metadata entries
self.assertGreater(len(triples), 0)
triple_strs = [(str(s), str(p), str(o)) for s, p, o in triples]
# Should include type definition triples
class_triple_found = any("Class" in triple[2] for triple in triple_strs)
self.assertTrue(class_triple_found, "Should generate class definition triples")
# Should include metadata entry triples
person_triple_found = any("person1" in triple[0] for triple in triple_strs)
self.assertTrue(person_triple_found, "Should generate metadata entry triples")
def test_to_graph(self):
"""Test RDF Graph generation"""
graph = self.facade.to_graph()
# Should have triples
self.assertGreater(len(graph), 0)
# Should have proper namespace binding
namespaces = dict(graph.namespaces())
self.assertIn('base', namespaces)
def test_to_json(self):
"""Test JSON-LD generation"""
json_data = self.facade.to_json()
self.assertIsInstance(json_data, dict)
self.assertIn("@context", json_data)
self.assertIn("@graph", json_data)
def test_write_to_crate(self):
"""Test writing to RO-Crate directory"""
with tempfile.TemporaryDirectory() as temp_dir:
self.facade.write(
temp_dir,
name="Test Crate",
description="A test RO-Crate",
license="MIT"
)
# Check that metadata file was created
metadata_file = Path(temp_dir) / "ro-crate-metadata.json"
self.assertTrue(metadata_file.exists())
# Check that the file contains valid JSON
with open(metadata_file, 'r') as f:
crate_data = json.load(f)
self.assertIn("@context", crate_data)
self.assertIn("@graph", crate_data)
def test_from_ro_crate_roundtrip(self):
"""Test creating facade from RO-Crate and ensuring roundtrip consistency"""
with tempfile.TemporaryDirectory() as temp_dir:
# Write original facade
self.facade.write(temp_dir, name="Roundtrip Test")
# Read back from file
metadata_file = Path(temp_dir) / "ro-crate-metadata.json"
imported_facade = SchemaFacade.from_ro_crate(metadata_file)
# Check that types were imported
self.assertGreater(len(imported_facade.types), 0)
# Check that metadata entries were imported
self.assertGreater(len(imported_facade.metadata_entries), 0)
def test_from_dict(self):
"""Test creating facade from dictionary"""
# Create a simple RO-Crate structure
crate_dict = {
"@context": ["https://w3id.org/ro/crate/1.1/context"],
"@graph": [
{
"@id": "./",
"@type": "Dataset",
"name": "Test Dataset"
},
{
"@id": "ro-crate-metadata.json",
"@type": "CreativeWork",
"about": {"@id": "./"}
},
{
"@id": "Person",
"@type": "rdfs:Class",
"rdfs:label": "Person",
"rdfs:comment": "A person"
},
{
"@id": "name",
"@type": "rdf:Property",
"rdfs:label": "Name",
"schema:domainIncludes": {"@id": "Person"},
"schema:rangeIncludes": {"@id": "http://www.w3.org/2001/XMLSchema#string"}
},
{
"@id": "person1",
"@type": "Person",
"name": "Alice Johnson"
}
]
}
facade = SchemaFacade.from_dict(crate_dict)
# Should have imported the class
person_type = facade.get_type("Person")
self.assertIsNotNone(person_type)
self.assertEqual(person_type.label, "Person")
# Should have imported the metadata entry
person_entry = facade.get_entry("person1")
self.assertIsNotNone(person_entry)
self.assertEqual(person_entry.class_id, "Person")
def test_resolve_forward_refs(self):
"""Test forward reference resolution"""
# This is mostly an internal method, but we can test it doesn't crash
self.facade.resolve_forward_refs()
# Should still have the same number of types and entries
self.assertEqual(len(self.facade.types), 1)
self.assertEqual(len(self.facade.metadata_entries), 1)
def test_add_property_type(self):
"""Test adding standalone property to registry"""
new_prop = TypeProperty(id="email", range_includes=[LiteralType.STRING])
result = self.facade.add_property_type(new_prop)
# Should return self for chaining
self.assertEqual(result, self.facade)
# Should be able to retrieve the property
retrieved_prop = self.facade.get_property_type("email")
self.assertIsNotNone(retrieved_prop)
self.assertEqual(retrieved_prop.id, "email")
def test_complex_schema(self):
"""Test facade with complex schema including restrictions"""
# Create a type with custom restrictions
title_prop = TypeProperty(id="title", range_includes=[LiteralType.STRING])
authors_prop = TypeProperty(id="authors", range_includes=["Person"])
title_restriction = Restriction(
property_type="title",
min_cardinality=1,
max_cardinality=1
)
authors_restriction = Restriction(
property_type="authors",
min_cardinality=1,
max_cardinality=None # Unbounded
)
article_type = Type(
id="Article",
rdfs_property=[title_prop, authors_prop],
restrictions=[title_restriction, authors_restriction],
comment="A research article",
label="Article"
)
article_entry = MetadataEntry(
id="article1",
class_id="Article",
properties={"title": "Great Research"},
references={"authors": ["person1"]}
)
complex_facade = SchemaFacade(
types=[self.person_type, article_type],
metadata_entries=[self.person_entry, article_entry]
)
# Test that complex schema works
self.assertEqual(len(complex_facade.types), 2)
self.assertEqual(len(complex_facade.metadata_entries), 2)
# Test restrictions are included
article = complex_facade.get_type("Article")
restrictions = article.get_restrictions()
self.assertGreater(len(restrictions), 0)
# Test triple generation works
triples = list(complex_facade.to_triples())
self.assertGreater(len(triples), 0)
def test_empty_facade_operations(self):
"""Test operations on empty facade"""
empty_facade = SchemaFacade()
# Should handle empty operations gracefully
self.assertEqual(len(empty_facade.get_types()), 0)
self.assertEqual(len(empty_facade.get_entries()), 0)
self.assertIsNone(empty_facade.get_type("NonExistent"))
self.assertIsNone(empty_facade.get_entry("NonExistent"))
self.assertEqual(len(empty_facade.get_entries_by_class("NonExistent")), 0)
# Should still generate basic structure
json_data = empty_facade.to_json()
self.assertIn("@context", json_data)
if __name__ == '__main__':
unittest.main()