use anyhow::Result;
use std::sync::Arc;
use uni_db::{DataType, Uni, UniError};
#[tokio::test]
async fn test_complex_cypher() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Product")
.property("name", DataType::String)
.property("price", DataType::Float)
.label("Order")
.property("date", DataType::String)
.edge_type("CONTAINS", &["Order"], &["Product"])
.property("qty", DataType::Int32)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (p1:Product {name: 'Apple', price: 1.2})")
.await?;
tx.execute("CREATE (p2:Product {name: 'Banana', price: 0.8})")
.await?;
tx.execute("CREATE (o1:Order {date: '2024-01-01'})").await?;
tx.execute("CREATE (o2:Order {date: '2024-01-02'})").await?;
tx.execute("MATCH (o:Order {date: '2024-01-01'}), (p:Product {name: 'Apple'}) CREATE (o)-[:CONTAINS {qty: 10}]->(p)").await?;
tx.execute("MATCH (o:Order {date: '2024-01-01'}), (p:Product {name: 'Banana'}) CREATE (o)-[:CONTAINS {qty: 5}]->(p)").await?;
tx.execute("MATCH (o:Order {date: '2024-01-02'}), (p:Product {name: 'Apple'}) CREATE (o)-[:CONTAINS {qty: 20}]->(p)").await?;
tx.commit().await?;
let query = "
MATCH (o:Order)-[r:CONTAINS]->(p:Product)
RETURN p.name, sum(r.qty) as total_qty
";
let result = db.session().query(query).await?;
assert_eq!(result.len(), 2);
let mut map = std::collections::HashMap::new();
for row in result {
let name: String = row.get("p.name")?;
let qty: i64 = row.get("total_qty")?;
map.insert(name, qty);
}
assert_eq!(map.get("Apple"), Some(&30));
assert_eq!(map.get("Banana"), Some(&5));
Ok(())
}
#[tokio::test]
async fn test_error_handling() -> Result<()> {
let db = Uni::in_memory().build().await?;
let res = db.session().query("MATCH (n").await;
assert!(matches!(res, Err(UniError::Parse { .. })));
let res = db.session().query("MATCH (n:NonExistent) RETURN n").await;
assert!(
res.is_ok(),
"Unknown labels should succeed with empty results"
);
assert!(
res.unwrap().rows().is_empty(),
"Unknown label should return no rows"
);
db.schema()
.label("User")
.property("age", DataType::Int32)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (:User {age: 25})").await?;
tx.commit().await?;
let res = db.session().query("MATCH (u:User) RETURN u.age").await?;
let row = &res.rows()[0];
let err = row.get::<bool>("u.age");
assert!(matches!(err, Err(UniError::Type { .. })));
Ok(())
}
#[tokio::test]
async fn test_concurrency() -> Result<()> {
let db = Arc::new(Uni::in_memory().build().await?);
db.schema()
.label("Counter")
.property("val", DataType::Int32)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (:Counter {val: 0})").await?;
tx.commit().await?;
let mut handles = Vec::new();
for _ in 0..10 {
let db_clone = db.clone();
handles.push(tokio::spawn(async move {
let session = db_clone.session();
let tx = session.tx().await.unwrap();
tx.execute("CREATE (:Counter {val: 1})").await.unwrap();
tx.commit().await.unwrap();
}));
}
for h in handles {
h.await?;
}
let count = db
.session()
.query("MATCH (c:Counter) RETURN count(c) as cnt")
.await?;
let cnt = count.rows()[0].get::<i64>("cnt")?;
assert_eq!(cnt, 11);
Ok(())
}
#[tokio::test]
async fn test_builder_config() -> Result<()> {
let db = Uni::open("tmp/test_config")
.config(uni_db::UniConfig {
cache_size: 1024 * 1024, parallelism: 2,
..Default::default()
})
.build()
.await?;
assert_eq!(db.config().cache_size, 1024 * 1024);
assert_eq!(db.config().parallelism, 2);
Ok(())
}