Skip to content

Commit 32e7577

Browse files
david-crespoclaude
andcommitted
add integration test for SCIM list groups with members
This test verifies that listing groups via the SCIM API correctly includes member information for each group, including groups with multiple members, groups with a single member, and groups with no members. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1c5ddcd commit 32e7577

File tree

1 file changed

+184
-0
lines changed
  • nexus/tests/integration_tests

1 file changed

+184
-0
lines changed

nexus/tests/integration_tests/scim.rs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,3 +2185,187 @@ async fn test_scim_list_users_with_groups(cptestctx: &ControlPlaneTestContext) {
21852185
let user5 = find_user(&users[4].id);
21862186
assert!(user5.groups.is_none());
21872187
}
2188+
2189+
#[nexus_test]
2190+
async fn test_scim_list_groups_with_members(cptestctx: &ControlPlaneTestContext) {
2191+
let client = &cptestctx.external_client;
2192+
let nexus = &cptestctx.server.server_context().nexus;
2193+
let opctx = OpContext::for_tests(
2194+
cptestctx.logctx.log.new(o!()),
2195+
nexus.datastore().clone(),
2196+
);
2197+
2198+
const SILO_NAME: &str = "saml-scim-silo";
2199+
create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim)
2200+
.await;
2201+
2202+
grant_iam(
2203+
client,
2204+
&format!("/v1/system/silos/{SILO_NAME}"),
2205+
shared::SiloRole::Admin,
2206+
opctx.authn.actor().unwrap().silo_user_id().unwrap(),
2207+
AuthnMode::PrivilegedUser,
2208+
)
2209+
.await;
2210+
2211+
let created_token: views::ScimClientBearerTokenValue =
2212+
object_create_no_body(
2213+
client,
2214+
&format!("/v1/system/scim/tokens?silo={}", SILO_NAME),
2215+
)
2216+
.await;
2217+
2218+
// Create 5 users
2219+
let mut users = Vec::new();
2220+
for i in 1..=5 {
2221+
let user: scim2_rs::User = NexusRequest::new(
2222+
RequestBuilder::new(client, Method::POST, "/scim/v2/Users")
2223+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2224+
.header(
2225+
http::header::AUTHORIZATION,
2226+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2227+
)
2228+
.allow_non_dropshot_errors()
2229+
.raw_body(Some(
2230+
serde_json::to_string(&serde_json::json!({
2231+
"userName": format!("user{}", i),
2232+
"externalId": format!("user{}@example.com", i),
2233+
}))
2234+
.unwrap(),
2235+
))
2236+
.expect_status(Some(StatusCode::CREATED)),
2237+
)
2238+
.execute_and_parse_unwrap()
2239+
.await;
2240+
users.push(user);
2241+
}
2242+
2243+
// Create 3 groups with various membership patterns:
2244+
// - group1: user1, user2, user3
2245+
// - group2: user1, user4
2246+
// - group3: no members
2247+
let group1: scim2_rs::Group = NexusRequest::new(
2248+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2249+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2250+
.header(
2251+
http::header::AUTHORIZATION,
2252+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2253+
)
2254+
.allow_non_dropshot_errors()
2255+
.raw_body(Some(
2256+
serde_json::to_string(&serde_json::json!({
2257+
"displayName": "group1",
2258+
"externalId": "group1@example.com",
2259+
"members": [
2260+
{"value": users[0].id},
2261+
{"value": users[1].id},
2262+
{"value": users[2].id},
2263+
],
2264+
}))
2265+
.unwrap(),
2266+
))
2267+
.expect_status(Some(StatusCode::CREATED)),
2268+
)
2269+
.execute_and_parse_unwrap()
2270+
.await;
2271+
2272+
let group2: scim2_rs::Group = NexusRequest::new(
2273+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2274+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2275+
.header(
2276+
http::header::AUTHORIZATION,
2277+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2278+
)
2279+
.allow_non_dropshot_errors()
2280+
.raw_body(Some(
2281+
serde_json::to_string(&serde_json::json!({
2282+
"displayName": "group2",
2283+
"externalId": "group2@example.com",
2284+
"members": [
2285+
{"value": users[0].id},
2286+
{"value": users[3].id},
2287+
],
2288+
}))
2289+
.unwrap(),
2290+
))
2291+
.expect_status(Some(StatusCode::CREATED)),
2292+
)
2293+
.execute_and_parse_unwrap()
2294+
.await;
2295+
2296+
let group3: scim2_rs::Group = NexusRequest::new(
2297+
RequestBuilder::new(client, Method::POST, "/scim/v2/Groups")
2298+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2299+
.header(
2300+
http::header::AUTHORIZATION,
2301+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2302+
)
2303+
.allow_non_dropshot_errors()
2304+
.raw_body(Some(
2305+
serde_json::to_string(&serde_json::json!({
2306+
"displayName": "group3",
2307+
"externalId": "group3@example.com",
2308+
}))
2309+
.unwrap(),
2310+
))
2311+
.expect_status(Some(StatusCode::CREATED)),
2312+
)
2313+
.execute_and_parse_unwrap()
2314+
.await;
2315+
2316+
// List all groups and verify members
2317+
let response: scim2_rs::ListResponse = NexusRequest::new(
2318+
RequestBuilder::new(client, Method::GET, "/scim/v2/Groups")
2319+
.header(http::header::CONTENT_TYPE, "application/scim+json")
2320+
.header(
2321+
http::header::AUTHORIZATION,
2322+
format!("Bearer oxide-scim-{}", created_token.bearer_token),
2323+
)
2324+
.allow_non_dropshot_errors()
2325+
.expect_status(Some(StatusCode::OK)),
2326+
)
2327+
.execute_and_parse_unwrap()
2328+
.await;
2329+
2330+
let returned_groups: Vec<scim2_rs::Group> = serde_json::from_value(
2331+
serde_json::to_value(&response.resources).unwrap(),
2332+
)
2333+
.unwrap();
2334+
2335+
// Find our created groups in the response
2336+
let find_group = |group_id: &str| {
2337+
returned_groups
2338+
.iter()
2339+
.find(|g| g.id == group_id)
2340+
.expect("group should be in list")
2341+
};
2342+
2343+
// group1 should have 3 members
2344+
let returned_group1 = find_group(&group1.id);
2345+
assert!(returned_group1.members.is_some());
2346+
let group1_members = returned_group1.members.as_ref().unwrap();
2347+
assert_eq!(group1_members.len(), 3);
2348+
let group1_member_ids: std::collections::HashSet<_> = group1_members
2349+
.iter()
2350+
.map(|m| m.value.as_ref().unwrap().as_str())
2351+
.collect();
2352+
assert!(group1_member_ids.contains(users[0].id.as_str()));
2353+
assert!(group1_member_ids.contains(users[1].id.as_str()));
2354+
assert!(group1_member_ids.contains(users[2].id.as_str()));
2355+
2356+
// group2 should have 2 members
2357+
let returned_group2 = find_group(&group2.id);
2358+
assert!(returned_group2.members.is_some());
2359+
let group2_members = returned_group2.members.as_ref().unwrap();
2360+
assert_eq!(group2_members.len(), 2);
2361+
let group2_member_ids: std::collections::HashSet<_> = group2_members
2362+
.iter()
2363+
.map(|m| m.value.as_ref().unwrap().as_str())
2364+
.collect();
2365+
assert!(group2_member_ids.contains(users[0].id.as_str()));
2366+
assert!(group2_member_ids.contains(users[3].id.as_str()));
2367+
2368+
// group3 should have no members
2369+
let returned_group3 = find_group(&group3.id);
2370+
assert!(returned_group3.members.is_none());
2371+
}

0 commit comments

Comments
 (0)