use scim_server::error::ValidationError;
use scim_server::resource::value_objects::{
CompositeValidator, CompositeValidatorChain, EmailAddress, EmailConsistencyValidator,
ExtensionAttributeValue, ExtensionCollection, GenericMultiValuedAttribute,
IdentityConsistencyValidator, Name, NameConsistencyValidator, ResourceId, SchemaConstructible,
SchemaUri, UniquePrimaryValidator, UserName, UserNameUniquenessValidator, ValueObject,
ValueObjectFactory,
};
use scim_server::schema::types::{AttributeDefinition, AttributeType, Mutability, Uniqueness};
use serde_json::{Value, json};
fn create_string_definition(name: &str, required: bool) -> AttributeDefinition {
AttributeDefinition {
name: name.to_string(),
data_type: AttributeType::String,
multi_valued: false,
required,
case_exact: false,
mutability: Mutability::ReadWrite,
uniqueness: Uniqueness::None,
canonical_values: vec![],
sub_attributes: vec![],
returned: None,
}
}
fn create_complex_definition(name: &str) -> AttributeDefinition {
AttributeDefinition {
name: name.to_string(),
data_type: AttributeType::Complex,
multi_valued: false,
required: false,
case_exact: false,
mutability: Mutability::ReadWrite,
uniqueness: Uniqueness::None,
canonical_values: vec![],
sub_attributes: vec![],
returned: None,
}
}
#[test]
fn test_value_object_factory_basic_creation() {
let factory = ValueObjectFactory::new();
let id_def = create_string_definition("id", true);
let id_value = Value::String("test-id-123".to_string());
let result = factory.create_value_object(&id_def, &id_value);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.attribute_name(), "id");
assert_eq!(obj.attribute_type(), AttributeType::String);
assert_eq!(obj.as_json_value(), id_value);
let username_def = create_string_definition("userName", true);
let username_value = Value::String("testuser@example.com".to_string());
let result = factory.create_value_object(&username_def, &username_value);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.attribute_name(), "userName");
assert_eq!(obj.attribute_type(), AttributeType::String);
}
#[test]
fn test_value_object_factory_complex_attributes() {
let factory = ValueObjectFactory::new();
let name_def = create_complex_definition("name");
let name_value = json!({
"formatted": "John Doe",
"givenName": "John",
"familyName": "Doe"
});
let result = factory.create_value_object(&name_def, &name_value);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.attribute_name(), "name");
assert_eq!(obj.attribute_type(), AttributeType::Complex);
if let Some(name) = obj.as_any().downcast_ref::<Name>() {
assert_eq!(name.formatted(), Some("John Doe"));
assert_eq!(name.given_name(), Some("John"));
assert_eq!(name.family_name(), Some("Doe"));
} else {
panic!("Failed to downcast to Name");
}
}
#[test]
fn test_value_object_factory_multi_valued_attributes() {
let factory = ValueObjectFactory::new();
let mut email_def = create_string_definition("emails", false);
email_def.multi_valued = true;
email_def.data_type = AttributeType::Complex;
let emails_value = json!([
{
"value": "john.doe@example.com",
"type": "work",
"primary": true
},
{
"value": "j.doe@work.com",
"type": "home",
"primary": false
}
]);
let result = factory.create_value_object(&email_def, &emails_value);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.attribute_name(), "emails");
assert_eq!(obj.attribute_type(), AttributeType::Complex);
if let Some(multi_valued) = obj.as_any().downcast_ref::<GenericMultiValuedAttribute>() {
assert_eq!(multi_valued.values().len(), 2);
} else {
panic!("Failed to downcast to GenericMultiValuedAttribute");
}
}
#[test]
fn test_value_object_factory_extension_attributes() {
let factory = ValueObjectFactory::new();
let custom_def = create_string_definition("customAttribute", false);
let custom_value = Value::String("custom-value".to_string());
let result = factory.create_value_object(&custom_def, &custom_value);
assert!(result.is_ok());
let obj = result.unwrap();
assert_eq!(obj.attribute_name(), "customAttribute");
let json_result = obj.to_json();
assert!(json_result.is_ok());
assert_eq!(json_result.unwrap(), custom_value);
}
#[test]
fn test_extension_attribute_value_creation() {
let schema_uri =
SchemaUri::new("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User".to_string())
.unwrap();
let definition = AttributeDefinition {
name: "employeeNumber".to_string(),
data_type: AttributeType::String,
multi_valued: false,
required: false,
case_exact: false,
mutability: Mutability::ReadWrite,
uniqueness: Uniqueness::None,
canonical_values: vec![],
sub_attributes: vec![],
returned: None,
};
let value = Value::String("EMP-12345".to_string());
let result = ExtensionAttributeValue::new(
schema_uri.clone(),
"employeeNumber".to_string(),
value.clone(),
Some(definition),
);
assert!(result.is_ok());
let ext_attr = result.unwrap();
assert_eq!(ext_attr.schema_uri(), &schema_uri);
assert_eq!(ext_attr.attribute_name(), "employeeNumber");
assert_eq!(ext_attr.value(), &value);
assert_eq!(ext_attr.attribute_type(), AttributeType::String);
}
#[test]
fn test_extension_attribute_validation() {
let schema_uri = SchemaUri::new("urn:test:extension:schema".to_string()).unwrap();
let string_def = AttributeDefinition {
name: "testAttribute".to_string(),
data_type: AttributeType::String,
multi_valued: false,
required: false,
case_exact: false,
mutability: Mutability::ReadWrite,
uniqueness: Uniqueness::None,
canonical_values: vec![],
sub_attributes: vec![],
returned: None,
};
let valid_result = ExtensionAttributeValue::new(
schema_uri.clone(),
"testAttribute".to_string(),
Value::String("valid".to_string()),
Some(string_def.clone()),
);
assert!(valid_result.is_ok());
let invalid_result = ExtensionAttributeValue::new(
schema_uri.clone(),
"testAttribute".to_string(),
Value::Number(serde_json::Number::from(123)),
Some(string_def),
);
assert!(invalid_result.is_err());
}
#[test]
fn test_extension_collection() {
let mut collection = ExtensionCollection::new();
let schema_uri = SchemaUri::new("urn:test:extension:custom".to_string()).unwrap();
let ext_attr = ExtensionAttributeValue::new_unchecked(
schema_uri.clone(),
"customField".to_string(),
Value::String("test-value".to_string()),
);
collection.add_attribute(ext_attr);
assert_eq!(collection.len(), 1);
assert!(!collection.is_empty());
assert!(
collection
.get_attribute(schema_uri.as_str(), "customField")
.is_some()
);
let json = collection.to_json().unwrap();
let restored = ExtensionCollection::from_json(&json).unwrap();
assert_eq!(collection.len(), restored.len());
}
#[test]
fn test_unique_primary_validator() {
let validator = UniquePrimaryValidator::new();
let objects: Vec<Box<dyn ValueObject>> = vec![
Box::new(ResourceId::new("test-id".to_string()).unwrap()),
Box::new(UserName::new("testuser".to_string()).unwrap()),
];
let result = validator.validate_composite(&objects);
assert!(result.is_ok());
assert!(validator.applies_to(&["emails".to_string()]));
assert!(!validator.applies_to(&["id".to_string()]));
}
#[test]
fn test_username_uniqueness_validator() {
let validator = UserNameUniquenessValidator::new(true)
.with_reserved_names(vec!["test".to_string(), "demo".to_string()]);
let valid_objects: Vec<Box<dyn ValueObject>> =
vec![Box::new(UserName::new("validuser".to_string()).unwrap())];
assert!(validator.validate_composite(&valid_objects).is_ok());
let reserved_objects: Vec<Box<dyn ValueObject>> =
vec![Box::new(UserName::new("admin".to_string()).unwrap())];
assert!(validator.validate_composite(&reserved_objects).is_err());
let custom_reserved: Vec<Box<dyn ValueObject>> =
vec![Box::new(UserName::new("test".to_string()).unwrap())];
assert!(validator.validate_composite(&custom_reserved).is_err());
let short_objects: Vec<Box<dyn ValueObject>> =
vec![Box::new(UserName::new("ab".to_string()).unwrap())];
assert!(validator.validate_composite(&short_objects).is_err());
}
#[test]
fn test_email_consistency_validator() {
let validator = EmailConsistencyValidator::new()
.with_allowed_domains(vec!["example.com".to_string(), "work.com".to_string()]);
let valid_objects: Vec<Box<dyn ValueObject>> = vec![Box::new(
EmailAddress::new("user@example.com".to_string(), None, None, None).unwrap(),
)];
assert!(validator.validate_composite(&valid_objects).is_ok());
let invalid_objects: Vec<Box<dyn ValueObject>> = vec![Box::new(
EmailAddress::new("user@invalid.com".to_string(), None, None, None).unwrap(),
)];
assert!(validator.validate_composite(&invalid_objects).is_err());
let subdomain_objects: Vec<Box<dyn ValueObject>> = vec![Box::new(
EmailAddress::new("user@mail.example.com".to_string(), None, None, None).unwrap(),
)];
assert!(validator.validate_composite(&subdomain_objects).is_ok());
}
#[test]
fn test_identity_consistency_validator() {
let validator = IdentityConsistencyValidator::new()
.with_external_id_requirement(true)
.with_id_format_validation(true);
let incomplete_objects: Vec<Box<dyn ValueObject>> = vec![
Box::new(ResourceId::new("test-id".to_string()).unwrap()),
Box::new(UserName::new("testuser".to_string()).unwrap()),
];
assert!(validator.validate_composite(&incomplete_objects).is_err());
let complete_objects: Vec<Box<dyn ValueObject>> = vec![
Box::new(ResourceId::new("550e8400-e29b-41d4-a716-446655440000".to_string()).unwrap()),
Box::new(UserName::new("testuser".to_string()).unwrap()),
Box::new(
scim_server::resource::value_objects::ExternalId::new("ext123".to_string()).unwrap(),
),
];
assert!(validator.validate_composite(&complete_objects).is_ok());
}
#[test]
fn test_name_consistency_validator() {
let validator = NameConsistencyValidator::new()
.with_name_component_requirement(true)
.with_formatted_name_validation(true);
let valid_name = Name::new_simple("John".to_string(), "Doe".to_string()).unwrap();
let valid_objects: Vec<Box<dyn ValueObject>> = vec![Box::new(valid_name)];
assert!(validator.validate_composite(&valid_objects).is_ok());
assert!(validator.applies_to(&["name".to_string()]));
assert!(!validator.applies_to(&["userName".to_string()]));
}
#[test]
fn test_composite_validator_chain() {
let chain = CompositeValidatorChain::with_default_validators();
let objects: Vec<Box<dyn ValueObject>> = vec![
Box::new(ResourceId::new("550e8400-e29b-41d4-a716-446655440000".to_string()).unwrap()),
Box::new(UserName::new("validuser".to_string()).unwrap()),
Box::new(EmailAddress::new("test@example.com".to_string(), None, None, None).unwrap()),
Box::new(Name::new_simple("John".to_string(), "Doe".to_string()).unwrap()),
];
let result = chain.validate_composite(&objects);
match result {
Ok(_) => {
}
Err(_) => {
}
}
let deps = chain.dependent_attributes();
assert!(!deps.is_empty());
assert!(deps.contains(&"userName".to_string()));
assert!(deps.contains(&"emails".to_string()));
}
#[test]
fn test_value_object_factory_batch_creation() {
let factory = ValueObjectFactory::new();
let definitions = vec![
create_string_definition("id", true),
create_string_definition("userName", true),
create_complex_definition("name"),
];
let json_obj = serde_json::Map::from_iter([
("id".to_string(), Value::String("test-123".to_string())),
(
"userName".to_string(),
Value::String("testuser".to_string()),
),
(
"name".to_string(),
json!({
"givenName": "Test",
"familyName": "User"
}),
),
]);
let result = factory.create_value_objects_from_json(&definitions, &json_obj);
assert!(result.is_ok());
let objects = result.unwrap();
assert_eq!(objects.len(), 3);
let id_obj = &objects[0];
assert_eq!(id_obj.attribute_name(), "id");
assert_eq!(id_obj.attribute_type(), AttributeType::String);
let username_obj = &objects[1];
assert_eq!(username_obj.attribute_name(), "userName");
assert_eq!(username_obj.attribute_type(), AttributeType::String);
let name_obj = &objects[2];
assert_eq!(name_obj.attribute_name(), "name");
assert_eq!(name_obj.attribute_type(), AttributeType::Complex);
}
#[test]
fn test_value_object_factory_missing_required_attribute() {
let factory = ValueObjectFactory::new();
let definitions = vec![
create_string_definition("id", true), create_string_definition("userName", false), ];
let json_obj = serde_json::Map::from_iter([
(
"userName".to_string(),
Value::String("testuser".to_string()),
),
]);
let result = factory.create_value_objects_from_json(&definitions, &json_obj);
assert!(result.is_err());
match result.unwrap_err() {
ValidationError::RequiredAttributeMissing(attr) => {
assert_eq!(attr, "id");
}
other => panic!("Expected RequiredAttributeMissing, got: {:?}", other),
}
}
#[test]
fn test_schema_constructible_trait_implementations() {
let id_def = create_string_definition("id", true);
assert!(ResourceId::can_construct_from(&id_def));
assert_eq!(ResourceId::constructor_priority(), 100);
let id_value = Value::String("test-id".to_string());
let id_result = ResourceId::from_schema_and_value(&id_def, &id_value);
assert!(id_result.is_ok());
assert_eq!(id_result.unwrap().as_str(), "test-id");
let username_def = create_string_definition("userName", true);
assert!(UserName::can_construct_from(&username_def));
let username_value = Value::String("testuser".to_string());
let username_result = UserName::from_schema_and_value(&username_def, &username_value);
assert!(username_result.is_ok());
assert_eq!(username_result.unwrap().as_str(), "testuser");
let name_def = create_complex_definition("name");
assert!(Name::can_construct_from(&name_def));
let name_value = json!({
"givenName": "John",
"familyName": "Doe"
});
let name_result = Name::from_schema_and_value(&name_def, &name_value);
assert!(name_result.is_ok());
}
#[test]
fn test_value_object_trait_implementations() {
let id = ResourceId::new("test-id".to_string()).unwrap();
assert_eq!(id.attribute_type(), AttributeType::String);
assert_eq!(id.attribute_name(), "id");
let id_definition = create_string_definition("id", true);
assert!(id.validate_against_schema(&id_definition).is_ok());
assert!(id.supports_definition(&id_definition));
let username = UserName::new("testuser".to_string()).unwrap();
assert_eq!(username.attribute_type(), AttributeType::String);
assert_eq!(username.attribute_name(), "userName");
let name = Name::new_simple("John".to_string(), "Doe".to_string()).unwrap();
assert_eq!(name.attribute_type(), AttributeType::Complex);
assert_eq!(name.attribute_name(), "name");
let name_definition = create_complex_definition("name");
assert!(name.validate_against_schema(&name_definition).is_ok());
}
#[test]
fn test_end_to_end_schema_driven_workflow() {
let factory = ValueObjectFactory::new();
let validator_chain = CompositeValidatorChain::with_default_validators();
let core_definitions = vec![
create_string_definition("id", true),
create_string_definition("userName", true),
create_complex_definition("name"),
];
let user_json = json!({
"id": "550e8400-e29b-41d4-a716-446655440000",
"userName": "bjensen",
"name": {
"formatted": "Ms. Barbara J Jensen, III",
"familyName": "Jensen",
"givenName": "Barbara"
}
});
let json_obj = user_json.as_object().unwrap();
let core_objects = factory
.create_value_objects_from_json(&core_definitions, json_obj)
.unwrap();
assert_eq!(core_objects.len(), 3);
let mut extension_collection = ExtensionCollection::new();
let enterprise_schema =
SchemaUri::new("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User".to_string())
.unwrap();
let employee_number = ExtensionAttributeValue::new_unchecked(
enterprise_schema,
"employeeNumber".to_string(),
Value::String("701984".to_string()),
);
extension_collection.add_attribute(employee_number);
let validation_result = validator_chain.validate_composite(&core_objects);
match validation_result {
Ok(_) => {
println!("All validations passed!");
}
Err(e) => {
println!("Some validations failed (expected): {:?}", e);
}
}
assert!(!extension_collection.is_empty());
assert_eq!(extension_collection.len(), 1);
let extension_json = extension_collection.to_json().unwrap();
assert!(extension_json.is_object());
println!("Schema-driven end-to-end workflow completed successfully!");
}