use anyhow::Result;
use uni_db::{DataType, IndexType, ScalarType, Uni, Value};
#[tokio::test]
async fn test_api_transactions() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Account")
.property("balance", DataType::Int64)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (:Account {balance: 100})").await?;
tx.execute("CREATE (:Account {balance: 200})").await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (a:Account) RETURN sum(a.balance) AS total")
.await?;
assert_eq!(result.rows()[0].get::<i64>("total")?, 300);
let tx = db.session().tx().await?;
tx.execute("CREATE (:Account {balance: 500})").await?;
let res_inner = tx
.query("MATCH (a:Account) RETURN sum(a.balance) AS total")
.await?;
assert_eq!(res_inner.rows()[0].get::<i64>("total")?, 800);
tx.rollback();
let res_outer = db
.session()
.query("MATCH (a:Account) RETURN sum(a.balance) AS total")
.await?;
assert_eq!(res_outer.rows()[0].get::<i64>("total")?, 300);
let tx = db.session().tx().await?;
tx.execute("CREATE (:Account {balance: 1000})").await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (a:Account) RETURN sum(a.balance) AS total")
.await?;
assert_eq!(result.rows()[0].get::<i64>("total")?, 1300);
Ok(())
}
#[tokio::test]
async fn test_api_schema_and_property_query() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Person")
.property("name", DataType::String)
.property("age", DataType::Int32)
.index("name", IndexType::Scalar(ScalarType::BTree))
.label("Movie")
.property("title", DataType::String)
.edge_type("ACTED_IN", &["Person"], &["Movie"])
.property("role", DataType::String)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (:Person {name: 'Tom Hanks', age: 68})")
.await?;
tx.execute("CREATE (:Movie {title: 'Cast Away'})").await?;
tx.execute(
"
MATCH (p:Person {name: 'Tom Hanks'}), (m:Movie {title: 'Cast Away'})
CREATE (p)-[:ACTED_IN {role: 'Chuck Noland'}]->(m)
",
)
.await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p.name, p.age, r.role, m.title")
.await?;
assert_eq!(result.len(), 1);
let row = &result.rows()[0];
assert_eq!(row.get::<String>("p.name")?, "Tom Hanks");
assert_eq!(row.get::<i32>("p.age")?, 68);
assert_eq!(row.get::<String>("r.role")?, "Chuck Noland");
assert_eq!(row.get::<String>("m.title")?, "Cast Away");
Ok(())
}
#[tokio::test]
async fn test_api_query_flow() -> Result<()> {
let db = Uni::in_memory().build().await?;
let result = db
.session()
.query("RETURN 1 AS num, 'hello' AS str")
.await?;
assert_eq!(result.len(), 1);
let row = &result.rows()[0];
let num: i64 = row.get("num")?;
let s: String = row.get("str")?;
assert_eq!(num, 1);
assert_eq!(s, "hello");
let result = db
.session()
.query("RETURN [1, 2, 3] AS list, {a: 1} AS map")
.await?;
let row = &result.rows()[0];
let list: Vec<i64> = row.get("list")?;
assert_eq!(list, vec![1, 2, 3]);
let result = db
.session()
.query_with("RETURN $x AS x")
.param("x", 42)
.fetch_all()
.await?;
let x: i64 = result.rows()[0].get("x")?;
assert_eq!(x, 42);
Ok(())
}
#[tokio::test]
async fn test_parameterized_create_vertex() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Person")
.property("name", DataType::String)
.property("age", DataType::Int64)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute_with("CREATE (p:Person {name: $name, age: $age})")
.param("name", "Alice")
.param("age", 30)
.run()
.await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (p:Person {name: 'Alice'}) RETURN p.age AS age")
.await?;
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].get::<i64>("age")?, 30);
Ok(())
}
#[tokio::test]
async fn test_parameterized_create_edge() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Person")
.property("name", DataType::String)
.edge_type("KNOWS", &["Person"], &["Person"])
.property("since", DataType::Int64)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (p:Person {name: 'Alice'})").await?;
tx.execute("CREATE (p:Person {name: 'Bob'})").await?;
tx.commit().await?;
let tx = db.session().tx().await?;
tx.execute_with(
"MATCH (a:Person {name: $src}), (b:Person {name: $dst}) CREATE (a)-[:KNOWS {since: $since}]->(b)",
)
.param("src", "Alice")
.param("dst", "Bob")
.param("since", 2024)
.run()
.await?;
tx.commit().await?;
let result = db
.session().query("MATCH (a:Person {name: 'Alice'})-[k:KNOWS]->(b:Person {name: 'Bob'}) RETURN k.since AS since")
.await?;
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].get::<i64>("since")?, 2024);
Ok(())
}
#[tokio::test]
async fn test_parameterized_set() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Person")
.property("name", DataType::String)
.property("age", DataType::Int64)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (p:Person {name: 'Alice', age: 30})")
.await?;
tx.commit().await?;
let tx = db.session().tx().await?;
tx.execute_with("MATCH (p:Person {name: $name}) SET p.age = $new_age")
.param("name", "Alice")
.param("new_age", 31)
.run()
.await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (p:Person {name: 'Alice'}) RETURN p.age AS age")
.await?;
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].get::<i64>("age")?, 31);
Ok(())
}
#[tokio::test]
async fn test_parameterized_delete() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Person")
.property("name", DataType::String)
.property("age", DataType::Int64)
.apply()
.await?;
let tx = db.session().tx().await?;
tx.execute("CREATE (p:Person {name: 'Alice', age: 30})")
.await?;
tx.execute("CREATE (p:Person {name: 'Bob', age: 25})")
.await?;
tx.commit().await?;
let tx = db.session().tx().await?;
tx.execute_with("MATCH (p:Person {name: $name}) DELETE p")
.param("name", "Alice")
.run()
.await?;
tx.commit().await?;
let result = db
.session()
.query("MATCH (p:Person) RETURN p.name AS name")
.await?;
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].get::<String>("name")?, "Bob");
Ok(())
}
#[tokio::test]
async fn test_execute_with_returns_auto_commit_result() -> Result<()> {
let db = Uni::in_memory().build().await?;
db.schema()
.label("Item")
.property("name", DataType::String)
.apply()
.await?;
let tx = db.session().tx().await?;
let result = tx
.execute_with("CREATE (i:Item {name: $name})")
.param("name", "Widget")
.run()
.await?;
tx.commit().await?;
assert_eq!(result.nodes_created(), 1);
assert_eq!(result.properties_set(), 1);
Ok(())
}
#[tokio::test]
async fn test_register_custom_function() -> Result<()> {
let db = Uni::in_memory().build().await?;
let session = db.session();
db.functions().register("double", |args| {
let n = args.first().and_then(|v| v.as_i64()).unwrap_or(0);
Ok(Value::Int(n * 2))
})?;
let result = session.query("RETURN double(21) AS val").await?;
assert_eq!(result.rows().len(), 1);
assert_eq!(result.rows()[0].get::<i64>("val")?, 42);
Ok(())
}
#[tokio::test]
async fn test_capabilities_write_lease() -> Result<()> {
let db = Uni::in_memory().build().await?;
let session = db.session();
let caps = session.capabilities();
assert!(caps.can_write);
assert!(caps.write_lease.is_none());
Ok(())
}