use scim_server::{
IsolationLevel, RequestContext, ResourceProvider, StaticTenantResolver, TenantContext,
TenantPermissions, TenantResolver, providers::StandardResourceProvider,
storage::InMemoryStorage,
};
use serde_json::json;
#[tokio::test]
async fn test_enhanced_request_context_creation() {
let tenant_context = TenantContext::new("test-tenant".to_string(), "test-client".to_string());
let enhanced_context = RequestContext::with_tenant_generated_id(tenant_context.clone());
assert_eq!(enhanced_context.tenant_id(), Some("test-tenant"));
assert_eq!(enhanced_context.client_id(), Some("test-client"));
assert!(!enhanced_context.request_id.is_empty());
assert!(enhanced_context.is_multi_tenant());
assert_eq!(enhanced_context.tenant_id(), Some("test-tenant"));
}
#[tokio::test]
async fn test_tenant_resolver() {
let resolver = StaticTenantResolver::new();
let tenant_a = TenantContext::new("tenant-a".to_string(), "client-a".to_string())
.with_isolation_level(IsolationLevel::Strict);
let tenant_b = TenantContext::new("tenant-b".to_string(), "client-b".to_string());
resolver.add_tenant("api-key-a", tenant_a.clone()).await;
resolver.add_tenant("api-key-b", tenant_b.clone()).await;
let resolved_a = resolver.resolve_tenant("api-key-a").await.unwrap();
assert_eq!(resolved_a.tenant_id, "tenant-a");
assert_eq!(resolved_a.isolation_level, IsolationLevel::Strict);
let resolved_b = resolver.resolve_tenant("api-key-b").await.unwrap();
assert_eq!(resolved_b.tenant_id, "tenant-b");
assert_eq!(resolved_b.isolation_level, IsolationLevel::Standard);
let invalid_result = resolver.resolve_tenant("invalid-key").await;
assert!(invalid_result.is_err());
assert!(resolver.validate_tenant("tenant-a").await.unwrap());
assert!(!resolver.validate_tenant("nonexistent").await.unwrap());
}
#[tokio::test]
async fn test_multi_tenant_provider_functionality() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let tenant_context = TenantContext::new("db-test".to_string(), "client".to_string());
let context = RequestContext::with_tenant_generated_id(tenant_context);
let user_data = json!({
"userName": "dbuser",
"displayName": "Database User",
"emails": [{"value": "db@example.com", "primary": true}]
});
let created_user = provider
.create_resource("User", user_data, &context)
.await
.unwrap();
assert_eq!(created_user.resource().get_username(), Some("dbuser"));
assert!(created_user.resource().get_id().is_some());
let users = provider
.list_resources("User", None, &context)
.await
.unwrap();
assert_eq!(users.len(), 1);
assert_eq!(users[0].resource().get_username(), Some("dbuser"));
let user_id = created_user.resource().get_id().unwrap();
let updated_data = json!({
"id": user_id,
"userName": "dbuser",
"displayName": "Updated Database User"
});
let updated_user = provider
.update_resource("User", user_id, updated_data, None, &context)
.await
.unwrap();
assert_eq!(
updated_user.resource().get_attribute("displayName"),
Some(&json!("Updated Database User"))
);
provider
.delete_resource("User", user_id, None, &context)
.await
.unwrap();
let deleted_user = provider
.get_resource("User", user_id, &context)
.await
.unwrap();
assert!(deleted_user.is_none());
}
#[tokio::test]
async fn test_multi_tenant_isolation() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let tenant_a_context = TenantContext::new("tenant-a".to_string(), "client-a".to_string());
let context_a = RequestContext::with_tenant_generated_id(tenant_a_context);
let tenant_b_context = TenantContext::new("tenant-b".to_string(), "client-b".to_string());
let context_b = RequestContext::with_tenant_generated_id(tenant_b_context);
let user_data_a = json!({"id": "user1", "userName": "john", "displayName": "John from A"});
let user_data_b = json!({"id": "user1", "userName": "john", "displayName": "John from B"});
provider
.create_resource("User", user_data_a, &context_a)
.await
.unwrap();
provider
.create_resource("User", user_data_b, &context_b)
.await
.unwrap();
let user_a = provider
.get_resource("User", "user1", &context_a)
.await
.unwrap()
.unwrap();
assert_eq!(
user_a.resource().get_attribute("displayName"),
Some(&json!("John from A"))
);
let user_b = provider
.get_resource("User", "user1", &context_b)
.await
.unwrap()
.unwrap();
assert_eq!(
user_b.resource().get_attribute("displayName"),
Some(&json!("John from B"))
);
let _cross_access_result = provider
.get_resource("User", "user1", &context_a)
.await
.unwrap();
let list_a = provider
.list_resources("User", None, &context_a)
.await
.unwrap();
let list_b = provider
.list_resources("User", None, &context_b)
.await
.unwrap();
assert_eq!(list_a.len(), 1);
assert_eq!(list_b.len(), 1);
}
#[tokio::test]
async fn test_tenant_permissions_and_limits() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let mut permissions = TenantPermissions::default();
permissions.can_delete = false;
permissions.max_users = Some(1);
let tenant_context = TenantContext::new("restricted".to_string(), "client".to_string())
.with_permissions(permissions);
let context = RequestContext::with_tenant_generated_id(tenant_context);
let user1_data = json!({"id": "user1", "userName": "user1"});
let result1 = provider.create_resource("User", user1_data, &context).await;
assert!(result1.is_ok());
let user2_data = json!({"id": "user2", "userName": "user2"});
let result2 = provider.create_resource("User", user2_data, &context).await;
assert!(result2.is_err());
let delete_result = provider
.delete_resource("User", "user1", None, &context)
.await;
assert!(delete_result.is_err());
}
#[tokio::test]
async fn test_end_to_end_workflow() {
let resolver = StaticTenantResolver::new();
let tenant_context = TenantContext::new("e2e-tenant".to_string(), "e2e-client".to_string());
resolver
.add_tenant("e2e-api-key", tenant_context.clone())
.await;
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let resolved_tenant = resolver.resolve_tenant("e2e-api-key").await.unwrap();
assert_eq!(resolved_tenant.tenant_id, "e2e-tenant");
let context = RequestContext::with_tenant_generated_id(resolved_tenant);
let user_data = json!({
"userName": "e2euser",
"displayName": "End-to-End User",
"emails": [{"value": "e2e@example.com", "primary": true}]
});
let created_user = provider
.create_resource("User", user_data, &context)
.await
.unwrap();
let user_id = created_user.resource().get_id().unwrap();
let retrieved_user = provider
.get_resource("User", user_id, &context)
.await
.unwrap()
.unwrap();
assert_eq!(retrieved_user.resource().get_username(), Some("e2euser"));
let updated_data = json!({
"id": user_id,
"userName": "e2euser",
"displayName": "Updated E2E User"
});
let updated_user = provider
.update_resource("User", user_id, updated_data, None, &context)
.await
.unwrap();
assert_eq!(
updated_user.resource().get_attribute("displayName"),
Some(&json!("Updated E2E User"))
);
let users = provider
.list_resources("User", None, &context)
.await
.unwrap();
assert_eq!(users.len(), 1);
let found_users = provider
.find_resources_by_attribute("User", "userName", "e2euser", &context)
.await
.unwrap();
assert!(!found_users.is_empty());
let found_user = &found_users[0];
assert_eq!(found_user.resource().get_id(), Some(user_id));
provider
.delete_resource("User", user_id, None, &context)
.await
.unwrap();
let deleted_user = provider
.get_resource("User", user_id, &context)
.await
.unwrap();
assert!(deleted_user.is_none());
}
#[tokio::test]
async fn test_backward_compatibility() {
let context = RequestContext::new("test-request".to_string());
assert_eq!(context.request_id, "test-request");
assert!(!context.is_multi_tenant());
assert!(context.tenant_id().is_none());
let tenant_context = TenantContext::new("compat-tenant".to_string(), "client".to_string());
let enhanced_context = RequestContext::with_tenant("test-request".to_string(), tenant_context);
assert!(enhanced_context.is_multi_tenant());
assert_eq!(enhanced_context.tenant_id(), Some("compat-tenant"));
let converted = enhanced_context.clone();
assert!(converted.is_multi_tenant());
assert_eq!(converted.tenant_id(), Some("compat-tenant"));
}
#[tokio::test]
async fn test_multi_tenant_performance() {
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
let tenant_count = 5;
let users_per_tenant = 10;
for tenant_idx in 0..tenant_count {
let tenant_id = format!("perf-tenant-{}", tenant_idx);
let tenant_context = TenantContext::new(tenant_id.clone(), "client".to_string());
let context = RequestContext::with_tenant_generated_id(tenant_context);
for user_idx in 0..users_per_tenant {
let user_data = json!({
"id": format!("user-{}", user_idx),
"userName": format!("user{}_{}", tenant_idx, user_idx),
"displayName": format!("User {} from Tenant {}", user_idx, tenant_idx)
});
provider
.create_resource("User", user_data, &context)
.await
.unwrap();
}
}
for tenant_idx in 0..tenant_count {
let tenant_id = format!("perf-tenant-{}", tenant_idx);
let tenant_context = TenantContext::new(tenant_id.clone(), "client".to_string());
let context = RequestContext::with_tenant_generated_id(tenant_context);
let users = provider
.list_resources("User", None, &context)
.await
.unwrap();
assert_eq!(users.len(), users_per_tenant);
}
let stats = provider.get_stats().await;
assert_eq!(stats.tenant_count, tenant_count);
assert_eq!(stats.total_resources, tenant_count * users_per_tenant);
}