#![allow(clippy::unwrap_used)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cast_lossless)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::missing_errors_doc)] #![allow(missing_docs)] #![allow(clippy::items_after_statements)] #![allow(clippy::used_underscore_binding)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::match_same_arms)] #![allow(clippy::branches_sharing_code)] #![allow(clippy::undocumented_unsafe_blocks)]
mod common;
use common::test_app::{api_router, get_json, make_test_state, make_test_state_with, post_json};
use fraiseql_test_utils::{
failing_adapter::FailingAdapter,
schema_builder::{TestQueryBuilder, TestSchemaBuilder},
};
use http::StatusCode;
#[tokio::test]
async fn explain_returns_complexity_for_valid_query() {
let router = api_router(make_test_state());
let (status, json) = post_json(
&router,
"/api/v1/query/explain",
serde_json::json!({ "query": "query { users { id name } }" }),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(json["status"], "success");
assert!(json["data"]["complexity"]["depth"].is_number());
assert!(json["data"]["complexity"]["alias_count"].is_number());
assert!(json["data"]["complexity"]["complexity"].is_number());
assert!(json["data"]["estimated_cost"].as_u64().unwrap() > 0);
}
#[tokio::test]
async fn explain_returns_sql_when_query_matches_schema() {
let schema = TestSchemaBuilder::new()
.with_query(
TestQueryBuilder::new("users", "User")
.returns_list(true)
.with_sql_source("v_user")
.build(),
)
.build();
let state = make_test_state_with(FailingAdapter::new(), schema);
let router = api_router(state);
let (_, json) = post_json(
&router,
"/api/v1/query/explain",
serde_json::json!({ "query": "query { users { id } }" }),
)
.await;
assert!(
json["data"]["sql"].is_string(),
"expected SQL string, got: {}",
json["data"]["sql"]
);
assert!(json["data"]["views_accessed"].as_array().is_some());
}
#[tokio::test]
async fn explain_returns_null_sql_for_unknown_query() {
let router = api_router(make_test_state());
let (status, json) = post_json(
&router,
"/api/v1/query/explain",
serde_json::json!({ "query": "query { users { id } }" }),
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(json["data"]["sql"].is_null());
assert_eq!(json["data"]["query_type"], "unknown");
}
#[tokio::test]
async fn explain_rejects_empty_query() {
let router = api_router(make_test_state());
let (status, json) =
post_json(&router, "/api/v1/query/explain", serde_json::json!({ "query": "" })).await;
assert_eq!(status, StatusCode::BAD_REQUEST);
assert_eq!(json["code"], "VALIDATION_ERROR");
}
#[tokio::test]
async fn explain_deeply_nested_query_generates_warnings() {
let deep_query =
"query { a { b { c { d { e { f { g { h { i { j { k { l } } } } } } } } } } } }";
let router = api_router(make_test_state());
let (status, json) =
post_json(&router, "/api/v1/query/explain", serde_json::json!({ "query": deep_query }))
.await;
assert_eq!(status, StatusCode::OK);
let warnings = json["data"]["warnings"].as_array().unwrap();
assert!(
warnings.iter().any(|w| w.as_str().unwrap().contains("depth")),
"Expected depth warning, got: {warnings:?}"
);
}
#[tokio::test]
async fn validate_accepts_well_formed_query() {
let router = api_router(make_test_state());
let (status, json) = post_json(
&router,
"/api/v1/query/validate",
serde_json::json!({ "query": "query { users { id } }" }),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(json["data"]["valid"], true);
assert!(json["data"]["errors"].as_array().unwrap().is_empty());
}
#[tokio::test]
async fn validate_rejects_mismatched_braces() {
let router = api_router(make_test_state());
let (status, json) = post_json(
&router,
"/api/v1/query/validate",
serde_json::json!({ "query": "query { users { id }" }),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(json["data"]["valid"], false);
let errors = json["data"]["errors"].as_array().unwrap();
assert!(
!errors.is_empty(),
"parser should report at least one error for mismatched braces"
);
}
#[tokio::test]
async fn validate_reports_empty_query_as_invalid() {
let router = api_router(make_test_state());
let (status, json) =
post_json(&router, "/api/v1/query/validate", serde_json::json!({ "query": "" })).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(json["data"]["valid"], false);
}
#[tokio::test]
async fn stats_returns_zero_counters_on_fresh_state() {
let router = api_router(make_test_state());
let (status, json) = get_json(&router, "/api/v1/query/stats").await;
assert_eq!(status, StatusCode::OK);
assert_eq!(json["status"], "success");
assert_eq!(json["data"]["total_queries"], 0);
assert_eq!(json["data"]["successful_queries"], 0);
assert_eq!(json["data"]["failed_queries"], 0);
assert_eq!(json["data"]["average_latency_ms"], 0.0);
}
#[tokio::test]
async fn stats_reflects_metrics_atomics() {
use std::sync::atomic::Ordering;
let state = make_test_state();
state.metrics.queries_total.fetch_add(5, Ordering::Relaxed);
state.metrics.queries_success.fetch_add(4, Ordering::Relaxed);
state.metrics.queries_error.fetch_add(1, Ordering::Relaxed);
state.metrics.queries_duration_us.fetch_add(10_000, Ordering::Relaxed);
let router = api_router(state);
let (_, json) = get_json(&router, "/api/v1/query/stats").await;
assert_eq!(json["data"]["total_queries"], 5);
assert_eq!(json["data"]["successful_queries"], 4);
assert_eq!(json["data"]["failed_queries"], 1);
let avg = json["data"]["average_latency_ms"].as_f64().unwrap();
assert!((avg - 2.0).abs() < 0.01, "expected ~2.0ms, got {avg}");
}