#![allow(clippy::unwrap_used)]
use fraiseql_server::{
error::{ErrorCode, ErrorExtensions, ErrorResponse, GraphQLError},
routes::graphql::GraphQLRequest,
validation::RequestValidator,
};
use serde_json::json;
#[test]
fn test_empty_query_validation() {
let validator = RequestValidator::new();
let result = validator.validate_query("");
assert!(result.is_err(), "expected Err for empty query, got: {result:?}");
let result = validator.validate_query(" ");
assert!(result.is_err(), "expected Err for whitespace-only query, got: {result:?}");
}
#[test]
fn test_depth_validation() {
let validator = RequestValidator::new().with_max_depth(3);
let shallow = "{ user { id } }";
validator
.validate_query(shallow)
.unwrap_or_else(|e| panic!("expected Ok for shallow query: {e}"));
let medium = "{ user { profile { settings } } }";
validator
.validate_query(medium)
.unwrap_or_else(|e| panic!("expected Ok for medium query: {e}"));
let deep = "{ user { profile { settings { theme { dark } } } } }";
assert!(validator.validate_query(deep).is_err(), "expected Err for deep query, got Ok");
}
#[test]
fn test_complexity_validation() {
let validator = RequestValidator::new().with_max_complexity(5);
let simple = "{ user { id name } }";
validator
.validate_query(simple)
.unwrap_or_else(|e| panic!("expected Ok for simple query: {e}"));
let complex = "{ users [ posts [ comments [ author [ name ] ] ] ] }";
assert!(
validator.validate_query(complex).is_err(),
"expected Err for complex query, got Ok"
);
}
#[test]
fn test_variables_validation() {
let validator = RequestValidator::new();
let valid = json!({
"id": "123",
"name": "John"
});
validator
.validate_variables(Some(&valid))
.unwrap_or_else(|e| panic!("expected Ok for valid variables: {e}"));
validator
.validate_variables(None)
.unwrap_or_else(|e| panic!("expected Ok for None variables: {e}"));
let invalid = json!([1, 2, 3]);
assert!(
validator.validate_variables(Some(&invalid)).is_err(),
"expected Err for array variables, got Ok"
);
}
#[test]
fn test_disable_validation() {
let validator = RequestValidator::new()
.with_depth_validation(false)
.with_complexity_validation(false)
.with_max_depth(1)
.with_max_complexity(1);
let deep = "{ a { b { c { d { e { f } } } } } }";
validator
.validate_query(deep)
.unwrap_or_else(|e| panic!("expected Ok when validation disabled: {e}"));
}
#[test]
fn test_error_serialization() {
let error = GraphQLError::validation("Invalid query")
.with_location(1, 5)
.with_path(vec!["user".to_string(), "id".to_string()]);
let json: serde_json::Value = serde_json::to_value(&error).unwrap();
assert_eq!(json["message"], "Invalid query");
assert_eq!(json["code"], "VALIDATION_ERROR");
assert_eq!(json["locations"][0]["line"], 1);
assert_eq!(json["locations"][0]["column"], 5);
let path = json["path"].as_array().unwrap();
assert_eq!(path.len(), 2);
assert_eq!(path[0], "user");
assert_eq!(path[1], "id");
}
#[test]
fn test_error_code_status_mapping() {
assert_eq!(ErrorCode::ValidationError.status_code(), axum::http::StatusCode::OK);
assert_eq!(ErrorCode::Unauthenticated.status_code(), axum::http::StatusCode::UNAUTHORIZED);
assert_eq!(ErrorCode::Forbidden.status_code(), axum::http::StatusCode::FORBIDDEN);
assert_eq!(ErrorCode::NotFound.status_code(), axum::http::StatusCode::NOT_FOUND);
assert_eq!(
ErrorCode::DatabaseError.status_code(),
axum::http::StatusCode::INTERNAL_SERVER_ERROR
);
assert_eq!(
ErrorCode::RateLimitExceeded.status_code(),
axum::http::StatusCode::TOO_MANY_REQUESTS
);
}
#[test]
fn test_graphql_request_deserialization() {
let json_str = r#"{"query": "{ users { id } }"}"#;
let request: GraphQLRequest = serde_json::from_str(json_str).unwrap();
assert_eq!(request.query.as_deref(), Some("{ users { id } }"));
assert_eq!(request.variables, None);
assert_eq!(request.operation_name, None);
}
#[test]
fn test_graphql_request_with_variables_deserialization() {
let json_str =
r#"{"query": "query($id: ID!) { user(id: $id) { name } }", "variables": {"id": "123"}}"#;
let request: GraphQLRequest = serde_json::from_str(json_str).unwrap();
assert_eq!(request.query.as_deref(), Some("query($id: ID!) { user(id: $id) { name } }"));
let variables = request.variables.expect("variables should be present");
assert_eq!(variables, json!({"id": "123"}));
}
#[test]
fn test_graphql_request_with_operation_name() {
let json = r#"{
"query": "query GetUser { user { id } }",
"operationName": "GetUser"
}"#;
let request: GraphQLRequest = serde_json::from_str(json).unwrap();
assert_eq!(request.operation_name, Some("GetUser".to_string()));
}
#[test]
fn test_request_validation_integration() {
let validator = RequestValidator::new();
let valid_request = GraphQLRequest {
query: Some("{ user { id } }".to_string()),
variables: None,
operation_name: None,
extensions: None,
document_id: None,
};
validator
.validate_query(valid_request.query.as_deref().unwrap())
.unwrap_or_else(|e| panic!("expected Ok for valid request query: {e}"));
validator
.validate_variables(valid_request.variables.as_ref())
.unwrap_or_else(|e| panic!("expected Ok for valid request variables: {e}"));
let deep_request = GraphQLRequest {
query: Some("{ a { b { c { d { e { f } } } } } }".to_string()),
variables: None,
operation_name: None,
extensions: None,
document_id: None,
};
let validator = validator.with_max_depth(2);
assert!(
validator.validate_query(deep_request.query.as_deref().unwrap()).is_err(),
"expected Err for query exceeding max depth 2, got Ok"
);
}
#[test]
fn test_multiple_errors_response() {
let response = ErrorResponse::new(vec![
GraphQLError::validation("Field not found"),
GraphQLError::database("Connection timeout"),
]);
let json: serde_json::Value = serde_json::to_value(&response).unwrap();
let errors = json["errors"].as_array().unwrap();
assert_eq!(errors.len(), 2);
assert_eq!(errors[0]["message"], "Field not found");
assert_eq!(errors[0]["code"], "VALIDATION_ERROR");
assert_eq!(errors[1]["message"], "Connection timeout");
assert_eq!(errors[1]["code"], "DATABASE_ERROR");
}
#[test]
fn test_error_extensions() {
let extensions = ErrorExtensions {
category: Some("VALIDATION".to_string()),
status: Some(400),
request_id: Some("req-12345".to_string()),
retry_after_secs: None,
detail: None,
};
let error = GraphQLError::validation("Invalid input").with_extensions(extensions);
let json: serde_json::Value = serde_json::to_value(&error).unwrap();
assert_eq!(json["message"], "Invalid input");
assert_eq!(json["code"], "VALIDATION_ERROR");
assert_eq!(json["extensions"]["category"], "VALIDATION");
assert_eq!(json["extensions"]["status"], 400);
assert_eq!(json["extensions"]["request_id"], "req-12345");
}
#[test]
fn test_validator_builder_pattern() {
let validator = RequestValidator::new()
.with_max_depth(5)
.with_max_complexity(50)
.with_depth_validation(true)
.with_complexity_validation(false);
let deep = "{ a { b { c { d { e { f } } } } } }";
assert!(
validator.validate_query(deep).is_err(),
"expected Err for deep query exceeding depth limit"
);
let invalid_syntax = "{ a [ b [ c [ d [ e ] ] ] ] }";
assert!(
validator.validate_query(invalid_syntax).is_err(),
"expected Err for invalid syntax, got Ok"
);
let complex = "{ a { b { c { d { e } } } } }";
validator
.validate_query(complex)
.unwrap_or_else(|e| panic!("expected Ok when complexity validation disabled: {e}"));
}
#[test]
fn test_all_error_codes_have_status() {
let codes = vec![
ErrorCode::ValidationError,
ErrorCode::ParseError,
ErrorCode::RequestError,
ErrorCode::Unauthenticated,
ErrorCode::Forbidden,
ErrorCode::NotFound,
ErrorCode::Conflict,
ErrorCode::DatabaseError,
ErrorCode::InternalServerError,
ErrorCode::Timeout,
ErrorCode::RateLimitExceeded,
ErrorCode::CircuitBreakerOpen,
ErrorCode::PersistedQueryNotFound,
ErrorCode::PersistedQueryMismatch,
ErrorCode::ForbiddenQuery,
ErrorCode::DocumentNotFound,
];
let ok_variants = [
ErrorCode::ValidationError,
ErrorCode::ParseError,
ErrorCode::PersistedQueryNotFound,
];
for code in codes {
let status = code.status_code();
if ok_variants.contains(&code) {
assert_eq!(status, axum::http::StatusCode::OK, "{code:?} should return 200");
} else {
assert!(
status.is_client_error() || status.is_server_error(),
"{code:?} should return 4xx/5xx"
);
}
}
}
#[test]
fn test_error_response_into_response() {
use axum::response::IntoResponse;
let error = GraphQLError::validation("Test error");
let response = ErrorResponse::from_error(error);
let http_response = response.into_response();
assert_eq!(http_response.status(), axum::http::StatusCode::OK);
}
#[test]
fn test_string_literal_handling() {
let validator = RequestValidator::new();
let query = r#"{ user { name: "John \"Doe\"" } }"#;
let result = validator.validate_query(query);
assert!(result.is_err(), "String literals in selection sets are invalid GraphQL syntax");
let valid_query = r#"query { user(name: "John") { id name } }"#;
validator
.validate_query(valid_query)
.unwrap_or_else(|e| panic!("String arguments in field invocations should be valid: {e}"));
}
#[test]
fn test_minimal_validator() {
let validator = RequestValidator::new();
let simple = "{ user }";
validator
.validate_query(simple)
.unwrap_or_else(|e| panic!("expected Ok for simple query: {e}"));
}
#[test]
fn test_validation_error_conversion() {
let error = fraiseql_server::ComplexityValidationError::QueryTooDeep {
max_depth: 10,
actual_depth: 15,
};
let error_msg = error.to_string();
assert_eq!(
error_msg, "Query exceeds maximum depth of 10: depth = 15",
"ValidationError::QueryTooDeep should produce exact error message"
);
}
#[test]
fn test_graphql_error_factory_methods() {
let validation_error = GraphQLError::validation("Validation failed");
assert_eq!(validation_error.code, ErrorCode::ValidationError);
let parse_error = GraphQLError::parse("Parse failed");
assert_eq!(parse_error.code, ErrorCode::ParseError);
let request_error = GraphQLError::request("Request failed");
assert_eq!(request_error.code, ErrorCode::RequestError);
let db_error = GraphQLError::database("DB failed");
assert_eq!(db_error.code, ErrorCode::DatabaseError);
let internal_error = GraphQLError::internal("Internal error");
assert_eq!(internal_error.code, ErrorCode::InternalServerError);
let execution_error = GraphQLError::execution("Execution failed");
assert_eq!(execution_error.code, ErrorCode::InternalServerError);
let not_found_error = GraphQLError::not_found("Not found");
assert_eq!(not_found_error.code, ErrorCode::NotFound);
let unauthenticated = GraphQLError::unauthenticated();
assert_eq!(unauthenticated.code, ErrorCode::Unauthenticated);
let forbidden = GraphQLError::forbidden();
assert_eq!(forbidden.code, ErrorCode::Forbidden);
}