Skip to content

Commit 452064c

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 <[email protected]>
1 parent f528b0a commit 452064c

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

0 commit comments

Comments
 (0)