use crate::{
Result, Rudof, RudofConfig,
formats::{InputSpec, IriNormalizationMode},
types::{QueryResult, Triple},
};
use rudof_iri::IriS;
use rudof_rdf::rdf_core::term::{Object, literal::ConcreteLiteral};
use std::str::FromStr;
const GRAPH: &str = r#"
prefix : <http://example.org/>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
:alice :name "Alice" ;
:age 23 ;
:knows :bob .
:bob :name "Bob" .
"#;
fn session() -> Rudof {
let mut rudof = Rudof::new(RudofConfig::default());
let data = InputSpec::from_str(GRAPH).unwrap();
rudof.load_data().with_data(&[data]).execute().unwrap();
rudof
}
fn iri(local: &str) -> IriS {
IriS::new_unchecked(&format!("http://example.org/{local}"))
}
fn node(local: &str) -> Object {
Object::iri(iri(local))
}
fn literal(value: &str) -> Object {
Object::Literal(ConcreteLiteral::str(value))
}
fn triples(rudof: &Rudof, subject: Option<&str>, predicate: Option<&str>, object: Option<&str>) -> Vec<Triple> {
let mut builder = rudof.triples();
if let Some(subject) = subject {
builder = builder.with_subject(subject);
}
if let Some(predicate) = predicate {
builder = builder.with_predicate(predicate);
}
if let Some(object) = object {
builder = builder.with_object(object);
}
let mut triples: Vec<Triple> = builder.execute().unwrap().collect::<Result<_>>().unwrap();
triples.sort_by_key(|triple| triple.to_string());
triples
}
fn all(rudof: &Rudof) -> Vec<Triple> {
triples(rudof, None, None, None)
}
#[test]
fn test_triples_patterns() {
let rudof = session();
assert_eq!(all(&rudof).len(), 4);
assert_eq!(triples(&rudof, Some(":alice"), None, None).len(), 3);
assert_eq!(triples(&rudof, None, Some(":name"), None).len(), 2);
assert_eq!(triples(&rudof, None, None, Some("\"Alice\"")).len(), 1);
assert_eq!(triples(&rudof, Some(":alice"), Some(":name"), None).len(), 1);
assert_eq!(triples(&rudof, Some(":alice"), None, Some("\"Alice\"")).len(), 1);
assert_eq!(triples(&rudof, None, Some(":name"), Some("\"Alice\"")).len(), 1);
assert_eq!(
triples(&rudof, Some(":alice"), Some(":name"), Some("\"Alice\"")).len(),
1
);
assert!(triples(&rudof, Some(":carol"), None, None).is_empty());
assert!(triples(&rudof, Some(":alice"), Some(":name"), Some("\"Bob\"")).is_empty());
}
#[test]
fn test_triples_are_the_terms_of_the_data() {
let rudof = session();
let triples = triples(&rudof, Some(":alice"), Some(":knows"), None);
assert_eq!(
triples,
vec![Triple {
subject: node("alice"),
predicate: iri("knows"),
object: node("bob"),
}]
);
assert_eq!(
triples[0].to_string(),
"http://example.org/alice <http://example.org/knows> http://example.org/bob ."
);
}
#[test]
fn test_add_triple() {
let mut rudof = session();
rudof.add_triple(":carol", ":name", "\"Carol\"").execute().unwrap();
assert_eq!(all(&rudof).len(), 5);
assert_eq!(
triples(&rudof, Some(":carol"), None, None),
vec![Triple {
subject: node("carol"),
predicate: iri("name"),
object: literal("Carol"),
}]
);
rudof.add_triple(":carol", ":name", "\"Carol\"").execute().unwrap();
assert_eq!(all(&rudof).len(), 5);
}
#[test]
fn test_add_triple_every_kind_of_term() {
let mut rudof = session();
rudof
.add_triple("<http://example.org/carol>", "<http://example.org/name>", "\"Carol\"")
.execute()
.unwrap();
rudof.add_triple("_:b1", ":name", "\"Blank\"").execute().unwrap();
rudof.add_triple(":carol", ":age", "30").execute().unwrap();
rudof
.add_triple(":carol", ":height", "\"1.7\"^^xsd:decimal")
.execute()
.unwrap();
rudof.add_triple(":carol", ":label", "\"Carol\"@en").execute().unwrap();
rudof.add_triple(":carol", ":knows", ":alice").execute().unwrap();
assert_eq!(all(&rudof).len(), 10);
let height = &triples(&rudof, Some(":carol"), Some(":height"), None)[0].object;
assert_ne!(height, &literal("1.7"), "{height} lost its datatype");
assert!(height.to_string().contains("1.7"), "{height}");
assert_eq!(triples(&rudof, Some("_:b1"), None, None).len(), 1);
}
#[test]
fn test_remove_triple() {
let mut rudof = session();
rudof.remove_triple(":alice", ":name", "\"Alice\"").execute().unwrap();
assert_eq!(all(&rudof).len(), 3);
assert!(triples(&rudof, Some(":alice"), Some(":name"), None).is_empty());
assert_eq!(triples(&rudof, Some(":bob"), Some(":name"), None).len(), 1);
rudof.remove_triple(":alice", ":name", "\"Alice\"").execute().unwrap();
rudof.remove_triple(":carol", ":name", "\"Carol\"").execute().unwrap();
assert_eq!(all(&rudof).len(), 3);
}
#[test]
fn test_add_and_remove_a_triple_with_the_session_prefixes() {
let mut rudof = Rudof::new(RudofConfig::default());
rudof.add_prefix("ex", "http://example.org/").execute().unwrap();
let data = InputSpec::from_str(GRAPH).unwrap();
rudof.load_data().with_data(&[data]).execute().unwrap();
rudof.add_triple("ex:carol", "ex:name", "\"Carol\"").execute().unwrap();
assert_eq!(triples(&rudof, Some(":carol"), None, None).len(), 1);
rudof
.remove_triple("ex:carol", "ex:name", "\"Carol\"")
.execute()
.unwrap();
assert!(triples(&rudof, Some(":carol"), None, None).is_empty());
}
#[test]
fn test_triple_operations_need_rdf_data() {
let mut rudof = Rudof::new(RudofConfig::default());
let err = rudof.add_triple(":a", ":p", ":b").execute().unwrap_err();
assert!(err.to_string().contains("No RDF data loaded"), "{err}");
let err = rudof.remove_triple(":a", ":p", ":b").execute().unwrap_err();
assert!(err.to_string().contains("No RDF data loaded"), "{err}");
let err = rudof.triples().execute().unwrap_err();
assert!(err.to_string().contains("No RDF data loaded"), "{err}");
}
#[test]
fn test_triple_operations_reject_terms_that_are_not_terms() {
let mut rudof = session();
let err = rudof.add_triple("\"Alice\"", ":p", ":b").execute().unwrap_err();
assert!(err.to_string().contains("cannot be the subject"), "{err}");
let err = rudof.add_triple(":a", "\"name\"", ":b").execute().unwrap_err();
assert!(err.to_string().contains("Iri ref"), "{err}");
let err = rudof
.add_triple(":a", "http://example.org/name", ":b")
.execute()
.unwrap_err();
assert!(err.to_string().contains("prefix 'http'"), "{err}");
let err = rudof.add_triple("other:a", ":p", ":b").execute().unwrap_err();
assert!(err.to_string().contains("other"), "{err}");
rudof
.add_triple("http://example.org/carol", ":name", "\"Carol\"")
.execute()
.unwrap();
let err = rudof
.add_triple("http://example.org/dave", ":name", "\"Dave\"")
.with_iri_mode(IriNormalizationMode::Strict)
.execute()
.unwrap_err();
assert!(err.to_string().contains("resolving prefix 'http"), "{err}");
let err = rudof
.triples()
.with_subject("{FOCUS :knows :bob}")
.execute()
.unwrap_err();
assert!(err.to_string().contains("is not a term"), "{err}");
}
#[cfg(all(not(target_family = "wasm"), feature = "sparql"))]
#[test]
fn test_triple_updates_are_refused_on_endpoint_data() {
let mut rudof = Rudof::new(RudofConfig::default());
rudof
.load_data()
.with_endpoint("http://example.org/sparql")
.execute()
.unwrap();
for err in [
rudof
.add_triple("<http://example.org/a>", "<http://example.org/p>", "\"x\"")
.execute()
.unwrap_err(),
rudof
.remove_triple("<http://example.org/a>", "<http://example.org/p>", "\"x\"")
.execute()
.unwrap_err(),
] {
assert!(err.to_string().contains("cannot be modified"), "{err}");
assert!(err.to_string().contains("example.org/sparql"), "{err}");
}
}
fn names_found(rudof: &mut Rudof) -> usize {
let query = InputSpec::from_str("SELECT ?name WHERE { ?s <http://example.org/name> ?name }").unwrap();
rudof.load_sparql_query(&query).execute().unwrap();
rudof.run_query().execute().unwrap();
match rudof.query_results() {
Some(QueryResult::Select(solutions)) => solutions.iter().count(),
other => panic!("expected SELECT solutions, got {other:?}"),
}
}
#[test]
fn test_added_triples_reach_serialization_and_queries() {
let mut rudof = session();
rudof.add_triple(":carol", ":name", "\"Carol\"").execute().unwrap();
let mut turtle = Vec::new();
rudof.serialize_data(&mut turtle).execute().unwrap();
let turtle = String::from_utf8(turtle).unwrap();
assert!(turtle.contains("Carol"), "{turtle}");
assert_eq!(names_found(&mut rudof), 3);
rudof.remove_triple(":carol", ":name", "\"Carol\"").execute().unwrap();
assert_eq!(names_found(&mut rudof), 2);
}