#![allow(clippy::unwrap_used)]
use fraiseql_core::{
db::{
projection_generator::{
MySqlProjectionGenerator, PostgresProjectionGenerator, SqliteProjectionGenerator,
},
types::{DatabaseType, JsonbValue},
},
runtime::ResultProjector,
schema::SqlProjectionHint,
};
use serde_json::json;
#[test]
fn test_sql_projection_hint_creation() {
let hint = SqlProjectionHint::new(
DatabaseType::PostgreSQL,
"jsonb_build_object('id', \"data\"->>'id', 'name', \"data\"->>'name')".to_string(),
65,
);
assert_eq!(hint.database, DatabaseType::PostgreSQL);
assert_eq!(hint.estimated_reduction_percent, 65);
assert!(hint.projection_template.contains("jsonb_build_object"));
}
#[test]
fn test_postgres_projection_complete_flow() {
let generator = PostgresProjectionGenerator::new();
let fields = vec!["id".to_string(), "name".to_string(), "email".to_string()];
let sql = generator.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("jsonb_build_object("));
assert!(sql.contains("'id'"));
assert!(sql.contains("'name'"));
assert!(sql.contains("'email'"));
assert!(sql.contains("\"data\""));
let select = generator.generate_select_clause("users", &fields).unwrap();
assert!(select.starts_with("SELECT jsonb_build_object("));
assert!(select.contains("FROM \"users\""));
}
#[test]
fn test_mysql_projection_complete_flow() {
let generator = MySqlProjectionGenerator::new();
let fields = vec!["id".to_string(), "email".to_string()];
let sql = generator.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("JSON_OBJECT("));
assert!(sql.contains("JSON_EXTRACT("));
assert!(sql.contains("'id'"));
assert!(sql.contains("'email'"));
assert!(sql.contains("`data`"));
}
#[test]
fn test_sqlite_projection_complete_flow() {
let generator = SqliteProjectionGenerator::new();
let fields = vec!["id".to_string(), "active".to_string()];
let sql = generator.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("json_object("));
assert!(sql.contains("json_extract("));
assert!(sql.contains("'id'"));
assert!(sql.contains("'active'"));
assert!(sql.contains("\"data\""));
}
#[test]
fn test_result_projector_add_typename() {
let projector = ResultProjector::new(vec!["id".to_string()]);
let data = json!({
"id": "123",
"name": "Alice",
"email": "alice@example.com"
});
let jsonb = JsonbValue::new(data);
let result = projector.add_typename_only(&jsonb, "User").unwrap();
assert_eq!(result.get("__typename"), Some(&json!("User")));
assert_eq!(result.get("id"), Some(&json!("123")));
assert_eq!(result.get("name"), Some(&json!("Alice")));
}
#[test]
fn test_result_projector_add_typename_array() {
let projector = ResultProjector::new(vec![]);
let data = json!([
{"id": "1", "name": "Alice"},
{"id": "2", "name": "Bob"}
]);
let jsonb = JsonbValue::new(data);
let result = projector.add_typename_only(&jsonb, "User").unwrap();
assert!(result.is_array());
let arr = result.as_array().unwrap();
assert_eq!(arr.len(), 2);
assert_eq!(arr[0].get("__typename"), Some(&json!("User")));
assert_eq!(arr[1].get("__typename"), Some(&json!("User")));
}
#[test]
fn test_result_projector_projects_single_field() {
let projector = ResultProjector::new(vec!["id".to_string()]);
let data = json!({
"id": "123",
"name": "Alice",
"email": "alice@example.com"
});
let jsonb = JsonbValue::new(data);
let projected = projector.project_results(&[jsonb], false).unwrap();
assert_eq!(projected.get("id"), Some(&json!("123")));
assert_eq!(projected.get("name"), None);
assert_eq!(projected.get("email"), None);
}
#[test]
fn test_result_projector_projects_multiple_fields() {
let projector = ResultProjector::new(vec!["id".to_string(), "email".to_string()]);
let data = json!({
"id": "123",
"name": "Alice",
"email": "alice@example.com",
"phone": "+1234567890"
});
let jsonb = JsonbValue::new(data);
let projected = projector.project_results(&[jsonb], false).unwrap();
assert_eq!(projected.get("id"), Some(&json!("123")));
assert_eq!(projected.get("email"), Some(&json!("alice@example.com")));
assert_eq!(projected.get("name"), None);
assert_eq!(projected.get("phone"), None);
}
#[test]
fn test_projection_custom_column_names() {
let pg = PostgresProjectionGenerator::with_column("metadata");
let mysql = MySqlProjectionGenerator::with_column("metadata");
let sqlite = SqliteProjectionGenerator::with_column("metadata");
let fields = vec!["id".to_string()];
let pg_sql = pg.generate_projection_sql(&fields).unwrap();
let mysql_sql = mysql.generate_projection_sql(&fields).unwrap();
let sqlite_sql = sqlite.generate_projection_sql(&fields).unwrap();
assert!(pg_sql.contains("\"metadata\""));
assert!(mysql_sql.contains("`metadata`"));
assert!(sqlite_sql.contains("\"metadata\""));
}
#[test]
fn test_projection_empty_fields_passthrough() {
let pg = PostgresProjectionGenerator::new();
let mysql = MySqlProjectionGenerator::new();
let sqlite = SqliteProjectionGenerator::new();
let empty_fields: Vec<String> = vec![];
let pg_sql = pg.generate_projection_sql(&empty_fields).unwrap();
let mysql_sql = mysql.generate_projection_sql(&empty_fields).unwrap();
let sqlite_sql = sqlite.generate_projection_sql(&empty_fields).unwrap();
assert_eq!(pg_sql, "\"data\"");
assert_eq!(mysql_sql, "`data`");
assert_eq!(sqlite_sql, "\"data\"");
}
#[test]
fn test_identifier_handling() {
let pg = PostgresProjectionGenerator::new();
let fields = vec![
"id".to_string(),
"user_id".to_string(),
"field123".to_string(),
];
let sql = pg.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("'id'"));
assert!(sql.contains("'user_id'"));
assert!(sql.contains("'field123'"));
}
#[test]
fn test_projection_field_escaping() {
let pg = PostgresProjectionGenerator::new();
let fields = vec!["user_id".to_string(), "data$obj".to_string()];
let err = pg.generate_projection_sql(&fields).unwrap_err();
assert!(
err.to_string().contains("cannot be safely projected"),
"expected validation error for '$', got: {err}"
);
let fields = vec!["user_id".to_string(), "data_obj".to_string()];
let sql = pg.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("'user_id'"));
assert!(sql.contains("'data_obj'"));
}
#[test]
fn test_projection_hint_reduction_calculation() {
let hint = SqlProjectionHint::new(
DatabaseType::PostgreSQL,
"jsonb_build_object('id', data->>'id', 'name', data->>'name', 'email', data->>'email', 'status', data->>'status', 'created_at', data->>'created_at')".to_string(),
75, );
assert_eq!(hint.estimated_reduction_percent, 75);
assert_eq!(hint.database, DatabaseType::PostgreSQL);
}
#[test]
fn test_result_projector_data_envelope() {
let result = json!([
{"id": "1", "name": "Alice"},
{"id": "2", "name": "Bob"}
]);
let wrapped = ResultProjector::wrap_in_data_envelope(result, "users");
assert!(wrapped.get("data").is_some());
let data_obj = wrapped.get("data").unwrap();
assert!(data_obj.get("users").is_some());
let users = data_obj.get("users").unwrap();
assert!(users.is_array());
assert_eq!(users.as_array().unwrap().len(), 2);
}
#[test]
fn test_result_projector_error_envelope() {
use fraiseql_core::error::FraiseQLError;
let error = FraiseQLError::Validation {
message: "Invalid field selection".to_string(),
path: Some("query.users".to_string()),
};
let wrapped = ResultProjector::wrap_error(&error);
assert!(wrapped.get("errors").is_some());
assert!(wrapped.get("data").is_none());
let errors = wrapped.get("errors").unwrap();
assert!(errors.is_array());
assert_eq!(errors.as_array().unwrap().len(), 1);
let error_obj = &errors.as_array().unwrap()[0];
assert!(error_obj.get("message").is_some());
}
#[test]
fn test_projection_with_nested_structure() {
let projector = ResultProjector::new(vec!["id".to_string(), "profile".to_string()]);
let data = json!({
"id": "123",
"profile": {
"name": "Alice",
"email": "alice@example.com"
},
"settings": {
"theme": "dark"
}
});
let jsonb = JsonbValue::new(data);
let projected = projector.project_results(&[jsonb], false).unwrap();
assert_eq!(projected.get("id"), Some(&json!("123")));
assert!(projected.get("profile").is_some());
assert_eq!(projected.get("settings"), None);
}
#[test]
fn test_complete_projection_pipeline() {
let _hint = SqlProjectionHint::new(
DatabaseType::PostgreSQL,
"jsonb_build_object('id', \"data\"->>'id', 'name', \"data\"->>'name')".to_string(),
87,
);
let fields = vec!["id".to_string(), "name".to_string()];
let generator = PostgresProjectionGenerator::new();
let sql = generator.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("jsonb_build_object"));
let result_data = json!({
"id": "123",
"name": "Alice"
});
let projector = ResultProjector::new(fields);
let jsonb = JsonbValue::new(result_data);
let with_typename = projector.add_typename_only(&jsonb, "User").unwrap();
let final_response = ResultProjector::wrap_in_data_envelope(with_typename, "user");
assert!(final_response.get("data").is_some());
let data = final_response.get("data").unwrap();
let user = data.get("user").unwrap();
assert_eq!(user.get("id"), Some(&json!("123")));
assert_eq!(user.get("name"), Some(&json!("Alice")));
assert_eq!(user.get("__typename"), Some(&json!("User")));
}
#[test]
fn test_projection_with_many_fields() {
let generator = PostgresProjectionGenerator::new();
let fields: Vec<String> = (0..50).map(|i| format!("field_{}", i)).collect();
let sql = generator.generate_projection_sql(&fields).unwrap();
assert!(sql.contains("jsonb_build_object("));
for field in &fields {
assert!(sql.contains(&format!("'{}'", field)));
}
}
#[test]
fn test_database_specific_syntax() {
let fields = vec!["id".to_string(), "status".to_string()];
let pg_sql = PostgresProjectionGenerator::new().generate_projection_sql(&fields).unwrap();
let mysql_sql = MySqlProjectionGenerator::new().generate_projection_sql(&fields).unwrap();
let sqlite_sql = SqliteProjectionGenerator::new().generate_projection_sql(&fields).unwrap();
assert!(pg_sql.contains("->>'"));
assert!(mysql_sql.contains("JSON_EXTRACT"));
assert!(sqlite_sql.contains("json_extract"));
}
#[test]
fn test_result_projector_list_vs_single() {
let projector = ResultProjector::new(vec!["id".to_string()]);
let data = json!({"id": "1", "name": "Alice"});
let jsonb = JsonbValue::new(data);
let list_result = projector.project_results(std::slice::from_ref(&jsonb), true).unwrap();
assert!(list_result.is_array());
assert_eq!(list_result.as_array().unwrap().len(), 1);
let single_result = projector.project_results(std::slice::from_ref(&jsonb), false).unwrap();
assert!(single_result.is_object());
assert_eq!(single_result.get("id"), Some(&json!("1")));
}
#[test]
fn test_projection_with_empty_results() {
let projector = ResultProjector::new(vec!["id".to_string()]);
let list_result = projector.project_results(&[], true).unwrap();
assert!(list_result.is_array());
assert_eq!(list_result.as_array().unwrap().len(), 0);
let single_result = projector.project_results(&[], false).unwrap();
assert_eq!(single_result, json!(null));
}