#[path = "testutils/mod.rs"]
mod testutils;
use graphlite::Value;
use std::collections::HashMap;
use testutils::test_fixture::{AggregateStats, FixtureType, TestCase, TestFixture, TestSuite};
#[test]
fn test_fraud_detection_queries() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_fraud_detection_queries")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
fixture.assert_first_value(
"MATCH (a:Account) RETURN count(a) as account_count",
"account_count",
Value::Number(50.0),
);
fixture.assert_first_value(
"MATCH (m:Merchant) RETURN count(m) as merchant_count",
"merchant_count",
Value::Number(20.0),
);
fixture.assert_first_value(
"MATCH ()-[t:Transaction]->() RETURN count(t) as transaction_count",
"transaction_count",
Value::Number(100.0),
);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant) WHERE t.amount > 100 RETURN count(t) as high_value_count"
);
assert_eq!(result.rows.len(), 1);
fixture.assert_first_value(
"MATCH (a:Account) WHERE a.status = 'active' RETURN count(a) as active_count",
"active_count",
Value::Number(45.0),
);
fixture.assert_first_value(
"MATCH (a:Account) WHERE a.status = 'inactive' RETURN count(a) as inactive_count",
"inactive_count",
Value::Number(5.0),
);
let result = fixture.assert_query_succeeds(
"MATCH (m:Merchant)
RETURN m.category as category, count(m) as count
ORDER BY count DESC",
);
assert_eq!(
result.rows.len(),
1,
"Should have 1 merchant category (all retail)"
);
fixture.assert_first_value(
"MATCH (m:Merchant) RETURN count(m) as merchant_count",
"merchant_count",
Value::Number(20.0),
);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant) WHERE t.amount > 100 RETURN count(t) as high_value_transactions"
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_simple_graph_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_simple_graph_operations")
.expect("Failed to setup graph");
fixture
.insert_simple_data()
.expect("Failed to insert simple data");
fixture.assert_first_value(
"MATCH (n:TestNode) RETURN count(n) as node_count",
"node_count",
Value::Number(20.0),
);
fixture.assert_first_value(
"MATCH ()-[e:CONNECTS_TO]->() RETURN count(e) as edge_count",
"edge_count",
Value::Number(9.0), );
fixture.assert_first_value(
"MATCH (n:TestNode) WHERE n.value > 100 RETURN count(n) as count",
"count",
Value::Number(10.0), );
let expected_stats = AggregateStats {
count: 20.0,
sum: 2100.0, avg: 105.0, min: 10.0,
max: 200.0,
};
let stats = fixture.assert_aggregates(
"MATCH (n:TestNode)
RETURN count(n.value) as count,
sum(n.value) as sum,
avg(n.value) as avg,
min(n.value) as min,
max(n.value) as max",
expected_stats,
);
assert_eq!(stats.count, 20.0);
assert_eq!(stats.sum, 2100.0);
assert_eq!(stats.avg, 105.0);
assert_eq!(stats.min, 10.0); assert_eq!(stats.max, 200.0);
let result = fixture.assert_query_succeeds(
"MATCH (start:TestNode)-[:CONNECTS_TO]->(end:TestNode) WHERE start.id = 1 RETURN count(end) as connected_count"
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_data_driven_match_queries() {
let test_suite = TestSuite {
name: "MATCH Query Tests".to_string(),
fixture_type: FixtureType::Fraud,
test_cases: vec![
TestCase {
name: "count_all_nodes".to_string(),
description: "Count all nodes in the graph".to_string(),
query: "MATCH (n) RETURN count(n) as total".to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([("total".to_string(), Value::Number(70.0))])), expected_error: None,
},
TestCase {
name: "count_all_relationships".to_string(),
description: "Count all relationships in the graph".to_string(),
query: "MATCH ()-[r]->() RETURN count(r) as total".to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([("total".to_string(), Value::Number(150.0))])), expected_error: None,
},
TestCase {
name: "find_accounts_by_status".to_string(),
description: "Find active accounts (all 50 are active)".to_string(),
query: "MATCH (a:Account) WHERE a.status = 'active' RETURN count(a) as count"
.to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([("count".to_string(), Value::Number(50.0))])), expected_error: None,
},
TestCase {
name: "retail_merchants".to_string(),
description: "Find retail merchants (all are retail)".to_string(),
query: "MATCH (m:Merchant) WHERE m.category = 'retail' RETURN count(m) as count"
.to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([("count".to_string(), Value::Number(20.0))])), expected_error: None,
},
TestCase {
name: "transaction_count".to_string(),
description: "Count all transactions".to_string(),
query: "MATCH ()-[t:Transaction]->() RETURN count(t) as count".to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([("count".to_string(), Value::Number(100.0))])),
expected_error: None,
},
],
};
let results = test_suite.run().expect("Failed to run test suite");
results.print_summary();
assert_eq!(results.failed, 0, "All test cases should pass");
}
#[test]
fn test_data_driven_aggregation_queries() {
let test_suite = TestSuite {
name: "Aggregation Query Tests".to_string(),
fixture_type: FixtureType::Fraud,
test_cases: vec![
TestCase {
name: "avg_transaction_amount".to_string(),
description: "Calculate average transaction amount".to_string(),
query: "MATCH ()-[t:Transaction]->() RETURN avg(t.amount) as avg_amount".to_string(),
expected_rows: Some(1),
expected_values: None,
expected_error: None,
},
TestCase {
name: "group_by_merchant_category".to_string(),
description: "Group transactions by merchant category".to_string(),
query: "MATCH (m:Merchant) RETURN m.category, count(m) as count ORDER BY count DESC".to_string(),
expected_rows: Some(1), expected_values: None,
expected_error: None,
},
TestCase {
name: "account_balance_distribution".to_string(),
description: "Get account balance statistics".to_string(),
query: "MATCH (a:Account) RETURN min(a.balance) as min_balance, max(a.balance) as max_balance, avg(a.balance) as avg_balance".to_string(),
expected_rows: Some(1),
expected_values: None,
expected_error: None,
},
TestCase {
name: "transactions_per_account".to_string(),
description: "Count transactions per account".to_string(),
query: "MATCH (a:Account)-[t:Transaction]->() RETURN a.name, count(t) as transaction_count ORDER BY transaction_count DESC LIMIT 10".to_string(), expected_rows: Some(10),
expected_values: None,
expected_error: None,
},
],
};
let results = test_suite.run().expect("Failed to run test suite");
results.print_summary();
assert_eq!(results.failed, 0, "All aggregation test cases should pass");
}
#[test]
fn test_data_modification_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_data_modification_operations")
.expect("Failed to setup graph");
fixture
.insert_simple_data()
.expect("Failed to insert simple data");
fixture.assert_query_succeeds("INSERT (new:TestNode {name: 'Node 21', value: 21})");
fixture.assert_first_value(
"MATCH (n:TestNode) RETURN count(n) as count",
"count",
Value::Number(21.0),
);
fixture.assert_query_succeeds("MATCH (n:TestNode) WHERE n.value = 21 SET n.updated = true");
let result = fixture
.assert_query_succeeds("MATCH (n:TestNode) WHERE n.updated = true RETURN n.value as value");
assert_eq!(result.rows.len(), 1);
fixture.assert_query_succeeds("MATCH (n:TestNode) WHERE n.value = 21 DELETE n");
fixture.assert_first_value(
"MATCH (n:TestNode) RETURN count(n) as count",
"count",
Value::Number(20.0),
);
}
#[test]
fn test_complex_pattern_matching() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_complex_pattern_matching")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"MATCH (a1:Account)-[:Transaction]->(m:Merchant)<-[:Transaction]-(a2:Account) RETURN count(DISTINCT m) as shared_merchants"
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant) WHERE t.amount > 50 RETURN a.name, count(t) as high_value_txns ORDER BY high_value_txns DESC LIMIT 10"
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[:Transaction]->(m:Merchant) WHERE a.balance > 2000 RETURN count(DISTINCT m) as merchant_count"
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_with_clause_basic_aggregation() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_with_clause_basic_aggregation")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WITH m, count(t) as transaction_count, avg(t.amount) as avg_amount
WHERE transaction_count > 3
RETURN m.name, transaction_count, avg_amount
ORDER BY transaction_count DESC",
);
assert!(
!result.rows.is_empty(),
"Should have merchants with >3 transactions"
);
assert!(
result.variables.len() >= 3,
"Should have at least 3 columns"
);
for row in &result.rows {
if let Some(Value::Number(count)) = row.get_value("transaction_count") {
assert!(
*count > 3.0,
"All returned merchants should have >3 transactions"
);
}
if let Some(Value::Number(avg)) = row.get_value("avg_amount") {
assert!(*avg > 0.0, "Average transaction amount should be positive");
}
}
}
#[test]
fn test_with_clause_distinct_aggregation() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_with_clause_distinct_aggregation")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WITH a, count(DISTINCT m) as merchant_count
RETURN avg(merchant_count) as avg_merchants_per_account",
);
assert_eq!(
result.rows.len(),
1,
"Aggregated RETURN should produce exactly one row"
);
if let Some(Value::Number(avg)) = result.rows[0].get_value("avg_merchants_per_account") {
assert!(*avg > 0.0, "Average should be greater than 0");
assert!(
*avg <= 20.0,
"Average should not exceed total merchant count"
);
} else {
panic!("Expected avg_merchants_per_account to be a number");
}
}
#[test]
fn test_error_handling() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_error_handling")
.expect("Failed to setup graph");
fixture
.insert_simple_data()
.expect("Failed to insert simple data");
fixture.assert_query_fails("MATCH (n:TestNode WHERE n.value > 10", "Parse error");
fixture.assert_query_fails("MATCH (n:TestNode) WHERE n.value > 10", "Parse error");
fixture.assert_query_fails("MATCH (n:TestNode) WHERE n.name > 100 RETURN n", "");
}
#[test]
fn test_transaction_consistency() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_transaction_consistency")
.expect("Failed to setup graph");
fixture
.insert_simple_data()
.expect("Failed to insert simple data");
fixture.assert_query_succeeds("BEGIN");
fixture.assert_query_succeeds("INSERT (temp:TempNode {name: 'Temporary', value: 999})");
fixture.assert_first_value(
"MATCH (n:TempNode) RETURN count(n) as count",
"count",
Value::Number(1.0),
);
fixture.assert_query_succeeds("ROLLBACK");
fixture.assert_first_value(
"MATCH (n:TempNode) RETURN count(n) as count",
"count",
Value::Number(0.0),
);
}
#[test]
#[ignore]
fn test_performance_with_large_dataset() {
let fixture =
TestFixture::with_large_data(1000, 3.0).expect("Failed to create large data fixture");
let start = std::time::Instant::now();
fixture.assert_query_succeeds("MATCH (n:PerfNode) RETURN count(n) as count");
let duration = start.elapsed();
assert!(
duration.as_secs() < 5,
"Query should complete within 5 seconds"
);
let start = std::time::Instant::now();
fixture.assert_query_succeeds(
"MATCH (n1:PerfNode)-[e:PERF_EDGE]->(n2:PerfNode)
RETURN n1.category, count(e) as edge_count
GROUP BY n1.category
ORDER BY edge_count DESC",
);
let duration = start.elapsed();
assert!(
duration.as_secs() < 10,
"Aggregation should complete within 10 seconds"
);
}