"""Test for GraphUpdate SPARQL query generation and execution. This test verifies that GraphUpdate.generate_sparql_queries() generates valid SPARQL queries that can be executed on RDFGraph instances using rdflib's update() method. """ from rdflib import Literal, URIRef from ontocast.onto.rdfgraph import RDFGraph from ontocast.onto.sparql_models import ( GenericSparqlQuery, GraphUpdate, TripleOp, ) def test_rdfgraph_recovers_dangling_semicolon_at_eof() -> None: """RDFGraph should recover from common LLM-truncated Turtle at EOF.""" ttl = """ @prefix ex: . @prefix rdf: . ex:Case85_968 a ex:Appeal ; ex:appealsTo ex:Cassation ; """ graph = RDFGraph._from_turtle_str(ttl) assert len(graph) == 2 assert ( URIRef("http://example.org/Case85_968"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Appeal"), ) in graph assert ( URIRef("http://example.org/Case85_968"), URIRef("http://example.org/appealsTo"), URIRef("http://example.org/Cassation"), ) in graph def test_graph_update_with_language_tags(): """Test GraphUpdate with language-tagged literals.""" # Create initial RDFGraph graph = RDFGraph._from_turtle_str( """ @prefix rdf: . @prefix rdfs: . @prefix ex: . ex:Test a rdfs:Class . """ ) initial_triple_count = len(graph) # Create Turtle with language-tagged literals triples = """ @prefix ex: . @prefix rdfs: . ex:Test rdfs:label "Test Label"@en ; rdfs:comment "Un commentaire"@fr . """ graph_update = GraphUpdate( triple_operations=[ TripleOp( type="insert", graph=triples, # type: ignore[arg-type] prefixes={"ex": "http://example.org/"}, ) ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate one query assert len(queries) == 1 # Execute the query on the graph graph.update(queries[0]) # Verify new triples were added assert len(graph) == initial_triple_count + 2 def test_graph_update_insert_operation(): """Test GraphUpdate with TripleOp insert operations using Turtle format.""" # Create initial RDFGraph graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:Person a rdfs:Class ; rdfs:label "Person" . """ ) initial_triple_count = len(graph) # Create triples in Turtle format triples = """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:John a ex:Person ; rdfs:label "John Doe" . """ graph_update = GraphUpdate( triple_operations=[ TripleOp( type="insert", graph=triples, # type: ignore[arg-type] prefixes={"ex": "http://example.org/"}, ) ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate one query assert len(queries) == 1 # Execute the query on the graph graph.update(queries[0]) # Verify new triples were added assert len(graph) == initial_triple_count + 2 assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Person"), ) in graph assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("John Doe"), ) in graph def test_graph_update_extract_insert_graph() -> None: """Test GraphUpdate.extract_insert_graph returns only insert triples.""" insert_ttl = """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:Person a rdfs:Class . ex:Person rdfs:label "Person" . """ delete_ttl = """ @prefix ex: . @prefix rdfs: . ex:Obsolete a rdfs:Class . """ gu = GraphUpdate( triple_operations=[ TripleOp(type="insert", graph=insert_ttl), # type: ignore[arg-type] TripleOp(type="delete", graph=delete_ttl), # type: ignore[arg-type] ] ) insert_graph = gu.extract_insert_graph() assert len(insert_graph) == 2 person_uri = URIRef("http://example.org/Person") rdf_type = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") rdfs_class = URIRef("http://www.w3.org/2000/01/rdf-schema#Class") rdfs_label = URIRef("http://www.w3.org/2000/01/rdf-schema#label") assert (person_uri, rdf_type, rdfs_class) in insert_graph assert (person_uri, rdfs_label, Literal("Person")) in insert_graph obsolete_uri = URIRef("http://example.org/Obsolete") assert (obsolete_uri, rdf_type, rdfs_class) not in insert_graph def test_graph_update_delete_operation(): """Test GraphUpdate with TripleOp delete operations.""" # Create RDFGraph with existing triples graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:Person a rdfs:Class ; rdfs:label "Person" . ex:John a ex:Person ; rdfs:label "John Doe" . ex:Jane a ex:Person ; rdfs:label "Jane Smith" . """ ) initial_triple_count = len(graph) # Create GraphUpdate with TripleOp using Turtle format triples = """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:John a ex:Person ; rdfs:label "John Doe" . """ graph_update = GraphUpdate( triple_operations=[ TripleOp( type="delete", graph=triples, # type: ignore[arg-type] prefixes={"ex": "http://example.org/"}, ) ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate one query assert len(queries) == 1 # Execute the query on the graph graph.update(queries[0]) # Verify triples were removed assert len(graph) == initial_triple_count - 2 assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Person"), ) not in graph assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("John Doe"), ) not in graph # Jane should still be there assert ( URIRef("http://example.org/Jane"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Person"), ) in graph def test_graph_update_with_prefixes(): """Test GraphUpdate with TripleOp operations that declare custom prefixes.""" # Create initial RDFGraph graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . ex:Person a rdf:Class . """ ) initial_triple_count = len(graph) # Create GraphUpdate with custom prefixes using Turtle format triples = """ @prefix ex: . @prefix rdf: . @prefix schema: . ex:John a ex:Person ; schema:name "John Doe" . """ graph_update = GraphUpdate( triple_operations=[ TripleOp( type="insert", graph=triples, # type: ignore[arg-type] prefixes={ "ex": "http://example.org/", "schema": "https://schema.org/", }, ), ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate one query assert len(queries) == 1 # Verify the query includes PREFIX declarations assert "PREFIX schema: " in queries[0] # Execute the query on the graph graph.update(queries[0]) # Verify new triples were added assert len(graph) == initial_triple_count + 2 assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Person"), ) in graph assert ( URIRef("http://example.org/John"), URIRef("https://schema.org/name"), Literal("John Doe"), ) in graph def test_graph_update_mixed_operations_ordered(): """Test GraphUpdate with mixed operations in specific order.""" # Create initial RDFGraph graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:Person a rdfs:Class ; rdfs:label "Person" . ex:John a ex:Person ; rdfs:label "John Doe" . """ ) initial_triple_count = len(graph) # Create GraphUpdate with mixed operations using Turtle format insert_jane = """ @prefix ex: . @prefix rdf: . @prefix schema: . ex:Jane a ex:Person ; schema:name "Jane Smith" . """ delete_john_label = """ @prefix ex: . @prefix rdfs: . ex:John rdfs:label "John Doe" . """ insert_john_label = """ @prefix ex: . @prefix rdfs: . ex:John rdfs:label "John Updated" . """ graph_update = GraphUpdate( triple_operations=[ # First: Insert new person with custom schema prefix TripleOp( type="insert", graph=insert_jane, # type: ignore[arg-type] prefixes={ "ex": "http://example.org/", "schema": "https://schema.org/", }, ), # Second: Delete John's label TripleOp( type="delete", graph=delete_john_label, # type: ignore[arg-type] prefixes={"ex": "http://example.org/"}, ), # Third: Insert new label for John TripleOp( type="insert", graph=insert_john_label, # type: ignore[arg-type] prefixes={"ex": "http://example.org/"}, ), ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate 3 queries (one for each TripleOp) assert len(queries) == 3 # Execute queries in order for query in queries: graph.update(query) # Verify final state # Should have: 4 initial + 2 added (Jane) - 1 deleted (John's old label) + 1 added (John's new label) = 6 triples assert ( len(graph) == initial_triple_count + 2 ) # +2 net change: +2 for Jane, -1 for John's old label, +1 for John's new label # Verify John's label was updated assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("John Updated"), ) in graph assert ( URIRef("http://example.org/John"), URIRef("http://www.w3.org/2000/01/rdf-schema#label"), Literal("John Doe"), ) not in graph # Verify Jane was added assert ( URIRef("http://example.org/Jane"), URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), URIRef("http://example.org/Person"), ) in graph assert ( URIRef("http://example.org/Jane"), URIRef("https://schema.org/name"), Literal("Jane Smith"), ) in graph def test_graph_update_generic_sparql_query(): """Test GraphUpdate with GenericSparqlQuery operation.""" # Create initial RDFGraph graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . @prefix rdfs: . ex:Person a rdfs:Class ; rdfs:label "Person" . ex:John a ex:Person ; rdfs:label "John Doe" . """ ) initial_triple_count = len(graph) # Create GraphUpdate with GenericSparqlQuery # Note: GenericSparqlQuery handles its own prefix declarations graph_update = GraphUpdate( sparql_operations=[ GenericSparqlQuery( query="PREFIX ex: \nPREFIX schema: \nPREFIX rdf: \nINSERT { ex:John schema:age 30 } WHERE { ex:John rdf:type ex:Person }" ), ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate one query assert len(queries) == 1 # Verify the query includes the custom SPARQL with prefixes assert "INSERT { ex:John schema:age 30 }" in queries[0] assert "WHERE { ex:John rdf:type ex:Person }" in queries[0] # Execute the query on the graph graph.update(queries[0]) # Verify the custom query was executed assert len(graph) == initial_triple_count + 1 assert ( URIRef("http://example.org/John"), URIRef("https://schema.org/age"), Literal(30), ) in graph def test_graph_update_empty_operations(): """Test GraphUpdate with empty operations list.""" graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . ex:Person a rdf:Class . """ ) initial_triple_count = len(graph) # Create GraphUpdate with empty operations graph_update = GraphUpdate(triple_operations=[]) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate no queries assert len(queries) == 0 # Graph should remain unchanged assert len(graph) == initial_triple_count def test_graph_update_operations_with_empty_triples(): """Test GraphUpdate with operations that have empty triples lists.""" graph = RDFGraph._from_turtle_str( """ @prefix ex: . @prefix rdf: . ex:Person a rdf:Class . """ ) initial_triple_count = len(graph) # Create GraphUpdate with operations that have empty triples graph_update = GraphUpdate( triple_operations=[ TripleOp(type="insert", graph=RDFGraph()), TripleOp(type="delete", graph=RDFGraph()), ] ) # Generate SPARQL queries queries = graph_update.generate_sparql_queries() # Should generate no queries (empty triples are skipped) assert len(queries) == 0 # Graph should remain unchanged assert len(graph) == initial_triple_count