use serde_json::json;
use crate::common::{ValidationErrorCode, builders::UserBuilder, fixtures::rfc_examples};
use scim_server::error::ValidationError;
use scim_server::schema::{SchemaRegistry, validation::OperationContext};
#[test]
fn test_single_value_for_multi_valued() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_single_email = UserBuilder::new().with_single_value_emails().build();
assert!(!user_single_email["emails"].is_array());
assert!(user_single_email["emails"].is_object());
let result = registry.validate_json_resource_with_context(
"User",
&user_single_email,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::Custom { message }) => {
assert!(message.contains("emails must be an array"));
}
Err(other) => panic!("Expected Custom error about emails array, got {:?}", other),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_single_value_for_multi_valued_addresses() {
let user_single_address = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"addresses": {
"type": "work",
"streetAddress": "123 Main St",
"locality": "Anytown",
"region": "CA",
"postalCode": "12345",
"country": "USA"
},
"meta": {
"resourceType": "User"
}
});
assert!(!user_single_address["addresses"].is_array());
assert!(user_single_address["addresses"].is_object());
}
#[test]
fn test_array_for_single_valued() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_array_username = UserBuilder::new().with_array_username().build();
assert!(user_array_username["userName"].is_array());
assert!(!user_array_username["userName"].is_string());
let result = registry.validate_json_resource_with_context(
"User",
&user_array_username,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::Custom { message }) => {
assert!(message.contains("userName must be a string"));
}
Err(other) => panic!(
"Expected Custom error about userName string, got {:?}",
other
),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_array_for_single_valued_display_name() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_array_display_name = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"displayName": ["John", "Doe"],
"meta": {
"resourceType": "User"
}
});
assert!(user_array_display_name["displayName"].is_array());
let result = registry.validate_json_resource_with_context(
"User",
&user_array_display_name,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::InvalidAttributeType {
attribute,
expected,
actual,
}) => {
assert_eq!(attribute, "displayName");
assert_eq!(expected, "string");
assert_eq!(actual, "array");
}
Err(other) => panic!("Expected InvalidAttributeType error, got {:?}", other),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_multiple_primary_values() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_multiple_primaries = UserBuilder::new().with_multiple_primary_emails().build();
let emails = user_multiple_primaries["emails"].as_array().unwrap();
let primary_count = emails
.iter()
.filter(|email| email["primary"] == true)
.count();
assert!(primary_count > 1, "Should have multiple primary emails");
let result = registry.validate_json_resource_with_context(
"User",
&user_multiple_primaries,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::MultiplePrimaryValues { attribute }) => {
assert_eq!(attribute, "emails");
}
Err(other) => panic!("Expected MultiplePrimaryValues error, got {:?}", other),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_multiple_primary_addresses() {
let user_multiple_primary_addresses = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"addresses": [
{
"type": "work",
"streetAddress": "123 Work St",
"primary": true
},
{
"type": "home",
"streetAddress": "456 Home St",
"primary": true }
],
"meta": {
"resourceType": "User"
}
});
let addresses = user_multiple_primary_addresses["addresses"]
.as_array()
.unwrap();
let primary_count = addresses
.iter()
.filter(|addr| addr["primary"] == true)
.count();
assert_eq!(primary_count, 2);
}
#[test]
fn test_invalid_multi_valued_structure() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_invalid_email_structure = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [
"plain-string-email", {
"value": "valid@example.com",
"type": "work"
}
],
"meta": {
"resourceType": "User"
}
});
let emails = user_invalid_email_structure["emails"].as_array().unwrap();
assert!(emails[0].is_string()); assert!(emails[1].is_object());
let result = registry.validate_json_resource_with_context(
"User",
&user_invalid_email_structure,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::Custom { message }) => {
assert!(message.contains("Invalid emails format"));
assert!(message.contains("invalid type: string"));
}
Err(other) => panic!(
"Expected Custom error about invalid emails format, got {:?}",
other
),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_multi_valued_missing_value() {
let user_email_without_value = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [
{
"type": "work",
"primary": true
}
],
"meta": {
"resourceType": "User"
}
});
let email = &user_email_without_value["emails"][0];
assert!(!email.as_object().unwrap().contains_key("value"));
assert!(email.as_object().unwrap().contains_key("type"));
}
#[test]
fn test_missing_required_sub_attribute() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_email_missing_value = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [
{
"type": "work",
"primary": true
}
],
"meta": {
"resourceType": "User"
}
});
let email = &user_email_missing_value["emails"][0];
assert!(!email.as_object().unwrap().contains_key("value"));
assert!(email.as_object().unwrap().contains_key("type"));
let result = registry.validate_json_resource_with_context(
"User",
&user_email_missing_value,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::MissingRequiredSubAttribute {
attribute,
sub_attribute,
}) => {
assert_eq!(attribute, "emails");
assert_eq!(sub_attribute, "value");
}
Err(other) => panic!(
"Expected MissingRequiredSubAttribute error, got {:?}",
other
),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_missing_required_address_sub_attribute() {
let user_incomplete_address = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"addresses": [
{
"type": "work"
}
],
"meta": {
"resourceType": "User"
}
});
let address = &user_incomplete_address["addresses"][0];
assert_eq!(address["type"], "work");
assert!(!address.as_object().unwrap().contains_key("streetAddress"));
}
#[test]
fn test_invalid_canonical_value() {
let registry = SchemaRegistry::new().expect("Failed to create registry");
let user_invalid_email_type = UserBuilder::new().with_invalid_email_type().build();
let emails = user_invalid_email_type["emails"].as_array().unwrap();
let invalid_email = emails.iter().find(|email| email["type"] == "invalid-type");
assert!(invalid_email.is_some());
let result = registry.validate_json_resource_with_context(
"User",
&user_invalid_email_type,
OperationContext::Update,
);
assert!(result.is_err());
match result {
Err(ValidationError::InvalidCanonicalValue {
attribute,
value,
allowed,
}) => {
assert_eq!(attribute, "emails.type");
assert_eq!(value, "invalid-type");
assert!(allowed.contains(&"work".to_string()));
assert!(allowed.contains(&"home".to_string()));
assert!(allowed.contains(&"other".to_string()));
}
Err(other) => panic!("Expected InvalidCanonicalValue error, got {:?}", other),
Ok(_) => panic!("Expected validation to fail, but it passed"),
}
}
#[test]
fn test_invalid_phone_canonical_values() {
let user_invalid_phone_types = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"phoneNumbers": [
{
"value": "555-1234",
"type": "invalid-phone-type" },
{
"value": "555-5678",
"type": "work" }
],
"meta": {
"resourceType": "User"
}
});
let phones = user_invalid_phone_types["phoneNumbers"].as_array().unwrap();
assert_eq!(phones[0]["type"], "invalid-phone-type");
assert_eq!(phones[1]["type"], "work");
}
#[test]
fn test_valid_multi_valued_attributes() {
let valid_user = rfc_examples::user_full();
let emails = valid_user["emails"].as_array().unwrap();
assert!(emails.len() >= 1);
for email in emails {
assert!(email["value"].is_string());
assert!(email["type"].is_string());
if email["primary"] == true {
assert_eq!(email["primary"], true);
}
}
let primary_count = emails
.iter()
.filter(|email| email["primary"] == true)
.count();
assert!(primary_count <= 1, "Should have at most one primary email");
}
#[test]
fn test_valid_single_valued_attributes() {
let valid_user = rfc_examples::user_minimal();
assert!(valid_user["userName"].is_string());
assert_eq!(valid_user["userName"], "bjensen@example.com");
assert!(valid_user["id"].is_string());
let full_user = rfc_examples::user_full();
assert!(full_user["displayName"].is_string());
assert_eq!(full_user["displayName"], "Babs Jensen");
}
#[test]
fn test_valid_canonical_values() {
let user = rfc_examples::user_full();
let emails = user["emails"].as_array().unwrap();
for email in emails {
let email_type = email["type"].as_str().unwrap();
let valid_email_types = ["work", "home", "other"];
assert!(
valid_email_types.contains(&email_type),
"Invalid email type: {}",
email_type
);
}
if let Some(addresses) = user["addresses"].as_array() {
for address in addresses {
let addr_type = address["type"].as_str().unwrap();
let valid_address_types = ["work", "home", "other"];
assert!(
valid_address_types.contains(&addr_type),
"Invalid address type: {}",
addr_type
);
}
}
}
#[test]
fn test_complex_multi_valued_validation() {
let valid_group = rfc_examples::group_basic();
let members = valid_group["members"].as_array().unwrap();
for member in members {
assert!(member["value"].is_string());
assert!(member["$ref"].is_string());
assert!(member["display"].is_string());
let ref_uri = member["$ref"].as_str().unwrap();
assert!(ref_uri.starts_with("https://"));
assert!(ref_uri.contains("/Users/"));
}
}
#[test]
fn test_multi_valued_edge_cases() {
let user_empty_emails = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [], "meta": {
"resourceType": "User"
}
});
assert!(user_empty_emails["emails"].is_array());
assert_eq!(user_empty_emails["emails"].as_array().unwrap().len(), 0);
let user_single_email_array = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [
{
"value": "test@example.com",
"type": "work",
"primary": true
}
],
"meta": {
"resourceType": "User"
}
});
assert!(user_single_email_array["emails"].is_array());
assert_eq!(
user_single_email_array["emails"].as_array().unwrap().len(),
1
);
}
#[test]
fn test_multi_valued_with_null_values() {
let user_with_nulls = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": "test@example.com",
"emails": [
null, {
"value": "test@example.com",
"type": "work"
}
],
"meta": {
"resourceType": "User"
}
});
let emails = user_with_nulls["emails"].as_array().unwrap();
assert!(emails[0].is_null());
assert!(emails[1].is_object());
}
#[test]
fn test_multiple_multi_valued_errors() {
let user_multiple_errors = json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "123",
"userName": ["array", "username"], "emails": { "value": "test@example.com",
"type": "work",
"primary": true
},
"phoneNumbers": [
{
"value": "555-1234",
"type": "invalid-type", "primary": true
},
{
"value": "555-5678",
"type": "work",
"primary": true }
],
"meta": {
"resourceType": "User"
}
});
assert!(user_multiple_errors["userName"].is_array()); assert!(user_multiple_errors["emails"].is_object());
let phones = user_multiple_errors["phoneNumbers"].as_array().unwrap();
assert_eq!(phones[0]["type"], "invalid-type");
let primary_count = phones
.iter()
.filter(|phone| phone["primary"] == true)
.count();
assert_eq!(primary_count, 2); }
#[cfg(test)]
mod coverage_tests {
use super::*;
use crate::common::TestCoverage;
#[test]
fn test_multi_valued_error_coverage() {
let mut coverage = TestCoverage::new();
coverage.mark_tested(ValidationErrorCode::SingleValueForMultiValued); coverage.mark_tested(ValidationErrorCode::ArrayForSingleValued); coverage.mark_tested(ValidationErrorCode::MultiplePrimaryValues); coverage.mark_tested(ValidationErrorCode::InvalidMultiValuedStructure); coverage.mark_tested(ValidationErrorCode::MissingRequiredSubAttribute); coverage.mark_tested(ValidationErrorCode::InvalidCanonicalValue);
let multi_valued_errors = [
ValidationErrorCode::SingleValueForMultiValued,
ValidationErrorCode::ArrayForSingleValued,
ValidationErrorCode::MultiplePrimaryValues,
ValidationErrorCode::InvalidMultiValuedStructure,
ValidationErrorCode::MissingRequiredSubAttribute,
ValidationErrorCode::InvalidCanonicalValue,
];
for error in &multi_valued_errors {
assert!(
coverage.is_tested(error),
"Error {:?} not covered by tests",
error
);
}
}
#[test]
fn test_multi_valued_test_scenarios() {
let test_scenarios = [
"single_value_for_multi_valued",
"array_for_single_valued",
"invalid_multi_valued_structure",
"multiple_primary_values",
"missing_required_sub_attributes",
"invalid_canonical_values",
"empty_arrays",
"single_item_arrays",
"null_values_in_arrays",
"valid_multi_valued_structure",
"valid_canonical_values",
"valid_single_valued_structure",
];
assert!(
test_scenarios.len() >= 10,
"Should have comprehensive test scenarios"
);
let tested_attributes = [
"emails",
"addresses",
"phoneNumbers",
"groups",
"userName",
"displayName",
];
assert!(
tested_attributes.len() >= 6,
"Should test multiple attribute types"
);
}
#[test]
fn test_canonical_value_coverage() {
let canonical_value_tests = [
("emails", vec!["work", "home", "other"]),
("addresses", vec!["work", "home", "other"]),
(
"phoneNumbers",
vec!["work", "home", "mobile", "fax", "pager", "other"],
),
(
"ims",
vec!["aim", "gtalk", "icq", "xmpp", "msn", "skype", "qq", "yahoo"],
),
];
for (attr_type, expected_values) in canonical_value_tests {
assert!(
expected_values.len() >= 3,
"Should test multiple canonical values for {}",
attr_type
);
}
}
}