#![allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
use fraiseql_core::{
schema::{
CompiledSchema, InputFieldDefinition, InputObjectDefinition, IntrospectionBuilder,
TypeKind,
},
validation::rules::{CompiledPattern, ValidationRule},
};
fn create_test_schema_with_validation() -> CompiledSchema {
let mut schema = CompiledSchema::default();
let mut fields = vec![];
let mut required_field = InputFieldDefinition::new("id", "String!".to_string());
required_field = required_field.with_validation_rule(ValidationRule::Required);
fields.push(required_field);
let mut email_field = InputFieldDefinition::new("email", "String".to_string());
email_field = email_field.with_validation_rule(ValidationRule::Pattern {
pattern: CompiledPattern::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").expect("valid regex"),
message: Some("Invalid email format".to_string()),
});
fields.push(email_field);
let mut age_field = InputFieldDefinition::new("age", "Int".to_string());
age_field = age_field.with_validation_rule(ValidationRule::Range {
min: Some(0),
max: Some(150),
});
fields.push(age_field);
let mut password_field = InputFieldDefinition::new("password", "String".to_string());
password_field = password_field.with_validation_rule(ValidationRule::Length {
min: Some(8),
max: Some(128),
});
fields.push(password_field);
let mut status_field = InputFieldDefinition::new("status", "String".to_string());
status_field = status_field.with_validation_rule(ValidationRule::Enum {
values: vec![
"ACTIVE".to_string(),
"INACTIVE".to_string(),
"PENDING".to_string(),
],
});
fields.push(status_field);
let mut start_date_field = InputFieldDefinition::new("startDate", "String".to_string());
start_date_field = start_date_field.with_validation_rule(ValidationRule::CrossField {
field: "endDate".to_string(),
operator: "lt".to_string(),
});
fields.push(start_date_field);
let mut card_field = InputFieldDefinition::new("cardNumber", "String".to_string());
card_field = card_field.with_validation_rule(ValidationRule::Checksum {
algorithm: "luhn".to_string(),
});
fields.push(card_field);
let mut reference_field = InputFieldDefinition::new("reference", "String".to_string());
reference_field = reference_field.with_validation_rule(ValidationRule::OneOf {
fields: vec![
"entityId".to_string(),
"name".to_string(),
"description".to_string(),
],
});
fields.push(reference_field);
let mut contact_field = InputFieldDefinition::new("contact", "String".to_string());
contact_field = contact_field.with_validation_rule(ValidationRule::AnyOf {
fields: vec![
"email".to_string(),
"phone".to_string(),
"address".to_string(),
],
});
fields.push(contact_field);
let mut created_at_field = InputFieldDefinition::new("createdAt", "String".to_string());
created_at_field =
created_at_field.with_validation_rule(ValidationRule::ConditionalRequired {
if_field_present: "entityId".to_string(),
then_required: vec!["updatedAt".to_string()],
});
fields.push(created_at_field);
let mut address_id_field = InputFieldDefinition::new("addressId", "String".to_string());
address_id_field =
address_id_field.with_validation_rule(ValidationRule::RequiredIfAbsent {
absent_field: "street".to_string(),
then_required: vec!["city".to_string(), "zip".to_string()],
});
fields.push(address_id_field);
let input_type = InputObjectDefinition::new("TestInput").with_fields(fields);
schema.input_types.push(input_type);
schema
}
#[test]
fn test_introspection_includes_validation_rules() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"));
assert!(test_input.is_some(), "TestInput should be in introspection");
let test_input = test_input.unwrap();
assert_eq!(test_input.kind, TypeKind::InputObject);
assert!(test_input.input_fields.is_some());
let input_fields = test_input.input_fields.as_ref().unwrap();
assert!(!input_fields.is_empty());
for field in input_fields {
assert!(
!field.validation_rules.is_empty(),
"Field {} should have validation rules",
field.name
);
}
}
#[test]
fn test_required_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let id_field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "id")
.unwrap();
assert_eq!(id_field.validation_rules.len(), 1);
assert_eq!(id_field.validation_rules[0].rule_type, "required");
}
#[test]
fn test_pattern_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let email_field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "email")
.unwrap();
assert_eq!(email_field.validation_rules.len(), 1);
assert_eq!(email_field.validation_rules[0].rule_type, "pattern");
assert!(email_field.validation_rules[0].pattern.is_some());
}
#[test]
fn test_range_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let age_field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "age")
.unwrap();
assert_eq!(age_field.validation_rules.len(), 1);
assert_eq!(age_field.validation_rules[0].rule_type, "range");
assert_eq!(age_field.validation_rules[0].min, Some(0));
assert_eq!(age_field.validation_rules[0].max, Some(150));
}
#[test]
fn test_length_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let password_field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "password")
.unwrap();
assert_eq!(password_field.validation_rules.len(), 1);
assert_eq!(password_field.validation_rules[0].rule_type, "length");
assert_eq!(password_field.validation_rules[0].min, Some(8));
assert_eq!(password_field.validation_rules[0].max, Some(128));
}
#[test]
fn test_enum_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let status_field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "status")
.unwrap();
assert_eq!(status_field.validation_rules.len(), 1);
assert_eq!(status_field.validation_rules[0].rule_type, "enum");
assert!(status_field.validation_rules[0].allowed_values.is_some());
let values = status_field.validation_rules[0].allowed_values.as_ref().unwrap();
assert_eq!(values.len(), 3);
}
#[test]
fn test_cross_field_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "startDate")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "cross_field");
assert_eq!(field.validation_rules[0].field_reference, Some("endDate".to_string()));
assert_eq!(field.validation_rules[0].operator, Some("lt".to_string()));
}
#[test]
fn test_checksum_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "cardNumber")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "checksum");
assert_eq!(field.validation_rules[0].algorithm, Some("luhn".to_string()));
}
#[test]
fn test_one_of_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "reference")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "one_of");
assert!(field.validation_rules[0].field_list.is_some());
}
#[test]
fn test_any_of_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "contact")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "any_of");
assert!(field.validation_rules[0].field_list.is_some());
}
#[test]
fn test_conditional_required_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "createdAt")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "conditional_required");
}
#[test]
fn test_required_if_absent_rule_in_introspection() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let test_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "TestInput"))
.unwrap();
let field = test_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "addressId")
.unwrap();
assert_eq!(field.validation_rules.len(), 1);
assert_eq!(field.validation_rules[0].rule_type, "required_if_absent");
}
#[test]
fn test_validation_rules_json_serialization() {
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let json = serde_json::to_string(&introspection).unwrap();
assert!(json.contains("\"required\""));
assert!(json.contains("\"pattern\""));
assert!(json.contains("\"range\""));
}
#[test]
fn test_field_without_validation_rules() {
let mut schema = CompiledSchema::default();
let field = InputFieldDefinition::new("unvalidated", "String".to_string());
let input_type = InputObjectDefinition::new("SimpleInput").with_fields(vec![field]);
schema.input_types.push(input_type);
let introspection = IntrospectionBuilder::build(&schema);
let simple_input = introspection
.types
.iter()
.find(|t| t.name.as_ref().is_some_and(|n| n == "SimpleInput"))
.unwrap();
let unvalidated_field = simple_input
.input_fields
.as_ref()
.unwrap()
.iter()
.find(|f| f.name == "unvalidated")
.unwrap();
assert!(unvalidated_field.validation_rules.is_empty());
}
#[test]
fn test_apollo_sandbox_compatibility() {
use fraiseql_core::schema::IntrospectionSchema;
let schema = create_test_schema_with_validation();
let introspection = IntrospectionBuilder::build(&schema);
let json = serde_json::to_string_pretty(&introspection).unwrap();
let deserialized: IntrospectionSchema = serde_json::from_str(&json).unwrap();
assert!(!deserialized.types.is_empty());
}
}