use super::ValidationErrorCode;
use scim_server::resource::value_objects::{ExternalId, ResourceId, SchemaUri, UserName};
use serde_json::{Value, json};
#[derive(Debug, Clone)]
pub struct UserBuilder {
data: Value,
expected_errors: Vec<ValidationErrorCode>,
}
impl UserBuilder {
pub fn new() -> Self {
Self {
data: json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "2819c223-7f76-453a-919d-413861904646",
"userName": "bjensen@example.com",
"meta": {
"resourceType": "User",
"created": "2010-01-23T04:56:22Z",
"lastModified": "2011-05-13T04:42:34Z",
"version": "W/\"3694e05e9dff590\"",
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
}),
expected_errors: Vec::new(),
}
}
pub fn new_full() -> Self {
Self {
data: json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "2819c223-7f76-453a-919d-413861904646",
"externalId": "701984",
"userName": "bjensen@example.com",
"name": {
"formatted": "Ms. Barbara J Jensen, III",
"familyName": "Jensen",
"givenName": "Barbara",
"middleName": "Jane",
"honorificPrefix": "Ms.",
"honorificSuffix": "III"
},
"displayName": "Babs Jensen",
"emails": [
{
"value": "bjensen@example.com",
"type": "work",
"primary": true
},
{
"value": "babs@jensen.org",
"type": "home"
}
],
"phoneNumbers": [
{
"value": "555-555-5555",
"type": "work"
},
{
"value": "555-555-4444",
"type": "mobile"
}
],
"active": true,
"meta": {
"resourceType": "User",
"created": "2010-01-23T04:56:22Z",
"lastModified": "2011-05-13T04:42:34Z",
"version": "W/\"a330bc54f0671c9\"",
"location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646"
}
}),
expected_errors: Vec::new(),
}
}
pub fn without_schemas(mut self) -> Self {
self.data.as_object_mut().unwrap().remove("schemas");
self.expected_errors
.push(ValidationErrorCode::MissingSchemas);
self
}
pub fn with_empty_schemas(mut self) -> Self {
self.data["schemas"] = json!([]);
self.expected_errors.push(ValidationErrorCode::EmptySchemas);
self
}
pub fn with_invalid_schema_uri(mut self) -> Self {
self.data["schemas"] = json!(["not-a-valid-uri"]);
self.expected_errors
.push(ValidationErrorCode::InvalidSchemaUri);
self
}
pub fn with_unknown_schema_uri(mut self) -> Self {
self.data["schemas"] = json!(["urn:ietf:params:scim:schemas:core:2.0:UnknownResource"]);
self.expected_errors
.push(ValidationErrorCode::UnknownSchemaUri);
self
}
pub fn with_duplicate_schema_uris(mut self) -> Self {
self.data["schemas"] = json!([
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:core:2.0:User"
]);
self.expected_errors
.push(ValidationErrorCode::DuplicateSchemaUri);
self
}
pub fn without_id(mut self) -> Self {
self.data.as_object_mut().unwrap().remove("id");
self.expected_errors.push(ValidationErrorCode::MissingId);
self
}
pub fn with_empty_id(mut self) -> Self {
self.data["id"] = json!("");
self.expected_errors.push(ValidationErrorCode::EmptyId);
self
}
pub fn with_reserved_id(mut self) -> Self {
self.data["id"] = json!("bulkId");
self.expected_errors
.push(ValidationErrorCode::InvalidIdFormat);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.data["id"] = json!(id);
self
}
pub fn with_numeric_id(mut self) -> Self {
self.data["id"] = json!(123);
self.expected_errors
.push(ValidationErrorCode::InvalidIdFormat);
self
}
pub fn with_array_id(mut self) -> Self {
self.data["id"] = json!(["123", "456"]);
self.expected_errors
.push(ValidationErrorCode::InvalidIdFormat);
self
}
pub fn with_object_id(mut self) -> Self {
self.data["id"] = json!({"nested": "value"});
self.expected_errors
.push(ValidationErrorCode::InvalidIdFormat);
self
}
pub fn with_external_id(mut self, external_id: &str) -> Self {
self.data["externalId"] = json!(external_id);
self
}
pub fn with_numeric_external_id(mut self) -> Self {
self.data["externalId"] = json!(123);
self.expected_errors
.push(ValidationErrorCode::InvalidExternalId);
self
}
pub fn with_array_external_id(mut self) -> Self {
self.data["externalId"] = json!(["ext1", "ext2"]);
self.expected_errors
.push(ValidationErrorCode::InvalidExternalId);
self
}
pub fn without_username(mut self) -> Self {
self.data.as_object_mut().unwrap().remove("userName");
self.expected_errors
.push(ValidationErrorCode::MissingRequiredAttribute);
self
}
pub fn with_invalid_username_type(mut self) -> Self {
self.data["userName"] = json!(123); self.expected_errors
.push(ValidationErrorCode::InvalidDataType);
self
}
pub fn with_invalid_meta_structure(mut self) -> Self {
self.data["meta"] = json!("not-an-object");
self.expected_errors
.push(ValidationErrorCode::InvalidMetaStructure);
self
}
pub fn with_string_meta(mut self) -> Self {
self.data["meta"] = json!("string-meta");
self.expected_errors
.push(ValidationErrorCode::InvalidMetaStructure);
self
}
pub fn with_array_meta(mut self) -> Self {
self.data["meta"] = json!(["array", "meta"]);
self.expected_errors
.push(ValidationErrorCode::InvalidMetaStructure);
self
}
pub fn without_meta_resource_type(mut self) -> Self {
if let Some(meta) = self.data["meta"].as_object_mut() {
meta.remove("resourceType");
}
self.expected_errors
.push(ValidationErrorCode::MissingResourceType);
self
}
pub fn with_invalid_meta_resource_type(mut self) -> Self {
self.data["meta"]["resourceType"] = json!("InvalidType");
self.expected_errors
.push(ValidationErrorCode::InvalidResourceType);
self
}
pub fn with_numeric_meta_resource_type(mut self) -> Self {
self.data["meta"]["resourceType"] = json!(123);
self.expected_errors
.push(ValidationErrorCode::InvalidResourceType);
self
}
pub fn with_readonly_meta_attributes(mut self) -> Self {
if let Some(meta) = self.data["meta"].as_object_mut() {
meta.insert("created".to_string(), json!("2024-01-01T00:00:00Z"));
meta.insert("lastModified".to_string(), json!("2024-01-01T00:00:00Z"));
meta.insert(
"location".to_string(),
json!("https://example.com/Users/123"),
);
meta.insert("version".to_string(), json!("W/\"123456\""));
}
self.expected_errors
.push(ValidationErrorCode::ClientProvidedMeta);
self
}
pub fn with_invalid_created_datetime(mut self) -> Self {
self.data["meta"]["created"] = json!("invalid-date");
self.expected_errors
.push(ValidationErrorCode::InvalidCreatedDateTime);
self
}
pub fn with_numeric_created_datetime(mut self) -> Self {
self.data["meta"]["created"] = json!(123456789);
self.expected_errors
.push(ValidationErrorCode::InvalidCreatedDateTime);
self
}
pub fn with_invalid_last_modified_datetime(mut self) -> Self {
self.data["meta"]["lastModified"] = json!("not-a-date");
self.expected_errors
.push(ValidationErrorCode::InvalidModifiedDateTime);
self
}
pub fn with_invalid_location_uri(mut self) -> Self {
self.data["meta"]["location"] = json!("not-a-uri");
self.expected_errors
.push(ValidationErrorCode::InvalidLocationUri);
self
}
pub fn with_invalid_version_format(mut self) -> Self {
self.data["meta"]["version"] = json!(["invalid", "version"]);
self.expected_errors
.push(ValidationErrorCode::InvalidVersionFormat);
self
}
pub fn with_invalid_boolean_active(mut self) -> Self {
self.data["active"] = json!("not-boolean"); self.expected_errors
.push(ValidationErrorCode::InvalidBooleanValue);
self
}
pub fn with_single_value_emails(mut self) -> Self {
self.data["emails"] = json!({
"value": "test@example.com",
"type": "work"
});
self.expected_errors
.push(ValidationErrorCode::SingleValueForMultiValued);
self
}
pub fn with_array_username(mut self) -> Self {
self.data["userName"] = json!(["user1", "user2"]);
self.expected_errors
.push(ValidationErrorCode::ArrayForSingleValued);
self
}
pub fn with_invalid_string_format(mut self) -> Self {
self.data["userName"] = json!(""); self.expected_errors
.push(ValidationErrorCode::InvalidStringFormat);
self
}
pub fn with_invalid_decimal_format(mut self) -> Self {
self.data["urn:example:decimal"] = json!("not-a-number");
self.expected_errors
.push(ValidationErrorCode::InvalidDecimalFormat);
self
}
pub fn with_invalid_integer_value(mut self) -> Self {
self.data["urn:example:integer"] = json!("not-an-integer");
self.expected_errors
.push(ValidationErrorCode::InvalidIntegerValue);
self
}
pub fn with_invalid_datetime_format(mut self) -> Self {
self.data["meta"]["created"] = json!("not-a-datetime");
self.expected_errors
.push(ValidationErrorCode::InvalidDateTimeFormat);
self
}
pub fn with_invalid_binary_data(mut self) -> Self {
self.data["x509Certificates"] = json!([{
"value": "not-base64!"
}]);
self.expected_errors
.push(ValidationErrorCode::InvalidBinaryData);
self
}
pub fn with_invalid_reference_uri(mut self) -> Self {
self.data["groups"] = json!([{
"value": "group-123",
"$ref": "not-a-uri"
}]);
self.expected_errors
.push(ValidationErrorCode::InvalidReferenceUri);
self
}
pub fn with_invalid_reference_type(mut self) -> Self {
self.data["groups"] = json!([{
"value": "group-123",
"$ref": "https://example.com/Groups/group-123",
"type": "invalid-type"
}]);
self.expected_errors
.push(ValidationErrorCode::InvalidReferenceType);
self
}
pub fn with_broken_reference(mut self) -> Self {
self.data["groups"] = json!([{
"value": "nonexistent-group",
"$ref": "https://example.com/Groups/nonexistent-group"
}]);
self.expected_errors
.push(ValidationErrorCode::BrokenReference);
self
}
pub fn with_multiple_primary_emails(mut self) -> Self {
self.data["emails"] = json!([
{
"value": "primary1@example.com",
"type": "work",
"primary": true
},
{
"value": "primary2@example.com",
"type": "home",
"primary": true
}
]);
self.expected_errors
.push(ValidationErrorCode::MultiplePrimaryValues);
self
}
pub fn with_invalid_email_type(mut self) -> Self {
self.data["emails"] = json!([
{
"value": "test@example.com",
"type": "invalid-type"
}
]);
self.expected_errors
.push(ValidationErrorCode::InvalidCanonicalValue);
self
}
pub fn with_invalid_name_sub_attribute_type(mut self) -> Self {
self.data["name"] = json!({
"familyName": "Jensen",
"givenName": 123 });
self.expected_errors
.push(ValidationErrorCode::InvalidSubAttributeType);
self
}
pub fn with_unknown_name_sub_attribute(mut self) -> Self {
self.data["name"] = json!({
"familyName": "Jensen",
"givenName": "Barbara",
"unknownAttribute": "unknown value"
});
self.expected_errors
.push(ValidationErrorCode::UnknownSubAttribute);
self
}
pub fn with_username(mut self, username: &str) -> Self {
self.data["userName"] = json!(username);
self
}
pub fn with_display_name(mut self, display_name: &str) -> Self {
self.data["displayName"] = json!(display_name);
self
}
pub fn with_email(mut self, email: &str, email_type: &str, primary: bool) -> Self {
let emails = self.data["emails"].as_array().cloned().unwrap_or_default();
let mut new_emails = emails;
new_emails.push(json!({
"value": email,
"type": email_type,
"primary": primary
}));
self.data["emails"] = json!(new_emails);
self
}
pub fn with_active(mut self, active: bool) -> Self {
self.data["active"] = json!(active);
self
}
pub fn with_resource_id(mut self, resource_id: ResourceId) -> Self {
self.data["id"] = json!(resource_id.as_str());
self
}
pub fn with_schema_uris(mut self, schema_uris: Vec<SchemaUri>) -> Self {
let uri_strings: Vec<String> = schema_uris
.into_iter()
.map(|uri| uri.as_str().to_string())
.collect();
self.data["schemas"] = json!(uri_strings);
self
}
pub fn with_external_id_value_object(mut self, external_id: ExternalId) -> Self {
self.data["externalId"] = json!(external_id.as_str());
self
}
pub fn with_user_name_value_object(mut self, user_name: UserName) -> Self {
self.data["userName"] = json!(user_name.as_str());
self
}
pub fn new_with_value_objects(
resource_id: ResourceId,
user_name: UserName,
schema_uris: Vec<SchemaUri>,
) -> Self {
Self::new()
.with_resource_id(resource_id)
.with_user_name_value_object(user_name)
.with_schema_uris(schema_uris)
}
pub fn extract_value_objects(
&self,
) -> (
Option<ResourceId>,
Option<UserName>,
Option<ExternalId>,
Vec<SchemaUri>,
) {
let resource_id = self
.data
.get("id")
.and_then(|v| v.as_str())
.and_then(|s| ResourceId::new(s.to_string()).ok());
let user_name = self
.data
.get("userName")
.and_then(|v| v.as_str())
.and_then(|s| UserName::new(s.to_string()).ok());
let external_id = self
.data
.get("externalId")
.and_then(|v| v.as_str())
.and_then(|s| ExternalId::new(s.to_string()).ok());
let schema_uris = self
.data
.get("schemas")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.filter_map(|s| SchemaUri::new(s.to_string()).ok())
.collect()
})
.unwrap_or_default();
(resource_id, user_name, external_id, schema_uris)
}
pub fn build(self) -> Value {
self.data
}
pub fn expected_errors(&self) -> &[ValidationErrorCode] {
&self.expected_errors
}
}
impl Default for UserBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GroupBuilder {
data: Value,
expected_errors: Vec<ValidationErrorCode>,
}
impl GroupBuilder {
pub fn new() -> Self {
Self {
data: json!({
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"id": "e9e30dba-f08f-4109-8486-d5c6a331660a",
"displayName": "Tour Guides",
"meta": {
"resourceType": "Group",
"created": "2010-01-23T04:56:22Z",
"lastModified": "2011-05-13T04:42:34Z",
"version": "W/\"3694e05e9dff592\"",
"location": "https://example.com/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a"
}
}),
expected_errors: Vec::new(),
}
}
pub fn without_display_name(mut self) -> Self {
self.data.as_object_mut().unwrap().remove("displayName");
self.expected_errors
.push(ValidationErrorCode::MissingRequiredAttribute);
self
}
pub fn with_empty_display_name(mut self) -> Self {
self.data["displayName"] = json!("");
self.expected_errors
.push(ValidationErrorCode::MissingRequiredAttribute);
self
}
pub fn with_display_name(mut self, display_name: &str) -> Self {
self.data["displayName"] = json!(display_name);
self
}
pub fn with_member(
mut self,
user_id: &str,
user_ref: &str,
display_name: Option<&str>,
) -> Self {
let members = self.data["members"].as_array().cloned().unwrap_or_default();
let mut new_members = members;
let mut member = json!({
"value": user_id,
"$ref": user_ref,
"type": "User"
});
if let Some(name) = display_name {
member["display"] = json!(name);
}
new_members.push(member);
self.data["members"] = json!(new_members);
self
}
pub fn build(self) -> Value {
self.data
}
pub fn expected_errors(&self) -> &[ValidationErrorCode] {
&self.expected_errors
}
}
impl Default for GroupBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SchemaBuilder {
data: Value,
}
impl SchemaBuilder {
pub fn new_user_schema() -> Self {
Self {
data: json!({
"id": "urn:ietf:params:scim:schemas:core:2.0:User",
"name": "User",
"description": "User Account",
"attributes": [
{
"name": "userName",
"type": "string",
"multiValued": false,
"required": true,
"caseExact": false,
"mutability": "readWrite",
"returned": "default",
"uniqueness": "server"
}
]
}),
}
}
pub fn build(self) -> Value {
self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_builder_new() {
let user = UserBuilder::new().build();
assert_eq!(
user["schemas"][0],
"urn:ietf:params:scim:schemas:core:2.0:User"
);
assert_eq!(user["userName"], "bjensen@example.com");
assert!(!user["id"].as_str().unwrap().is_empty());
}
#[test]
fn test_user_builder_without_schemas() {
let builder = UserBuilder::new().without_schemas();
let user = builder.build();
assert!(!user.as_object().unwrap().contains_key("schemas"));
let builder2 = UserBuilder::new().without_schemas();
let expected_errors = builder2.expected_errors();
assert_eq!(expected_errors, &[ValidationErrorCode::MissingSchemas]);
}
#[test]
fn test_user_builder_with_empty_schemas() {
let builder = UserBuilder::new().with_empty_schemas();
let user = builder.build();
assert_eq!(user["schemas"], json!([]));
let builder2 = UserBuilder::new().with_empty_schemas();
assert_eq!(
builder2.expected_errors(),
&[ValidationErrorCode::EmptySchemas]
);
}
#[test]
fn test_user_builder_multiple_errors() {
let builder = UserBuilder::new().without_schemas().without_username();
let expected_errors = builder.expected_errors();
assert_eq!(expected_errors.len(), 2);
assert!(expected_errors.contains(&ValidationErrorCode::MissingSchemas));
assert!(expected_errors.contains(&ValidationErrorCode::MissingRequiredAttribute));
}
#[test]
fn test_group_builder_new() {
let group = GroupBuilder::new().build();
assert_eq!(
group["schemas"][0],
"urn:ietf:params:scim:schemas:core:2.0:Group"
);
assert_eq!(group["displayName"], "Tour Guides");
}
#[test]
fn test_group_builder_without_display_name() {
let builder = GroupBuilder::new().without_display_name();
let group = builder.build();
assert!(!group.as_object().unwrap().contains_key("displayName"));
let builder2 = GroupBuilder::new().without_display_name();
assert_eq!(
builder2.expected_errors(),
&[ValidationErrorCode::MissingRequiredAttribute]
);
}
}