use scim_server::providers::StandardResourceProvider;
use scim_server::resource::{core::RequestContext, version::ConditionalResult};
use scim_server::storage::InMemoryStorage;
use serde_json::json;
use std::sync::Arc;
use tokio;
#[tokio::test]
async fn test_conditional_operations_prevent_data_loss() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let context = RequestContext::with_generated_id();
let user_data = json!({
"userName": "admin.user",
"active": true,
"department": "Engineering"
});
let created_user = provider
.create_versioned_resource("User", user_data, &context)
.await
.expect("Failed to create user");
let user_id = created_user.resource().get_id().unwrap();
let initial_version = created_user.version().clone();
let admin_a_update = json!({
"userName": "admin.user",
"active": false, "department": "Engineering",
"notes": "Disabled due to security incident"
});
let admin_b_update = json!({
"userName": "admin.user",
"active": true, "department": "Security",
"notes": "Transferred to security team"
});
let result_a = provider
.conditional_update("User", user_id, admin_a_update, &initial_version, &context)
.await
.expect("Admin A update failed");
let updated_user = match result_a {
ConditionalResult::Success(user) => user,
_ => panic!("Admin A update should have succeeded"),
};
assert_eq!(
updated_user.resource().get("active").unwrap(),
&json!(false)
);
assert_eq!(
updated_user.resource().get("notes").unwrap(),
&json!("Disabled due to security incident")
);
let new_version = updated_user.version().clone();
assert!(!new_version.matches(&initial_version));
let result_b = provider
.conditional_update(
"User",
user_id,
admin_b_update,
&initial_version, &context,
)
.await
.expect("Admin B update failed");
match result_b {
ConditionalResult::VersionMismatch(conflict) => {
assert_eq!(conflict.expected, initial_version);
assert_eq!(conflict.current, new_version);
assert!(conflict.message.contains("modified by another client"));
}
_ => panic!("Admin B should have gotten a version conflict"),
}
let final_user = provider
.get_versioned_resource("User", user_id, &context)
.await
.expect("Failed to get final user")
.expect("User should exist");
assert_eq!(final_user.resource().get("active").unwrap(), &json!(false));
assert_eq!(
final_user.resource().get("notes").unwrap(),
&json!("Disabled due to security incident")
);
assert_eq!(
final_user.resource().get("department").unwrap(),
&json!("Engineering")
);
assert_ne!(
final_user.resource().get("department").unwrap(),
&json!("Security")
);
}
#[tokio::test]
async fn test_conflict_resolution_workflow() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let context = RequestContext::with_generated_id();
let user_data = json!({
"userName": "conflict.user",
"active": true,
"role": "Developer"
});
let created_user = provider
.create_versioned_resource("User", user_data, &context)
.await
.unwrap();
let user_id = created_user.resource().get_id().unwrap();
let version_1 = created_user.version().clone();
let promotion_update = json!({
"userName": "conflict.user",
"active": true,
"role": "Manager",
"team": "Backend"
});
let result_a = provider
.conditional_update("User", user_id, promotion_update, &version_1, &context)
.await
.unwrap();
let promoted_user = match result_a {
ConditionalResult::Success(user) => user,
_ => panic!("Promotion should succeed"),
};
let _version_2 = promoted_user.version().clone();
let team_update = json!({
"userName": "conflict.user",
"active": true,
"role": "Developer", "team": "Frontend"
});
let result_b = provider
.conditional_update("User", user_id, team_update, &version_1, &context)
.await
.unwrap();
assert!(matches!(result_b, ConditionalResult::VersionMismatch(_)));
let current_user = provider
.get_versioned_resource("User", user_id, &context)
.await
.unwrap()
.unwrap();
assert_eq!(
current_user.resource().get("role").unwrap(),
&json!("Manager")
);
let informed_update = json!({
"userName": "conflict.user",
"active": true,
"role": "Manager", "team": "Frontend" });
let result_b_retry = provider
.conditional_update(
"User",
user_id,
informed_update,
current_user.version(), &context,
)
.await
.unwrap();
let final_user = match result_b_retry {
ConditionalResult::Success(user) => user,
_ => panic!("Informed update should succeed"),
};
assert_eq!(
final_user.resource().get("role").unwrap(),
&json!("Manager")
);
assert_eq!(
final_user.resource().get("team").unwrap(),
&json!("Frontend")
);
}
#[tokio::test]
async fn test_conditional_delete_prevents_accidental_deletion() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let context = RequestContext::with_generated_id();
let user_data = json!({
"userName": "delete.test",
"active": true
});
let created_user = provider
.create_versioned_resource("User", user_data, &context)
.await
.unwrap();
let user_id = created_user.resource().get_id().unwrap();
let version_1 = created_user.version().clone();
let update_data = json!({
"userName": "delete.test",
"active": true,
"importantData": "DO NOT DELETE - contains critical audit trail"
});
let update_result = provider
.conditional_update("User", user_id, update_data, &version_1, &context)
.await
.unwrap();
let updated_user = match update_result {
ConditionalResult::Success(user) => user,
_ => panic!("Update should succeed"),
};
let delete_result = provider
.conditional_delete(
"User", user_id, &version_1, &context,
)
.await
.unwrap();
match delete_result {
ConditionalResult::VersionMismatch(conflict) => {
assert_eq!(conflict.expected, version_1);
assert!(conflict.current.matches(updated_user.version()));
}
_ => panic!("Delete should have failed with version mismatch"),
}
let final_user = provider
.get_versioned_resource("User", user_id, &context)
.await
.unwrap()
.unwrap();
assert_eq!(
final_user.resource().get("importantData").unwrap(),
&json!("DO NOT DELETE - contains critical audit trail")
);
}
#[tokio::test]
async fn test_conditional_operations_performance() {
let storage = InMemoryStorage::new();
let provider = Arc::new(StandardResourceProvider::new(storage));
let context = RequestContext::with_generated_id();
let user_data = json!({
"userName": "perf.test",
"active": true,
"counter": 0
});
let created_user = provider
.create_versioned_resource("User", user_data, &context)
.await
.unwrap();
let user_id = created_user.resource().get_id().unwrap().to_string();
let start = std::time::Instant::now();
let mut current_version = created_user.version().clone();
for i in 1..=100 {
let update_data = json!({
"userName": "perf.test",
"active": true,
"counter": i
});
let result = provider
.conditional_update("User", &user_id, update_data, ¤t_version, &context)
.await
.unwrap();
match result {
ConditionalResult::Success(updated) => {
current_version = updated.version().clone();
}
_ => panic!("Sequential update {} should succeed", i),
}
}
let duration = start.elapsed();
assert!(
duration.as_millis() < 1000,
"100 updates took {:?}",
duration
);
let final_user = provider
.get_versioned_resource("User", &user_id, &context)
.await
.unwrap()
.unwrap();
assert_eq!(final_user.resource().get("counter").unwrap(), &json!(100));
}