#![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)]
use fraiseql_server::{
error::GraphQLError, routes::graphql::GraphQLRequest, validation::RequestValidator,
};
use serde_json::json;
#[test]
fn test_simple_query_structure() {
let request = GraphQLRequest {
query: Some("{ user { id } }".to_string()),
variables: None,
operation_name: None,
extensions: None,
document_id: None,
};
assert_eq!(request.query.as_deref(), Some("{ user { id } }"));
assert_eq!(request.variables, None);
assert_eq!(request.operation_name, None);
}
#[test]
fn test_query_with_variables() {
let variables = json!({
"userId": "123e4567-e89b-12d3-a456-426614174000",
"limit": 10
});
let request = GraphQLRequest {
query: Some("query($userId: ID!, $limit: Int!) { user(id: $userId) { posts(limit: $limit) { id } } }".to_string()),
variables: Some(variables),
operation_name: Some("GetUserPosts".to_string()),
extensions: None,
document_id: None,
};
assert_eq!(request.operation_name, Some("GetUserPosts".to_string()));
let vars = request.variables.expect("variables should be present");
assert_eq!(vars["userId"], "123e4567-e89b-12d3-a456-426614174000");
assert_eq!(vars["limit"], 10);
}
#[test]
fn test_simple_query_validation() {
let validator = RequestValidator::new();
let simple_queries = vec![
"{ user { id } }",
"{ users { id name } }",
"query { post { title } }",
"query GetUser { user { id } }",
];
for query in simple_queries {
assert!(validator.validate_query(query).is_ok(), "Failed to validate query: {}", query);
}
}
#[test]
fn test_multi_field_query_validation() {
let validator = RequestValidator::new();
let multi_field = "{
users {
id
name
email
}
}";
validator
.validate_query(multi_field)
.unwrap_or_else(|e| panic!("Multi-field query should pass validation: {e}"));
}
#[test]
fn test_nested_query_validation() {
let validator = RequestValidator::new();
let nested = "{
posts {
id
title
author {
id
name
email
}
}
}";
validator
.validate_query(nested)
.unwrap_or_else(|e| panic!("Nested query should pass validation: {e}"));
}
#[test]
fn test_query_depth_limit() {
let validator = RequestValidator::new().with_max_depth(4);
let shallow = "{ user { profile { name } } }";
validator
.validate_query(shallow)
.unwrap_or_else(|e| panic!("Shallow query (2 levels) should pass depth limit of 4: {e}"));
let at_limit = "{ user { profile { settings { theme } } } }";
validator
.validate_query(at_limit)
.unwrap_or_else(|e| panic!("Query at depth limit (3 levels) should pass: {e}"));
let over_limit = "{ user { profile { settings { theme { dark { mode } } } } } }";
assert!(
validator.validate_query(over_limit).is_err(),
"Query exceeding depth limit (5 levels, max 4) should be rejected"
);
}
#[test]
fn test_query_complexity_limit() {
let validator = RequestValidator::new().with_max_complexity(10);
let simple = "{ user { id } }";
validator
.validate_query(simple)
.unwrap_or_else(|e| panic!("Simple query should pass complexity limit: {e}"));
let moderate = "{ users { id name email posts { id title } } }";
validator
.validate_query(moderate)
.unwrap_or_else(|e| panic!("Moderate query should pass complexity limit of 10: {e}"));
}
#[test]
fn test_variables_validation() {
let validator = RequestValidator::new();
let valid_vars = json!({
"id": "123",
"name": "John",
"limit": 10
});
validator
.validate_variables(Some(&valid_vars))
.unwrap_or_else(|e| panic!("Valid variables object should pass validation: {e}"));
let empty_vars = json!({});
validator
.validate_variables(Some(&empty_vars))
.unwrap_or_else(|e| panic!("Empty variables object should pass validation: {e}"));
validator
.validate_variables(None)
.unwrap_or_else(|e| panic!("No variables (None) should pass validation: {e}"));
let invalid_array = json!([1, 2, 3]);
assert!(
validator.validate_variables(Some(&invalid_array)).is_err(),
"Variables as array should be rejected"
);
let invalid_string = json!("some string");
assert!(
validator.validate_variables(Some(&invalid_string)).is_err(),
"Variables as string should be rejected"
);
}
#[test]
fn test_pagination_query_validation() {
let validator = RequestValidator::new();
let with_pagination = "query($limit: Int!, $offset: Int!) {
users(limit: $limit, offset: $offset) {
id name
}
}";
validator
.validate_query(with_pagination)
.unwrap_or_else(|e| panic!("Pagination query should pass validation: {e}"));
}
#[test]
fn test_empty_query_rejection() {
let validator = RequestValidator::new();
let empty_queries = vec!["", " ", "\n", "\t"];
for query in empty_queries {
assert!(
validator.validate_query(query).is_err(),
"Should reject empty query: {:?}",
query
);
}
}
#[test]
fn test_structural_validator_rejects_known_invalid() {
let validator = RequestValidator::new().with_max_depth(3);
let too_deep = "{ a { b { c { d { e } } } } }";
assert!(
validator.validate_query(too_deep).is_err(),
"Query exceeding max_depth should be rejected"
);
let unclosed = "{ user { id";
assert!(
validator.validate_query(unclosed).is_err(),
"Malformed queries with unclosed braces must be rejected"
);
}
#[test]
fn test_graphql_error_response_format() {
let error = GraphQLError::parse("Unexpected token".to_string());
let json = serde_json::to_value(&error).unwrap();
assert_eq!(json["message"], "Unexpected token");
assert_eq!(json["code"], "PARSE_ERROR");
}
#[test]
fn test_graphql_request_deserialization() {
let json_request = r#"{
"query": "{ users { id name } }",
"variables": {
"limit": 10
},
"operationName": "GetUsers"
}"#;
let request: GraphQLRequest = serde_json::from_str(json_request).unwrap();
assert_eq!(request.query.as_deref(), Some("{ users { id name } }"));
let variables = request.variables.expect("variables should be present");
assert_eq!(variables["limit"], 10);
assert_eq!(request.operation_name, Some("GetUsers".to_string()));
}
#[test]
fn test_minimal_graphql_request() {
let json_request = r#"{"query": "{ users { id } }"}"#;
let request: GraphQLRequest = serde_json::from_str(json_request).unwrap();
assert_eq!(request.query.as_deref(), Some("{ users { id } }"));
assert_eq!(request.variables, None);
assert_eq!(request.operation_name, None);
}
#[test]
fn test_complete_graphql_request() {
let json_request = r#"{
"query": "query GetUser($id: ID!) { user(id: $id) { id name email } }",
"variables": { "id": "123" },
"operationName": "GetUser"
}"#;
let request: GraphQLRequest = serde_json::from_str(json_request).unwrap();
assert_eq!(request.operation_name, Some("GetUser".to_string()));
assert_eq!(request.variables.unwrap().get("id").and_then(|v| v.as_str()), Some("123"));
}
#[test]
fn test_validation_pipeline() {
let validator = RequestValidator::new();
let request = GraphQLRequest {
query: Some("{ users { id name } }".to_string()),
variables: Some(json!({"limit": 10})),
operation_name: None,
extensions: None,
document_id: None,
};
validator
.validate_query(request.query.as_deref().unwrap())
.unwrap_or_else(|e| panic!("Query structure validation should pass: {e}"));
validator
.validate_variables(request.variables.as_ref())
.unwrap_or_else(|e| panic!("Variables format validation should pass: {e}"));
}
#[test]
fn test_batch_query_validation() {
let validator = RequestValidator::new();
let queries = vec![
"{ user { id } }",
"{ users { id name } }",
"{ posts { id title author { name } } }",
"{ comments { id content } }",
];
for query in queries {
assert!(validator.validate_query(query).is_ok(), "Failed validation for: {}", query);
}
}
#[test]
fn test_query_depth_acceptance_by_level() {
let validator = RequestValidator::new().with_max_depth(3);
let within_limit = vec![
"{ id }", "{ user { id } }", "{ user { profile { name } } }", ];
for query in within_limit {
assert!(
validator.validate_query(query).is_ok(),
"Query should pass with max_depth=3: {query}"
);
}
let over_limit = "{ posts { author { posts { title } } } }";
assert!(
validator.validate_query(over_limit).is_err(),
"Query at depth 4 should fail with max_depth=3"
);
}