#![allow(clippy::unwrap_used)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_errors_doc)]
use fraiseql_core::{
cache::{extract_accessed_views, generate_cache_key},
db::{WhereClause, WhereOperator},
schema::QueryDefinition,
};
use serde_json::json;
const FIXED_QUERY: &str = "query { users { id } }";
const FIXED_VERSION: &str = "schema-v42";
fn fixed_vars() -> serde_json::Value {
json!({"limit": 5})
}
fn simple_where() -> WhereClause {
WhereClause::Field {
path: vec!["status".to_string()],
operator: WhereOperator::Eq,
value: json!("active"),
}
}
fn other_where() -> WhereClause {
WhereClause::Field {
path: vec!["status".to_string()],
operator: WhereOperator::Eq,
value: json!("inactive"),
}
}
#[test]
fn where_clause_component_contributes_independently() {
let vars = fixed_vars();
let key_none = generate_cache_key(FIXED_QUERY, &vars, None, FIXED_VERSION);
let key_some = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), FIXED_VERSION);
let key_other = generate_cache_key(FIXED_QUERY, &vars, Some(&other_where()), FIXED_VERSION);
assert_ne!(key_none, key_some, "M1: WHERE clause must contribute to key");
assert_ne!(key_some, key_other, "M1: Different WHERE values must produce different keys");
assert_ne!(key_none, key_other, "M1: Absent vs present WHERE must differ");
}
#[test]
fn schema_version_component_contributes_independently() {
let vars = fixed_vars();
let k_v1 = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), "schema-v1");
let k_v2 = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), "schema-v2");
let k_v3 = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), "schema-v3");
assert_ne!(k_v1, k_v2, "M2: Schema version must contribute to key (v1 vs v2)");
assert_ne!(k_v2, k_v3, "M2: Schema version must contribute to key (v2 vs v3)");
assert_ne!(k_v1, k_v3, "M2: Schema version must contribute to key (v1 vs v3)");
}
#[test]
fn query_component_contributes_independently() {
let key_users = generate_cache_key("query { users { id } }", &json!({}), None, FIXED_VERSION);
let key_posts = generate_cache_key("query { posts { id } }", &json!({}), None, FIXED_VERSION);
assert_ne!(key_users, key_posts, "M3: Query string must contribute to key");
}
#[test]
fn variables_component_contributes_independently() {
let key_alice =
generate_cache_key(FIXED_QUERY, &json!({"userId": "alice"}), None, FIXED_VERSION);
let key_bob = generate_cache_key(FIXED_QUERY, &json!({"userId": "bob"}), None, FIXED_VERSION);
assert_ne!(key_alice, key_bob, "M3: Variable values must contribute to key");
}
#[test]
fn extract_views_includes_additional_views() {
let query_def = QueryDefinition {
name: "usersWithPosts".to_string(),
return_type: "UserWithPosts".to_string(),
returns_list: true,
sql_source: Some("v_user_with_posts".to_string()),
additional_views: vec!["v_post".to_string(), "v_comment".to_string()],
..QueryDefinition::new("usersWithPosts", "UserWithPosts")
};
let views = extract_accessed_views(&query_def);
assert!(views.contains(&"v_post".to_string()), "M4: additional_views must be included");
assert!(
views.contains(&"v_comment".to_string()),
"M4: all additional_views must be included"
);
assert_eq!(views.len(), 3, "M4: primary + 2 additional = 3 views total");
}
#[test]
fn extract_views_includes_primary_sql_source() {
let query_def =
QueryDefinition::new("users", "User").returning_list().with_sql_source("v_user");
let views = extract_accessed_views(&query_def);
assert_eq!(views, vec!["v_user"], "M5: primary sql_source must be in views");
assert_eq!(views.len(), 1, "M5: no phantom entries");
}
#[test]
fn extract_views_with_no_sql_source_returns_empty() {
let query_def = QueryDefinition::new("custom", "Custom");
let views = extract_accessed_views(&query_def);
assert!(views.is_empty(), "M5: no sql_source → empty views");
}
#[test]
fn none_and_some_where_clause_produce_different_keys() {
let k_none = generate_cache_key(FIXED_QUERY, &json!({}), None, "v1");
let k_some = generate_cache_key(FIXED_QUERY, &json!({}), Some(&simple_where()), "v1");
assert_ne!(k_none, k_some, "M6: None WHERE must differ from Some WHERE");
}
#[test]
fn key_is_fully_deterministic_with_all_components() {
let vars = fixed_vars();
let run1 = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), FIXED_VERSION);
let run2 = generate_cache_key(FIXED_QUERY, &vars, Some(&simple_where()), FIXED_VERSION);
assert_eq!(run1, run2, "Key must be deterministic across repeated calls");
}
#[test]
fn single_char_difference_in_where_value_changes_entire_key() {
let w1 = WhereClause::Field {
path: vec!["email".to_string()],
operator: WhereOperator::Eq,
value: json!("alice@example.com"),
};
let w2 = WhereClause::Field {
path: vec!["email".to_string()],
operator: WhereOperator::Eq,
value: json!("blice@example.com"), };
let k1 = generate_cache_key(FIXED_QUERY, &json!({}), Some(&w1), FIXED_VERSION);
let k2 = generate_cache_key(FIXED_QUERY, &json!({}), Some(&w2), FIXED_VERSION);
assert_ne!(k1, k2, "Single-char WHERE difference must change key");
}