#[path = "testutils/mod.rs"]
mod testutils;
use graphlite::Value;
use std::collections::HashMap;
use testutils::test_fixture::{FixtureType, TestCase, TestFixture, TestSuite};
#[test]
fn test_basic_match_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_basic_match_operations")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds("MATCH (n) RETURN count(n) as total_nodes");
assert_eq!(result.rows.len(), 1);
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 ()-[r:Transaction]->() RETURN count(r) as transaction_count",
"transaction_count",
Value::Number(100.0), );
fixture.assert_first_value(
"MATCH ()-[r:Purchase]->() RETURN count(r) as purchase_count",
"purchase_count",
Value::Number(50.0), );
fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
RETURN count(*) as pattern_count",
);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[r]-(m:Merchant)
RETURN count(r) as bidirectional_count",
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_where_clause_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_where_clause_operations")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.balance > 1000
RETURN count(a) as high_balance_accounts",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.balance > 500 AND a.status = 'active'
RETURN count(a) as filtered_accounts",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.status = 'active' OR a.balance > 2000
RETURN count(a) as standard_accounts",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE NOT a.status = 'inactive'
RETURN count(a) as active_accounts",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (m:Merchant)
WHERE m.category = 'retail'
RETURN count(m) as selected_merchants",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH ()-[t:Transaction]->()
WHERE t.amount >= 50 AND t.amount <= 500
RETURN count(t) as mid_range_transactions",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 50
RETURN a.id, m.name, t.amount
ORDER BY t.amount DESC
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[:Transaction]->(m:Merchant)
WHERE m.category = 'retail'
RETURN count(a) as shoppers",
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_return_clause_variations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_return_clause_variations")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.balance > 1000
RETURN a.id, a.balance, a.status
LIMIT 5",
);
assert!(result.rows.len() <= 5);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
RETURN a.id as account_num,
a.balance as current_balance,
a.status as account_status
LIMIT 10",
);
assert!(result.rows.len() <= 10);
assert!(result.rows[0].values.contains_key("account_num"));
assert!(result.rows[0].values.contains_key("current_balance"));
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
RETURN DISTINCT a.status as account_status",
);
assert!(result.rows.len() <= 2);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
RETURN a.balance as current_balance,
a.id as account_id,
a.status as account_status
LIMIT 5",
);
assert!(result.rows.len() <= 5);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
RETURN a.id,
a.balance
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->()
RETURN a.id,
count(t) as transaction_count,
avg(t.amount) as avg_amount,
sum(t.amount) as total_spent
GROUP BY a.id
ORDER BY total_spent DESC
LIMIT 10",
);
assert!(result.rows.len() <= 10);
}
#[test]
fn test_select_statement_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_select_statement_operations")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds(
"SELECT count(*) as total_nodes
FROM MATCH (n)",
);
assert_eq!(result.rows.len(), 1);
let result = fixture.assert_query_succeeds(
"SELECT a.id, a.balance
FROM MATCH (a:Account)
WHERE a.balance > 1000
ORDER BY a.balance DESC
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result = fixture.assert_query_succeeds(
"SELECT DISTINCT m.category
FROM MATCH (m:Merchant)
ORDER BY m.category",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"SELECT a.id, m.name, t.amount
FROM MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 10
ORDER BY t.amount DESC
LIMIT 20",
);
assert!(result.rows.len() <= 20);
let result = fixture.assert_query_succeeds(
"SELECT m.category, count(*) as merchant_count
FROM MATCH (m:Merchant)
GROUP BY m.category
ORDER BY merchant_count DESC",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"SELECT a.status, count(*) as account_count
FROM MATCH (a:Account)
GROUP BY a.status
HAVING count(*) > 5
ORDER BY account_count DESC",
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_call_statement_operations() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_call_statement_operations")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result = fixture.assert_query_succeeds("CALL gql.list_schemas()");
assert!(!result.rows.is_empty(), "Should have at least one schema");
let result = fixture.assert_query_succeeds("CALL gql.list_graphs()");
assert!(!result.rows.is_empty(), "Should have at least one graph");
let graph_name = result.rows[0]
.values
.get("graph_name")
.expect("Should have graph_name column");
assert!(
!matches!(graph_name, Value::Null),
"graph_name should not be NULL, got: {:?}",
graph_name
);
let schema_name = result.rows[0]
.values
.get("schema_name")
.expect("Should have schema_name column");
assert!(
!matches!(schema_name, Value::Null),
"schema_name should not be NULL, got: {:?}",
schema_name
);
if let Value::String(name) = graph_name {
assert!(!name.is_empty(), "Graph name should not be empty");
} else {
panic!("graph_name should be a String, got: {:?}", graph_name);
}
}
#[test]
fn test_dql_data_driven_cases() {
let test_suite = TestSuite {
name: "DQL Operations Test Suite".to_string(),
fixture_type: FixtureType::Fraud,
test_cases: vec![
TestCase {
name: "match_all_nodes".to_string(),
description: "Match 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: "match_with_label".to_string(),
description: "Match nodes with specific label".to_string(),
query: "MATCH (a:Account) RETURN count(a) as accounts".to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([(
"accounts".to_string(),
Value::Number(50.0),
)])),
expected_error: None,
},
TestCase {
name: "match_relationships".to_string(),
description: "Match relationships".to_string(),
query: "MATCH ()-[r:Transaction]->() RETURN count(r) as transactions".to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([(
"transactions".to_string(),
Value::Number(100.0),
)])),
expected_error: None,
},
TestCase {
name: "where_property_filter".to_string(),
description: "Filter nodes by property value".to_string(),
query:
"MATCH (a:Account) WHERE a.status = 'active' RETURN count(a) as active_accounts"
.to_string(),
expected_rows: Some(1),
expected_values: Some(HashMap::from([(
"active_accounts".to_string(),
Value::Number(50.0),
)])),
expected_error: None,
},
TestCase {
name: "where_range_filter".to_string(),
description: "Filter with range conditions".to_string(),
query:
"MATCH ()-[t:Transaction]->() WHERE t.amount > 50 RETURN count(t) as high_value"
.to_string(),
expected_rows: Some(1),
expected_values: None, expected_error: None,
},
TestCase {
name: "return_distinct".to_string(),
description: "Return distinct values".to_string(),
query: "MATCH (m:Merchant) RETURN DISTINCT m.category".to_string(),
expected_rows: Some(1), expected_values: None,
expected_error: None,
},
TestCase {
name: "select_with_where".to_string(),
description: "SELECT statement with WHERE clause".to_string(),
query: "SELECT count(*) as count FROM MATCH (a:Account) WHERE a.balance > 1000"
.to_string(),
expected_rows: Some(1),
expected_values: None,
expected_error: None,
},
TestCase {
name: "call_list_schemas".to_string(),
description: "Call system procedure to list schemas".to_string(),
query: "CALL gql.list_schemas()".to_string(),
expected_rows: None, expected_values: None,
expected_error: None,
},
],
};
let results = test_suite.run().expect("Failed to run DQL test suite");
results.print_summary();
assert!(
results.passed >= 4,
"Should have at least 4 passing DQL tests"
);
}
#[test]
fn test_complex_query_patterns() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_complex_query_patterns")
.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)
WHERE t.amount > 50
RETURN a.id, m.name, t.amount
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 100
RETURN a.id, count(t) as transaction_count
GROUP BY a.id
LIMIT 20",
);
assert!(result.rows.len() <= 20);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[r]->(m:Merchant)
WHERE a.balance > 2000
RETURN a.id, count(r) as total_relationships
GROUP BY a.id
LIMIT 15",
);
assert!(result.rows.len() <= 15);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 100
RETURN a.id, a.balance, t.amount
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 200
RETURN a.id, t.amount
LIMIT 10",
);
assert!(result.rows.len() <= 10);
let result2 = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.balance > 3000
RETURN a.id, a.balance
LIMIT 10",
);
assert!(result2.rows.len() <= 10);
}
#[test]
fn test_performance_queries() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_performance_queries")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let start = std::time::Instant::now();
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
RETURN m.category,
count(t) as transaction_count,
sum(t.amount) as total_amount,
avg(t.amount) as avg_amount,
min(t.amount) as min_amount,
max(t.amount) as max_amount
GROUP BY m.category
ORDER BY total_amount DESC",
);
let duration = start.elapsed();
assert!(
duration.as_secs() < 10,
"Complex query should complete within 10 seconds"
);
assert!(!result.rows.is_empty());
let start = std::time::Instant::now();
fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.id = '4000000001'
RETURN a.*",
);
let duration = start.elapsed();
assert!(
duration.as_millis() < 100,
"Index lookup should be very fast"
);
let start = std::time::Instant::now();
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
RETURN a.id, m.name, t.amount, t.timestamp
ORDER BY t.amount DESC",
);
let duration = start.elapsed();
assert!(
duration.as_secs() < 15,
"Large result set query should complete within 15 seconds"
);
}
#[test]
fn test_dql_edge_cases() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_dql_edge_cases")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let result =
fixture.assert_query_succeeds("MATCH (n:NonExistentLabel) RETURN count(n) as count");
assert_eq!(result.rows.len(), 1);
assert_eq!(
result.rows[0].values.get("count").unwrap(),
&Value::Number(0.0)
);
fixture.assert_query_succeeds("INSERT (test_null:NullTest {prop1: 'value', prop2: null})");
let result = fixture.assert_query_succeeds(
"MATCH (n:NullTest)
RETURN n.prop1 as existing_prop,
n.prop2 as null_prop,
n.non_existent as missing_prop",
);
assert_eq!(result.rows.len(), 1);
assert_eq!(
result.rows[0].values.get("null_prop").unwrap(),
&Value::Null
);
fixture.assert_query_succeeds("INSERT (big_number:BigNum {value: 999999999999999})");
fixture.assert_query_succeeds(
"MATCH (n:BigNum)
WHERE n.value > 999999999999998
RETURN count(n) as count",
);
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE a.id STARTS WITH '400000000'
RETURN count(a) as matching_accounts",
);
assert!(!result.rows.is_empty());
let result = fixture.assert_query_succeeds(
"MATCH (a:Account)
WHERE (a.balance > 1000 AND a.status = 'active')
OR (a.balance > 5000 AND a.status = 'inactive')
OR (a.balance > 4000)
RETURN count(a) as complex_condition_matches",
);
assert!(!result.rows.is_empty());
}
#[test]
fn test_query_optimization() {
let fixture = TestFixture::new().expect("Failed to create test fixture");
fixture
.setup_graph("test_query_optimization")
.expect("Failed to setup graph");
fixture
.insert_fraud_data()
.expect("Failed to insert fraud data");
let start = std::time::Instant::now();
fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE a.id = '4000001000' // Very selective
AND t.amount > 1000
RETURN count(*) as result_count",
);
let selective_duration = start.elapsed();
let start = std::time::Instant::now();
fixture.assert_query_succeeds(
"MATCH (a:Account)-[t:Transaction]->(m:Merchant)
WHERE t.amount > 1000 // Less selective
AND a.id = '4000001000'
RETURN count(*) as result_count",
);
let less_selective_duration = start.elapsed();
assert!(
selective_duration.as_secs() < 5,
"Selective query should be fast"
);
assert!(
less_selective_duration.as_secs() < 5,
"Less selective query should still be reasonable"
);
}