#![allow(clippy::unwrap_used)]
use fraiseql_core::{
error::FraiseQLError,
validation::{
async_validators::{AsyncValidator, EmailFormatValidator, PhoneE164Validator},
validators::{
EnumValidator, LengthValidator, PatternValidator, RequiredValidator, Validator,
},
},
};
#[test]
fn test_pattern_validator_rejects_non_matching_value() {
let v = PatternValidator::new("^[0-9]+$", "must be digits only").unwrap();
let result = v.validate("abc123", "code");
assert!(result.is_err(), "expected validation error for non-matching value");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("code"), "error path should match field name");
}
}
#[test]
fn test_pattern_validator_rejects_empty_string_for_non_empty_pattern() {
let v = PatternValidator::new("^.+$", "must not be empty").unwrap();
let result = v.validate("", "username");
assert!(
result.is_err(),
"expected Err for empty string with non-empty pattern, got: {result:?}"
);
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("username"));
}
}
#[test]
fn test_length_validator_rejects_too_short_value() {
let v = LengthValidator::new(Some(5), None);
let result = v.validate("ab", "name");
assert!(result.is_err(), "expected error: string is too short");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("name"));
}
}
#[test]
fn test_length_validator_rejects_too_long_value() {
let v = LengthValidator::new(None, Some(4));
let result = v.validate("toolong", "tag");
assert!(result.is_err(), "expected error: string is too long");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("tag"));
}
}
#[test]
fn test_enum_validator_rejects_value_not_in_set() {
let v = EnumValidator::new(vec!["active".into(), "inactive".into(), "pending".into()]);
let result = v.validate("deleted", "status");
assert!(result.is_err(), "expected error: 'deleted' is not in the enum set");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("status"));
}
}
#[test]
fn test_enum_validator_rejects_case_mismatch() {
let v = EnumValidator::new(vec!["Active".into()]);
let result = v.validate("active", "role");
assert!(result.is_err(), "expected error: wrong case");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("role"));
}
}
#[test]
fn test_required_validator_rejects_empty_string() {
let v = RequiredValidator;
let result = v.validate("", "email");
assert!(result.is_err(), "expected error: required field is empty");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("email"));
}
}
#[tokio::test]
async fn test_email_validator_rejects_missing_at_symbol() {
let v = EmailFormatValidator::new();
let result = v.validate_async("notanemail", "email").await;
assert!(result.is_err(), "expected error: no @ in email");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("email"));
}
}
#[tokio::test]
async fn test_email_validator_rejects_missing_domain() {
let v = EmailFormatValidator::new();
let result = v.validate_async("user@", "contact_email").await;
assert!(result.is_err(), "expected error: no domain after @");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("contact_email"));
}
}
#[tokio::test]
async fn test_email_validator_rejects_missing_tld() {
let v = EmailFormatValidator::new();
let result = v.validate_async("user@localhost", "user_email").await;
assert!(result.is_err(), "expected error: no TLD");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("user_email"));
}
}
#[tokio::test]
async fn test_phone_validator_rejects_missing_plus_prefix() {
let v = PhoneE164Validator::new();
let result = v.validate_async("14155552671", "phone").await;
assert!(result.is_err(), "expected error: no leading +");
let err = result.unwrap_err();
assert!(
matches!(err, FraiseQLError::Validation { .. }),
"expected Validation error, got: {err:?}"
);
if let FraiseQLError::Validation { path, .. } = err {
assert_eq!(path.as_deref(), Some("phone"));
}
}
#[tokio::test]
async fn test_phone_validator_rejects_too_short_number() {
let v = PhoneE164Validator::new();
let result = v.validate_async("+123", "mobile").await;
assert!(result.is_err(), "expected error: number too short");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("mobile"));
}
}
#[tokio::test]
async fn test_phone_validator_rejects_leading_zero_after_plus() {
let v = PhoneE164Validator::new();
let result = v.validate_async("+0441234567", "phone_number").await;
assert!(result.is_err(), "expected error: leading zero after +");
if let FraiseQLError::Validation { path, .. } = result.unwrap_err() {
assert_eq!(path.as_deref(), Some("phone_number"));
}
}