#![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 assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn ggen() -> Command {
Command::cargo_bin("ggen").expect("ggen binary should build")
}
fn write_data_ttl(dir: &TempDir) -> std::path::PathBuf {
let path = dir.path().join("data.ttl");
let ttl = r#"@prefix ex: <http://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
ex:alice a foaf:Person ;
foaf:name "Alice" ;
foaf:knows ex:bob .
ex:bob a foaf:Person ;
foaf:name "Bob" .
"#;
fs::write(&path, ttl).expect("write data.ttl");
path
}
fn write_ontology_ttl(dir: &TempDir) -> std::path::PathBuf {
let path = dir.path().join("schema.ttl");
let ttl = r#"@prefix ex: <http://example.org#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:Product a owl:Class ;
rdfs:label "Product" ;
rdfs:comment "A product in the catalog" .
ex:Order a owl:Class ;
rdfs:label "Order" ;
rdfs:comment "A customer order" .
ex:name a owl:DatatypeProperty ;
rdfs:domain ex:Product ;
rdfs:range xsd:string ;
rdfs:label "Name" .
ex:price a owl:DatatypeProperty ;
rdfs:domain ex:Product ;
rdfs:range xsd:decimal ;
rdfs:label "Price" .
ex:quantity a owl:DatatypeProperty ;
rdfs:domain ex:Order ;
rdfs:range xsd:integer ;
rdfs:label "Quantity" .
"#;
fs::write(&path, ttl).expect("write schema.ttl");
path
}
#[test]
fn graph_load_imports_ttl_and_reports_triple_count() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
ggen()
.arg("graph")
.arg("load")
.arg("--file")
.arg(data.to_str().unwrap())
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"triples_loaded\":5"))
.stdout(predicate::str::contains("\"total_triples\":5"))
.stdout(predicate::str::contains("Turtle"));
}
#[test]
fn graph_load_missing_file_fails_loudly() {
let dir = TempDir::new().unwrap();
ggen()
.arg("graph")
.arg("load")
.arg("--file")
.arg(dir.path().join("does-not-exist.ttl").to_str().unwrap())
.current_dir(&dir)
.assert()
.failure()
.stderr(predicate::str::contains("not found").or(predicate::str::contains("Load failed")));
}
#[test]
fn graph_query_returns_bindings_from_loaded_graph() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
let sparql = r#"PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name WHERE { ?p a foaf:Person ; foaf:name ?name } ORDER BY ?name"#;
ggen()
.arg("graph")
.arg("query")
.arg("--sparql_query")
.arg(sparql)
.arg("--graph_file")
.arg(data.to_str().unwrap())
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"result_count\":2"))
.stdout(predicate::str::contains("Alice"))
.stdout(predicate::str::contains("Bob"));
}
#[test]
fn graph_query_ask_returns_real_boolean() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
ggen()
.arg("graph")
.arg("query")
.arg("--sparql_query")
.arg("PREFIX foaf: <http://xmlns.com/foaf/0.1/> ASK { ?s foaf:knows ?o }")
.arg("--graph_file")
.arg(data.to_str().unwrap())
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("result"))
.stdout(predicate::str::contains("true"));
}
#[test]
fn graph_export_writes_serialized_file_with_real_triples() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
let out = dir.path().join("exported.ttl");
ggen()
.arg("graph")
.arg("export")
.arg("--input_file")
.arg(data.to_str().unwrap())
.arg("--output")
.arg(out.to_str().unwrap())
.arg("--format")
.arg("turtle")
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"triples_exported\":5"));
assert!(out.exists(), "export must write a real output file");
let content = fs::read_to_string(&out).expect("read exported file");
assert!(!content.is_empty(), "exported file must have content");
assert!(
content.contains("http://example.org/alice") || content.contains("alice"),
"export must contain real serialized triples, got:\n{content}"
);
assert!(
content.contains("http://example.org/bob") || content.contains("bob"),
"export must contain real serialized triples, got:\n{content}"
);
}
#[test]
fn graph_export_ntriples_writes_full_iris() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
let out = dir.path().join("exported.nt");
ggen()
.arg("graph")
.arg("export")
.arg("--input_file")
.arg(data.to_str().unwrap())
.arg("--output")
.arg(out.to_str().unwrap())
.arg("--format")
.arg("ntriples")
.current_dir(&dir)
.assert()
.success();
assert!(out.exists(), "n-triples export file must exist");
let content = fs::read_to_string(&out).expect("read nt file");
assert!(
content.contains("<http://example.org/alice>"),
"n-triples must use full IRIs, got:\n{content}"
);
assert!(
content.contains("<http://xmlns.com/foaf/0.1/name>"),
"n-triples must contain the foaf:name predicate IRI, got:\n{content}"
);
}
#[test]
fn graph_visualize_writes_dot_artifact_with_real_nodes() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
ggen()
.arg("graph")
.arg("visualize")
.arg("--input_file")
.arg(data.to_str().unwrap())
.arg("--format")
.arg("dot")
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"nodes_rendered\":2"));
let dot = dir.path().join("data.dot");
assert!(
dot.exists(),
"visualize must write a real .dot artifact at {dot:?}"
);
let content = fs::read_to_string(&dot).expect("read dot file");
assert!(
content.contains("digraph RDF"),
"artifact must be real Graphviz DOT, got:\n{content}"
);
assert!(
content.contains("http://example.org/alice"),
"DOT must contain real graph node IRIs, got:\n{content}"
);
}
#[test]
fn graph_visualize_json_writes_node_edge_json() {
let dir = TempDir::new().unwrap();
let data = write_data_ttl(&dir);
ggen()
.arg("graph")
.arg("visualize")
.arg("--input_file")
.arg(data.to_str().unwrap())
.arg("--format")
.arg("json")
.current_dir(&dir)
.assert()
.success();
let json = dir.path().join("data.json");
assert!(
json.exists(),
"visualize json must write a real artifact at {json:?}"
);
let content = fs::read_to_string(&json).expect("read json artifact");
assert!(content.contains("\"nodes\""), "json must have nodes array");
assert!(content.contains("\"edges\""), "json must have edges array");
assert!(
content.contains("http://example.org/alice"),
"json must contain real node IRIs, got:\n{content}"
);
}
#[test]
fn graph_validate_extracts_schema_and_reports_counts() {
let dir = TempDir::new().unwrap();
let schema = write_ontology_ttl(&dir);
ggen()
.arg("graph")
.arg("validate")
.arg("--schema_file")
.arg(schema.to_str().unwrap())
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"classes_count\":2"))
.stdout(predicate::str::contains("\"properties_count\":3"))
.stdout(predicate::str::contains("\"is_valid\":true"));
}
#[test]
fn graph_validate_strict_mode_runs_reference_checks() {
let dir = TempDir::new().unwrap();
let schema = write_ontology_ttl(&dir);
ggen()
.arg("graph")
.arg("validate")
.arg("--schema_file")
.arg(schema.to_str().unwrap())
.arg("--strict")
.current_dir(&dir)
.assert()
.success()
.stdout(predicate::str::contains("\"classes_count\":2"))
.stdout(predicate::str::contains("\"is_valid\":true"));
}