#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::needless_raw_string_hashes,
clippy::duration_suboptimal_units,
clippy::branches_sharing_code,
clippy::used_underscore_binding,
clippy::single_char_pattern,
clippy::ignore_without_reason,
clippy::cloned_ref_to_slice_refs,
clippy::doc_overindented_list_items,
clippy::match_wildcard_for_single_variants,
clippy::ignored_unit_patterns,
clippy::needless_collect,
clippy::unnecessary_map_or,
clippy::manual_flatten,
clippy::manual_strip,
clippy::future_not_send,
clippy::unnested_or_patterns,
clippy::no_effect_underscore_binding,
clippy::literal_string_with_formatting_args
)]
use ggen_core::graph::{CachedResult, ConstructExecutor, Graph};
#[test]
fn test_construct_query_produces_valid_triples() {
let graph = Graph::new().expect("Failed to create graph");
graph
.insert_turtle(
r#"
@prefix ex: <http://example.org/> .
ex:alice ex:knows ex:bob .
ex:alice ex:knows ex:carol .
"#,
)
.expect("Failed to insert turtle");
let executor = ConstructExecutor::new(&graph);
let triples = executor
.execute(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("CONSTRUCT query should succeed");
assert_eq!(
triples.len(),
2,
"Expected 2 inferred triples, got {}",
triples.len()
);
let ntriples_pattern =
regex::Regex::new(r#"^<[^>]+>\s+<[^>]+>\s+(<[^>]+>|"[^"]*").*$"#).unwrap();
for triple in &triples {
assert!(
ntriples_pattern.is_match(triple),
"Triple does not match N-Triples pattern: {}",
triple
);
}
let cached = graph
.query_cached(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("Cached CONSTRUCT should succeed");
match cached {
CachedResult::Graph(triples_from_cache) => {
assert_eq!(triples_from_cache.len(), 2);
}
other => panic!(
"Expected CachedResult::Graph, got {:?}",
std::mem::discriminant(&other)
),
}
}
#[test]
fn test_construct_triples_can_be_reinserted() {
let source_graph = Graph::new().expect("Failed to create graph");
source_graph
.insert_turtle(
r#"
@prefix ex: <http://example.org/> .
ex:alice ex:knows ex:bob .
"#,
)
.expect("Failed to insert turtle");
let executor = ConstructExecutor::new(&source_graph);
let triples = executor
.execute(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("CONSTRUCT query should succeed");
assert!(
!triples.is_empty(),
"Should have produced at least one triple"
);
let ntriples: String = triples
.iter()
.map(|t| format!("{} .", t))
.collect::<Vec<_>>()
.join("\n");
let target_graph = Graph::new().expect("Failed to create target graph");
target_graph
.insert_turtle(&ntriples)
.expect("Re-inserted triples should parse without error");
assert!(
!target_graph.is_empty(),
"Target graph should not be empty after re-insert"
);
assert_eq!(
target_graph.len(),
1,
"Target graph should have exactly 1 triple"
);
let check = target_graph
.query_cached(
r#"
PREFIX ex: <http://example.org/>
ASK { ?s ex:related ?o }
"#,
)
.expect("ASK query should succeed");
match check {
CachedResult::Boolean(true) => {} other => panic!("Expected ASK true for re-inserted triple, got {:?}", other),
}
}
#[test]
fn test_construct_with_optional_produces_partial_results() {
let graph = Graph::new().expect("Failed to create graph");
graph
.insert_turtle(
r#"
@prefix ex: <http://example.org/> .
ex:alice a ex:Person ;
ex:name "Alice" .
ex:bob a ex:Person ;
ex:name "Bob" ;
ex:email "bob@example.org" .
"#,
)
.expect("Failed to insert turtle");
let executor = ConstructExecutor::new(&graph);
let triples = executor
.execute(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?person ex:contact ?email }
WHERE {
?person a ex:Person ;
ex:name ?name .
OPTIONAL { ?person ex:email ?email }
FILTER(BOUND(?email))
}
"#,
)
.expect("CONSTRUCT with OPTIONAL should not error");
assert_eq!(
triples.len(),
1,
"Expected exactly 1 triple (bob), got {}",
triples.len()
);
let triple_str = &triples[0];
assert!(
triple_str.contains("bob")
|| triple_str.contains("Bob")
|| triple_str.contains("http://example.org/bob"),
"Triple should reference bob: {}",
triple_str
);
}
#[test]
fn test_construct_chain_two_rules() {
let graph = Graph::new().expect("Failed to create graph");
graph
.insert_turtle(
r#"
@prefix ex: <http://example.org/> .
ex:alice ex:knows ex:bob .
ex:bob ex:knows ex:carol .
"#,
)
.expect("Failed to insert turtle");
let executor = ConstructExecutor::new(&graph);
let rule1_count = executor
.execute_and_materialize(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("Rule 1 materialization should succeed");
assert_eq!(rule1_count, 2, "Rule 1 should infer 2 triples");
let rule2_count = executor
.execute_and_materialize(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:indirectConnection ?o }
WHERE { ?s ex:related ?o }
"#,
)
.expect("Rule 2 materialization should succeed");
assert_eq!(
rule2_count, 2,
"Rule 2 should find 2 inferred triples from rule 1"
);
let total = graph.len();
assert!(
total >= 6,
"Graph should have at least 6 triples after chaining, got {}",
total
);
let check = graph
.query_cached(
r#"
PREFIX ex: <http://example.org/>
ASK { ?s ex:indirectConnection ?o }
"#,
)
.expect("ASK query should succeed");
match check {
CachedResult::Boolean(true) => {} other => panic!("Expected ASK true for rule 2 output, got {:?}", other),
}
}
#[test]
fn test_construct_empty_graph_returns_empty() {
let graph = Graph::new().expect("Failed to create graph");
assert!(graph.is_empty(), "Graph should start empty");
let executor = ConstructExecutor::new(&graph);
let triples = executor
.execute(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("CONSTRUCT on empty graph should not error");
assert!(
triples.is_empty(),
"CONSTRUCT on empty graph should return zero triples, got {}",
triples.len()
);
let cached = graph
.query_cached(
r#"
PREFIX ex: <http://example.org/>
CONSTRUCT { ?s ex:related ?o }
WHERE { ?s ex:knows ?o }
"#,
)
.expect("Cached CONSTRUCT on empty graph should not error");
match cached {
CachedResult::Graph(t) => {
assert!(t.is_empty(), "Cached result should be empty");
}
other => panic!(
"Expected CachedResult::Graph, got {:?}",
std::mem::discriminant(&other)
),
}
}