use rustauth_core::context::create_auth_context;
use rustauth_core::db::DbFieldType;
use rustauth_core::options::RustAuthOptions;
use rustauth_scim::{scim, ScimOptions, UPSTREAM_PLUGIN_ID, VERSION};
#[test]
fn scim_public_constants_match_plugin_metadata() {
let plugin = scim(ScimOptions::default());
assert_eq!(UPSTREAM_PLUGIN_ID, "scim");
assert_eq!(plugin.id, UPSTREAM_PLUGIN_ID);
assert_eq!(plugin.version.as_deref(), Some(VERSION));
}
#[test]
fn scim_plugin_registers_snake_case_plural_schema() -> Result<(), Box<dyn std::error::Error>> {
let context = create_auth_context(RustAuthOptions {
plugins: vec![scim(ScimOptions::default())],
secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
..RustAuthOptions::default()
})?;
let table = context
.db_schema
.table("scim_provider")
.ok_or("missing scim_provider table")?;
assert_eq!(table.name, "scim_providers");
let provider_id = context.db_schema.field("scim_provider", "provider_id")?;
assert_eq!(provider_id.name, "provider_id");
assert_eq!(provider_id.field_type, DbFieldType::String);
assert!(provider_id.required);
assert!(provider_id.unique);
let scim_token = context.db_schema.field("scim_provider", "scim_token")?;
assert_eq!(scim_token.name, "scim_token");
assert_eq!(scim_token.field_type, DbFieldType::String);
assert!(scim_token.required);
assert!(scim_token.unique);
assert!(!scim_token.returned);
let organization_id = context
.db_schema
.field("scim_provider", "organization_id")?;
assert_eq!(organization_id.name, "organization_id");
assert_eq!(organization_id.field_type, DbFieldType::String);
assert!(!organization_id.required);
assert!(organization_id.index);
let user_id = context.db_schema.field("scim_provider", "user_id")?;
assert_eq!(user_id.name, "user_id");
assert_eq!(user_id.field_type, DbFieldType::String);
assert!(!user_id.required);
assert!(user_id.index);
assert!(user_id.foreign_key.is_some());
let context_without_ownership = create_auth_context(RustAuthOptions {
plugins: vec![scim(ScimOptions::default())],
secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
..RustAuthOptions::default()
})?;
let stable_user_id = context_without_ownership
.db_schema
.field("scim_provider", "user_id")?;
assert_eq!(stable_user_id.name, "user_id");
assert!(!stable_user_id.required);
let user_profile = context
.db_schema
.table("scim_user_profile")
.ok_or("missing scim_user_profile table")?;
assert_eq!(user_profile.name, "scim_user_profiles");
assert_eq!(
context
.db_schema
.field("scim_user_profile", "attributes")?
.field_type,
DbFieldType::Json
);
let group_profile = context
.db_schema
.table("scim_group_profile")
.ok_or("missing scim_group_profile table")?;
assert_eq!(group_profile.name, "scim_group_profiles");
assert_eq!(
context
.db_schema
.field("scim_group_profile", "team_id")?
.field_type,
DbFieldType::String
);
Ok(())
}
#[test]
fn scim_plugin_registers_expected_endpoint_surface() {
let plugin = scim(ScimOptions::default());
let endpoints = plugin
.endpoints
.iter()
.map(|endpoint| (endpoint.method.clone(), endpoint.path.as_str()))
.collect::<Vec<_>>();
assert!(endpoints.contains(&(http::Method::POST, "/scim/generate-token")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/list-provider-connections")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/get-provider-connection")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/delete-provider-connection")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/Users")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Users")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Users/:userId")));
assert!(endpoints.contains(&(http::Method::PUT, "/scim/v2/Users/:userId")));
assert!(endpoints.contains(&(http::Method::PATCH, "/scim/v2/Users/:userId")));
assert!(endpoints.contains(&(http::Method::DELETE, "/scim/v2/Users/:userId")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/Users/.search")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Groups")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/Groups")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Groups/:groupId")));
assert!(endpoints.contains(&(http::Method::PUT, "/scim/v2/Groups/:groupId")));
assert!(endpoints.contains(&(http::Method::PATCH, "/scim/v2/Groups/:groupId")));
assert!(endpoints.contains(&(http::Method::DELETE, "/scim/v2/Groups/:groupId")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/Groups/.search")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/.search")));
assert!(endpoints.contains(&(http::Method::POST, "/scim/v2/Bulk")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Me")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/ServiceProviderConfig")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Schemas")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/Schemas/:schemaId")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/ResourceTypes")));
assert!(endpoints.contains(&(http::Method::GET, "/scim/v2/ResourceTypes/:resourceTypeId")));
}
#[test]
fn scim_plugin_registers_endpoint_media_types_and_openapi_metadata() {
let plugin = scim(ScimOptions::default());
let expected_operation_ids = [
"generateSCIMToken",
"listSCIMProviderConnections",
"getSCIMProviderConnection",
"deleteSCIMProviderConnection",
"createSCIMUser",
"listSCIMUsers",
"getSCIMUser",
"updateSCIMUser",
"patchSCIMUser",
"deleteSCIMUser",
"searchSCIMUsers",
"createSCIMGroup",
"listSCIMGroups",
"getSCIMGroup",
"updateSCIMGroup",
"patchSCIMGroup",
"deleteSCIMGroup",
"searchSCIMGroups",
"searchSCIMResources",
"bulkSCIM",
"getSCIMMe",
"getSCIMServiceProviderConfig",
"getSCIMSchemas",
"getSCIMSchema",
"getSCIMResourceTypes",
"getSCIMResourceType",
];
let operation_ids = plugin
.endpoints
.iter()
.map(|endpoint| endpoint.options.operation_id.as_deref())
.collect::<Vec<_>>();
assert_eq!(
operation_ids,
expected_operation_ids
.iter()
.map(|operation_id| Some(*operation_id))
.collect::<Vec<_>>()
);
for endpoint in &plugin.endpoints {
assert!(
endpoint.options.operation_id.is_some(),
"{} {} should have an operation id",
endpoint.method,
endpoint.path
);
assert!(
endpoint.options.openapi.is_some(),
"{} {} should have OpenAPI metadata",
endpoint.method,
endpoint.path
);
}
let create_user = plugin
.endpoints
.iter()
.find(|endpoint| endpoint.method == http::Method::POST && endpoint.path == "/scim/v2/Users")
.expect("create SCIM user endpoint should exist");
assert_eq!(
create_user.options.allowed_media_types,
vec!["application/scim+json", "application/json"]
);
let metadata = plugin
.endpoints
.iter()
.find(|endpoint| {
endpoint.method == http::Method::GET
&& endpoint.path == "/scim/v2/ServiceProviderConfig"
})
.expect("metadata endpoint should exist");
assert!(metadata.options.allowed_media_types.is_empty());
}
#[test]
fn scim_openapi_documents_requests_and_responses() {
let plugin = scim(ScimOptions::default());
let generate_token = plugin
.endpoints
.iter()
.find(|endpoint| {
endpoint.method == http::Method::POST && endpoint.path == "/scim/generate-token"
})
.expect("generate token endpoint should exist");
let generate_openapi = generate_token
.options
.openapi
.as_ref()
.expect("generate token OpenAPI metadata");
assert!(generate_openapi.request_body.is_some());
assert!(generate_openapi.responses.contains_key("201"));
assert!(generate_openapi.responses.contains_key("400"));
assert!(generate_openapi.responses["201"]["content"]
.get("application/json")
.is_some());
assert!(generate_openapi.responses["201"]["content"]
.get("application/scim+json")
.is_none());
let create_user = plugin
.endpoints
.iter()
.find(|endpoint| endpoint.method == http::Method::POST && endpoint.path == "/scim/v2/Users")
.expect("create user endpoint should exist");
let create_user_openapi = create_user
.options
.openapi
.as_ref()
.expect("create user OpenAPI metadata");
assert!(create_user_openapi.request_body.is_some());
assert!(create_user_openapi.responses.contains_key("201"));
assert!(create_user_openapi.responses.contains_key("409"));
assert!(create_user_openapi.responses["201"]["content"]
.get("application/scim+json")
.is_some());
let patch_user = plugin
.endpoints
.iter()
.find(|endpoint| {
endpoint.method == http::Method::PATCH && endpoint.path == "/scim/v2/Users/:userId"
})
.expect("patch user endpoint should exist");
let patch_user_openapi = patch_user
.options
.openapi
.as_ref()
.expect("patch user OpenAPI metadata");
assert!(patch_user_openapi.request_body.is_some());
assert!(patch_user_openapi.responses.contains_key("204"));
let bulk = plugin
.endpoints
.iter()
.find(|endpoint| endpoint.method == http::Method::POST && endpoint.path == "/scim/v2/Bulk")
.expect("bulk endpoint should exist");
let bulk_openapi = bulk
.options
.openapi
.as_ref()
.expect("bulk OpenAPI metadata");
assert!(bulk_openapi.request_body.is_some());
assert!(bulk_openapi.responses.contains_key("200"));
let schemas = plugin
.endpoints
.iter()
.find(|endpoint| {
endpoint.method == http::Method::GET && endpoint.path == "/scim/v2/Schemas"
})
.expect("schemas endpoint should exist");
let schemas_openapi = schemas
.options
.openapi
.as_ref()
.expect("schemas OpenAPI metadata");
assert!(schemas_openapi.request_body.is_none());
assert!(schemas_openapi.responses.contains_key("200"));
}