#![allow(clippy::unwrap_used)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_errors_doc)] #![allow(missing_docs)]
use std::{
collections::HashMap,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
};
use async_trait::async_trait;
use fraiseql_core::{
cache::{CacheConfig, CachedDatabaseAdapter, QueryResultCache},
db::{
DatabaseAdapter, DatabaseType, SupportsMutations, WhereClause,
types::{JsonbValue, OrderByClause, PoolMetrics},
},
error::Result as FraiseQLResult,
schema::{CompiledSchema, SqlProjectionHint},
};
use fraiseql_server::{Server, server_config::ServerConfig};
#[derive(Debug)]
struct CountingAdapter {
call_count: Arc<AtomicU64>,
}
impl CountingAdapter {
fn new() -> (Self, Arc<AtomicU64>) {
let counter = Arc::new(AtomicU64::new(0));
(
Self {
call_count: Arc::clone(&counter),
},
counter,
)
}
}
impl Clone for CountingAdapter {
fn clone(&self) -> Self {
Self {
call_count: Arc::clone(&self.call_count),
}
}
}
#[async_trait]
impl DatabaseAdapter for CountingAdapter {
async fn execute_where_query(
&self,
_view: &str,
_where_clause: Option<&WhereClause>,
_limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> FraiseQLResult<Vec<JsonbValue>> {
self.call_count.fetch_add(1, Ordering::SeqCst);
Ok(vec![JsonbValue::new(
serde_json::json!({"id": 1, "name": "test"}),
)])
}
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]>,
) -> FraiseQLResult<Vec<JsonbValue>> {
self.call_count.fetch_add(1, Ordering::SeqCst);
Ok(vec![JsonbValue::new(
serde_json::json!({"id": 1, "name": "test"}),
)])
}
fn database_type(&self) -> DatabaseType {
DatabaseType::SQLite
}
async fn health_check(&self) -> FraiseQLResult<()> {
Ok(())
}
fn pool_metrics(&self) -> PoolMetrics {
PoolMetrics::default()
}
async fn execute_raw_query(
&self,
_sql: &str,
) -> FraiseQLResult<Vec<HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
async fn execute_parameterized_aggregate(
&self,
_sql: &str,
_params: &[serde_json::Value],
) -> FraiseQLResult<Vec<HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
}
impl SupportsMutations for CountingAdapter {}
#[tokio::test]
async fn test_cached_adapter_cache_hit_on_second_query() {
let (inner, counter) = CountingAdapter::new();
let cache = QueryResultCache::new(CacheConfig::enabled());
let adapter = CachedDatabaseAdapter::new(inner, cache, "test-schema-v1".to_string());
let _ = adapter.execute_where_query("v_item", None, None, None, None).await.unwrap();
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"first call must reach the underlying adapter"
);
let _ = adapter.execute_where_query("v_item", None, None, None, None).await.unwrap();
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"second identical call must be served from cache (adapter count unchanged)"
);
}
#[tokio::test]
async fn test_cached_adapter_disabled_is_passthrough() {
let (inner, counter) = CountingAdapter::new();
let cache = QueryResultCache::new(CacheConfig::disabled());
let adapter = CachedDatabaseAdapter::new(inner, cache, "test-schema-v1".to_string());
let _ = adapter.execute_where_query("v_item", None, None, None, None).await.unwrap();
assert_eq!(
counter.load(Ordering::SeqCst),
1,
"first call must reach the underlying adapter"
);
let _ = adapter.execute_where_query("v_item", None, None, None, None).await.unwrap();
assert_eq!(
counter.load(Ordering::SeqCst),
2,
"second call must also reach the adapter when cache is disabled"
);
}
#[tokio::test]
async fn test_server_new_wraps_adapter_successfully() {
let (adapter, _counter) = CountingAdapter::new();
let mut schema = CompiledSchema::default();
schema
.queries
.push(fraiseql_core::schema::QueryDefinition::new("items", "Item"));
let config = ServerConfig {
cache_enabled: true,
..ServerConfig::default()
};
let _server = Server::new(config, schema, Arc::new(adapter), None)
.await
.expect("Server::new must succeed when adapter satisfies bounds");
}
#[tokio::test]
async fn test_server_new_cache_disabled_also_builds() {
let (adapter, _counter) = CountingAdapter::new();
let schema = CompiledSchema::default();
let config = ServerConfig {
cache_enabled: false,
..ServerConfig::default()
};
let _server = Server::new(config, schema, Arc::new(adapter), None)
.await
.expect("Server::new must succeed when cache_enabled = false");
}