#![allow(clippy::unwrap_used)]
#![allow(clippy::used_underscore_binding)] use std::collections::HashMap;
use async_trait::async_trait;
use fraiseql_core::{
db::{
traits::{DatabaseAdapter, SupportsMutations},
types::{DatabaseType, JsonbValue, OrderByClause, PoolMetrics},
where_clause::WhereClause,
},
error::{FraiseQLError, Result},
runtime::ResultProjector,
schema::SqlProjectionHint,
};
use serde_json::json;
struct MockDatabaseAdapter {
tables: HashMap<String, Vec<JsonbValue>>,
}
impl MockDatabaseAdapter {
fn with_sample_data() -> Self {
let mut tables = HashMap::new();
let users = vec![
JsonbValue::new(json!({
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "Alice Johnson",
"email": "alice@example.com",
"status": "active",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z",
"metadata": {
"last_login": "2024-01-14T15:30:00Z",
"login_count": 42
}
})),
JsonbValue::new(json!({
"id": "223e4567-e89b-12d3-a456-426614174001",
"name": "Bob Smith",
"email": "bob@example.com",
"status": "active",
"created_at": "2024-01-10T09:30:00Z",
"updated_at": "2024-01-14T11:00:00Z",
"metadata": {
"last_login": "2024-01-13T20:15:00Z",
"login_count": 87
}
})),
JsonbValue::new(json!({
"id": "323e4567-e89b-12d3-a456-426614174002",
"name": "Carol Davis",
"email": "carol@example.com",
"status": "inactive",
"created_at": "2023-12-01T14:45:00Z",
"updated_at": "2024-01-05T08:20:00Z",
"metadata": {
"last_login": "2024-01-01T12:00:00Z",
"login_count": 15
}
})),
];
tables.insert("users".to_string(), users);
let products = vec![
JsonbValue::new(json!({
"id": "prod-001",
"sku": "SKU-001",
"name": "Product A",
"price": 99.99,
"stock": 150,
"category": "Electronics",
"available": true
})),
JsonbValue::new(json!({
"id": "prod-002",
"sku": "SKU-002",
"name": "Product B",
"price": 149.99,
"stock": 75,
"category": "Electronics",
"available": true
})),
JsonbValue::new(json!({
"id": "prod-003",
"sku": "SKU-003",
"name": "Product C",
"price": 49.99,
"stock": 0,
"category": "Accessories",
"available": false
})),
];
tables.insert("products".to_string(), products);
Self { tables }
}
fn get_table(&self, table_name: &str) -> Vec<JsonbValue> {
self.tables.get(table_name).cloned().unwrap_or_default()
}
}
#[async_trait]
impl DatabaseAdapter for MockDatabaseAdapter {
async fn execute_with_projection(
&self,
view: &str,
_projection: Option<&SqlProjectionHint>,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
self.execute_where_query(view, where_clause, limit, None, None).await
}
async fn execute_where_query(
&self,
view: &str,
_where_clause: Option<&WhereClause>,
limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
let mut results = self.get_table(view);
if let Some(limit_val) = limit {
results.truncate(limit_val as usize);
}
Ok(results)
}
async fn health_check(&self) -> Result<()> {
Ok(())
}
fn database_type(&self) -> DatabaseType {
DatabaseType::PostgreSQL
}
fn pool_metrics(&self) -> PoolMetrics {
PoolMetrics {
total_connections: 5,
active_connections: 1,
idle_connections: 4,
waiting_requests: 0,
}
}
async fn execute_raw_query(
&self,
_sql: &str,
) -> Result<Vec<HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
async fn execute_parameterized_aggregate(
&self,
_sql: &str,
_params: &[serde_json::Value],
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
async fn execute_function_call(
&self,
_function_name: &str,
_args: &[serde_json::Value],
) -> Result<Vec<HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
}
impl SupportsMutations for MockDatabaseAdapter {}
#[tokio::test]
async fn test_seed_data_users_available() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
assert_eq!(results.len(), 3);
}
#[tokio::test]
async fn test_seed_data_products_available() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("products", None, None, None, None).await.unwrap();
assert_eq!(results.len(), 3);
}
#[tokio::test]
async fn test_seed_data_contains_correct_fields() {
let adapter = MockDatabaseAdapter::with_sample_data();
let users = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
let user = &users[0];
let user_obj = user.as_value();
assert_eq!(user_obj.get("name"), Some(&json!("Alice Johnson")));
assert_eq!(user_obj.get("email"), Some(&json!("alice@example.com")));
assert_eq!(user_obj.get("status"), Some(&json!("active")));
}
#[tokio::test]
async fn test_query_execution_all_users() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
assert_eq!(results.len(), 3);
}
#[tokio::test]
async fn test_query_execution_with_limit() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(2), None, None).await.unwrap();
assert_eq!(results.len(), 2);
}
#[tokio::test]
async fn test_query_execution_products() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("products", None, None, None, None).await.unwrap();
assert_eq!(results.len(), 3);
}
#[tokio::test]
async fn test_result_projection_single_field() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
let projector = ResultProjector::new(vec!["id".to_string(), "name".to_string()]);
let projected = projector.project_results(&results, true).unwrap();
let arr = projected.as_array().unwrap();
assert_eq!(arr.len(), 3);
let first = &arr[0];
assert!(first.get("id").is_some());
assert!(first.get("name").is_some());
assert_eq!(first.get("email"), None);
assert_eq!(first.get("status"), None);
}
#[tokio::test]
async fn test_result_projection_multiple_fields() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
let projector =
ResultProjector::new(vec!["id".to_string(), "name".to_string(), "email".to_string()]);
let projected = projector.project_results(&results, false).unwrap();
assert!(projected.get("id").is_some());
assert!(projected.get("name").is_some());
assert!(projected.get("email").is_some());
assert_eq!(projected.get("status"), None);
}
#[tokio::test]
async fn test_result_projection_products() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("products", None, None, None, None).await.unwrap();
let projector = ResultProjector::new(vec!["name".to_string(), "price".to_string()]);
let projected = projector.project_results(&results, true).unwrap();
let arr = projected.as_array().unwrap();
assert_eq!(arr.len(), 3);
for item in arr {
assert!(item.get("name").is_some());
assert!(item.get("price").is_some());
assert_eq!(item.get("sku"), None);
assert_eq!(item.get("category"), None);
}
}
#[tokio::test]
async fn test_graphql_response_data_envelope() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
let projector = ResultProjector::new(vec!["id".to_string(), "name".to_string()]);
let projected = projector.project_results(&results, false).unwrap();
let response = ResultProjector::wrap_in_data_envelope(projected, "user");
assert!(response.get("data").is_some());
let data = response.get("data").unwrap();
assert!(data.get("user").is_some());
}
#[tokio::test]
async fn test_graphql_response_with_typename() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
let projector = ResultProjector::new(vec!["id".to_string(), "name".to_string()]);
let _projected = projector.project_results(&results, false).unwrap();
let with_typename = projector.add_typename_only(&results[0], "User").unwrap();
assert_eq!(with_typename.get("__typename"), Some(&json!("User")));
}
#[tokio::test]
async fn test_graphql_response_list_with_typename() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
let projector = ResultProjector::new(vec!["id".to_string()]);
let with_typename = projector.add_typename_only(&results[0], "User").unwrap();
assert_eq!(with_typename.get("__typename"), Some(&json!("User")));
}
#[tokio::test]
async fn test_graphql_error_response() {
let error = FraiseQLError::Validation {
message: "Invalid query field".to_string(),
path: Some("query.user.invalidField".to_string()),
};
let response = ResultProjector::wrap_error(&error);
assert!(response.get("errors").is_some());
assert_eq!(response.get("data"), None);
let errors = response.get("errors").unwrap().as_array().unwrap();
assert_eq!(errors.len(), 1);
assert!(errors[0].get("message").is_some());
}
#[tokio::test]
async fn test_complete_e2e_pipeline_single_user() {
let adapter = MockDatabaseAdapter::with_sample_data();
let db_results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
assert_eq!(db_results.len(), 1);
let projector =
ResultProjector::new(vec!["id".to_string(), "name".to_string(), "email".to_string()]);
let projected = projector.project_results(&db_results, false).unwrap();
assert!(projected.get("id").is_some());
assert!(projected.get("name").is_some());
assert!(projected.get("email").is_some());
assert_eq!(projected.get("status"), None);
let with_typename = projector.add_typename_only(&db_results[0], "User").unwrap();
let response = ResultProjector::wrap_in_data_envelope(with_typename, "user");
assert!(response.get("data").is_some());
let data = response.get("data").unwrap();
let user = data.get("user").unwrap();
assert_eq!(user.get("__typename"), Some(&json!("User")));
}
#[tokio::test]
async fn test_complete_e2e_pipeline_user_list() {
let adapter = MockDatabaseAdapter::with_sample_data();
let db_results = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
assert_eq!(db_results.len(), 3);
let projector = ResultProjector::new(vec!["id".to_string(), "name".to_string()]);
let projected = projector.project_results(&db_results, true).unwrap();
let arr = projected.as_array().unwrap();
assert_eq!(arr.len(), 3);
let response = ResultProjector::wrap_in_data_envelope(projected, "users");
assert!(response.get("data").is_some());
let data = response.get("data").unwrap();
let users = data.get("users").unwrap().as_array().unwrap();
assert_eq!(users.len(), 3);
}
#[tokio::test]
async fn test_complete_e2e_pipeline_products() {
let adapter = MockDatabaseAdapter::with_sample_data();
let db_results = adapter.execute_where_query("products", None, None, None, None).await.unwrap();
let projector =
ResultProjector::new(vec!["id".to_string(), "name".to_string(), "price".to_string()]);
let projected = projector.project_results(&db_results, true).unwrap();
let arr = projected.as_array().unwrap();
for item in arr {
assert!(item.get("id").is_some());
assert!(item.get("name").is_some());
assert!(item.get("price").is_some());
assert_eq!(item.get("sku"), None);
}
let response = ResultProjector::wrap_in_data_envelope(projected, "products");
assert!(response.get("data").is_some());
}
#[tokio::test]
async fn test_empty_projection_fields() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
let projector = ResultProjector::new(vec![]);
let _projected = projector.project_results(&results, false).unwrap();
assert!(_projected.is_object());
}
#[tokio::test]
async fn test_projection_nonexistent_fields() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(1), None, None).await.unwrap();
let projector = ResultProjector::new(vec![
"nonexistent_field".to_string(),
"another_missing".to_string(),
]);
let projected = projector.project_results(&results, false).unwrap();
assert!(projected.is_object());
assert_eq!(projected.as_object().unwrap().len(), 0);
}
#[tokio::test]
async fn test_query_with_zero_limit() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter.execute_where_query("users", None, Some(0), None, None).await.unwrap();
assert_eq!(results.len(), 0);
}
#[tokio::test]
async fn test_large_limit() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results = adapter
.execute_where_query("users", None, Some(1000), None, None)
.await
.unwrap();
assert_eq!(results.len(), 3);
}
#[tokio::test]
async fn test_seed_data_not_mutated() {
let adapter = MockDatabaseAdapter::with_sample_data();
let results1 = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
let results2 = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
assert_eq!(results1.len(), results2.len());
for (r1, r2) in results1.iter().zip(results2.iter()) {
assert_eq!(r1.as_value(), r2.as_value());
}
}
#[tokio::test]
async fn test_different_tables_independent() {
let adapter = MockDatabaseAdapter::with_sample_data();
let users = adapter.execute_where_query("users", None, None, None, None).await.unwrap();
let products = adapter.execute_where_query("products", None, None, None, None).await.unwrap();
assert_eq!(users.len(), 3);
assert_eq!(products.len(), 3);
assert!(users[0].as_value().get("email").is_some());
assert!(products[0].as_value().get("sku").is_some());
}