Skip to content

Commit b030da1

Browse files
committed
test: self-service group creation cap including concurrency at the boundary
1 parent 51e96c9 commit b030da1

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

aruna/tests/self_service_groups.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
mod shared;
2+
3+
use aruna_api::routes::groups::CreateGroupRequest;
4+
use aruna_core::UserId;
5+
use reqwest::StatusCode;
6+
use shared::{TestResult, create_bearer_token, spawn_seed_node};
7+
use ulid::Ulid;
8+
9+
async fn post_group(base_url: &str, token: &str, name: &str) -> TestResult<reqwest::Response> {
10+
Ok(reqwest::Client::new()
11+
.post(format!("{base_url}/api/v1/groups"))
12+
.bearer_auth(token)
13+
.json(&CreateGroupRequest {
14+
name: name.to_string(),
15+
})
16+
.send()
17+
.await?)
18+
}
19+
20+
#[tokio::test]
21+
async fn fresh_user_creates_groups_up_to_the_cap() -> TestResult<()> {
22+
let seed = spawn_seed_node().await?;
23+
let member_token = create_bearer_token(
24+
seed.context.as_ref(),
25+
UserId::local(Ulid::new(), seed.realm_id),
26+
seed.realm_id,
27+
seed.capabilities.clone(),
28+
)
29+
.await?;
30+
31+
// The default realm quota caps self-service creation at 3 owned groups.
32+
for i in 0..3 {
33+
let response = post_group(&seed.base_url, &member_token, &format!("workspace-{i}")).await?;
34+
assert_eq!(response.status(), StatusCode::CREATED, "create {i}");
35+
}
36+
let response = post_group(&seed.base_url, &member_token, "workspace-over-cap").await?;
37+
assert_eq!(response.status(), StatusCode::CONFLICT);
38+
39+
// Realm admins are exempt from the cap.
40+
let admin_token = create_bearer_token(
41+
seed.context.as_ref(),
42+
seed.user_id,
43+
seed.realm_id,
44+
seed.capabilities.clone(),
45+
)
46+
.await?;
47+
for i in 0..4 {
48+
let response = post_group(&seed.base_url, &admin_token, &format!("admin-{i}")).await?;
49+
assert_eq!(response.status(), StatusCode::CREATED, "admin create {i}");
50+
}
51+
52+
seed.shutdown().await;
53+
Ok(())
54+
}
55+
56+
#[tokio::test]
57+
async fn concurrent_creates_cannot_slip_past_the_cap() -> TestResult<()> {
58+
let seed = spawn_seed_node().await?;
59+
let member_token = create_bearer_token(
60+
seed.context.as_ref(),
61+
UserId::local(Ulid::new(), seed.realm_id),
62+
seed.realm_id,
63+
seed.capabilities.clone(),
64+
)
65+
.await?;
66+
67+
let attempts: Vec<_> = (0..6)
68+
.map(|i| {
69+
let base_url = seed.base_url.clone();
70+
let token = member_token.clone();
71+
tokio::spawn(async move {
72+
match post_group(&base_url, &token, &format!("race-{i}")).await {
73+
Ok(response) => Ok(response.status()),
74+
Err(error) => Err(error.to_string()),
75+
}
76+
})
77+
})
78+
.collect();
79+
let mut statuses = Vec::new();
80+
for attempt in attempts {
81+
statuses.push(attempt.await?);
82+
}
83+
84+
let created = statuses
85+
.iter()
86+
.filter(|status| matches!(status, Ok(status) if *status == StatusCode::CREATED))
87+
.count();
88+
assert!(created >= 1, "at least one create succeeds: {statuses:?}");
89+
assert!(created <= 3, "cap held under concurrency: {statuses:?}");
90+
91+
seed.shutdown().await;
92+
Ok(())
93+
}

0 commit comments

Comments
 (0)