Compare commits

..

6 Commits

Author SHA1 Message Date
houseme 566877d3ba Merge branch 'main' into overtrue/activate-group-e2e 2026-08-23 12:37:24 +08:00
overtrue 3294c64fcc test(e2e): correct group regression fixtures 2026-08-23 11:11:01 +08:00
overtrue a8be0f81f5 test(e2e): bind group selection to Linux listing 2026-08-23 08:55:07 +08:00
overtrue 3179b7acb8 test(e2e): pin group deletion errors 2026-08-23 06:42:55 +08:00
overtrue d25b84a793 Merge remote-tracking branch 'origin/main' into overtrue/activate-group-e2e 2026-08-23 05:43:17 +08:00
overtrue a79b806fb4 test(e2e): activate group management regressions 2026-08-23 02:37:15 +08:00
3 changed files with 122 additions and 40 deletions
+2 -2
View File
@@ -1,2 +1,2 @@
sha256-darwin=9f767b37ed8b1c82da62ea441462d75487785c8086e56f08fb6f6cd89c6e2e52 sha256-darwin=f832043fcca8c0b616c5d820a3a652da7544298ef5812a8668a3a9a3e4607b8b
sha256-linux=fbdaf42b220958d4b1e8880e0f8b5a7992d38e21051bb60596dd4538424757d6 sha256-linux=93b94adb110b86a41d0b7313909e0bf53cb1515e2d08e8f105652b29b249990f
+118 -36
View File
@@ -14,7 +14,7 @@
//! E2E tests for group management (fixes #2028). //! E2E tests for group management (fixes #2028).
use crate::common::{RustFSTestEnvironment, admin_request, awscurl_delete, awscurl_get, awscurl_put, init_logging}; use crate::common::{RustFSTestEnvironment, admin_ok, admin_request, init_logging};
use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::config::{Credentials, Region};
use aws_sdk_s3::{Client, Config}; use aws_sdk_s3::{Client, Config};
use tracing::info; use tracing::info;
@@ -83,7 +83,6 @@ async fn update_group_members_rejects_invalid_new_group_names() -> Result<(), Bo
/// Test that deleting a group with members fails, and deleting an empty group succeeds. /// Test that deleting a group with members fails, and deleting an empty group succeeds.
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
#[ignore = "requires awscurl and spawns a real RustFS server"]
async fn test_delete_group_requires_empty_membership() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn test_delete_group_requires_empty_membership() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_logging(); init_logging();
@@ -91,29 +90,58 @@ async fn test_delete_group_requires_empty_membership() -> Result<(), Box<dyn std
env.start_rustfs_server(vec![]).await?; env.start_rustfs_server(vec![]).await?;
// 1. Create a user // 1. Create a user
let add_user_url = format!("{}/rustfs/admin/v3/add-user?accessKey=testuser1", env.url);
let user_body = serde_json::json!({ let user_body = serde_json::json!({
"secretKey": "testuser1secret", "secretKey": "testuser1secret",
"status": "enabled" "status": "enabled"
}); });
awscurl_put(&add_user_url, &user_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
"/rustfs/admin/v3/add-user?accessKey=testuser1",
Some(user_body.to_string()),
)
.await?;
info!("Created testuser1"); info!("Created testuser1");
// 2. Create a group with testuser1 as a member // 2. Create a group with testuser1 as a member
let update_members_url = format!("{}/rustfs/admin/v3/update-group-members", env.url);
let add_member_body = serde_json::json!({ let add_member_body = serde_json::json!({
"group": "testgroup", "group": "testgroup",
"members": ["testuser1"], "members": ["testuser1"],
"isRemove": false, "isRemove": false,
"groupStatus": "enabled" "groupStatus": "enabled"
}); });
awscurl_put(&update_members_url, &add_member_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
"/rustfs/admin/v3/update-group-members",
Some(add_member_body.to_string()),
)
.await?;
info!("Added testuser1 to testgroup"); info!("Added testuser1 to testgroup");
// 3. Attempt to delete the group while it still has members — should fail // 3. Attempt to delete the group while it still has members — should fail
let delete_group_url = format!("{}/rustfs/admin/v3/group/testgroup", env.url); let (delete_status, delete_body) = admin_request(
let delete_result = awscurl_delete(&delete_group_url, &env.access_key, &env.secret_key).await; &env.url,
assert!(delete_result.is_err(), "deleting a non-empty group should fail"); http::Method::DELETE,
"/rustfs/admin/v3/group/testgroup",
None,
&env.access_key,
&env.secret_key,
)
.await?;
assert_eq!(
delete_status,
reqwest::StatusCode::BAD_REQUEST,
"deleting a non-empty group must return HTTP 400, body: {delete_body}"
);
assert!(
delete_body.contains("<Code>InvalidRequest</Code>"),
"deleting a non-empty group must return InvalidRequest, body: {delete_body}"
);
assert!(
delete_body.contains("<Message>group is not empty</Message>"),
"deleting a non-empty group returned an unexpected message: {delete_body}"
);
info!("Delete of non-empty group correctly rejected"); info!("Delete of non-empty group correctly rejected");
// 4. Remove the member from the group // 4. Remove the member from the group
@@ -123,17 +151,42 @@ async fn test_delete_group_requires_empty_membership() -> Result<(), Box<dyn std
"isRemove": true, "isRemove": true,
"groupStatus": "enabled" "groupStatus": "enabled"
}); });
awscurl_put(&update_members_url, &remove_member_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
"/rustfs/admin/v3/update-group-members",
Some(remove_member_body.to_string()),
)
.await?;
info!("Removed testuser1 from testgroup"); info!("Removed testuser1 from testgroup");
// 5. Delete the now-empty group — should succeed // 5. Delete the now-empty group — should succeed
awscurl_delete(&delete_group_url, &env.access_key, &env.secret_key).await?; admin_ok(&env, http::Method::DELETE, "/rustfs/admin/v3/group/testgroup", None).await?;
info!("Deleted empty testgroup successfully"); info!("Deleted empty testgroup successfully");
// 6. Verify the group no longer exists // 6. Verify the group no longer exists
let get_group_url = format!("{}/rustfs/admin/v3/group?group=testgroup", env.url); let (get_status, get_body) = admin_request(
let get_result = awscurl_get(&get_group_url, &env.access_key, &env.secret_key).await; &env.url,
assert!(get_result.is_err(), "group should no longer exist after deletion"); http::Method::GET,
"/rustfs/admin/v3/group?group=testgroup",
None,
&env.access_key,
&env.secret_key,
)
.await?;
assert_eq!(
get_status,
reqwest::StatusCode::NOT_FOUND,
"a deleted group must return HTTP 404, body: {get_body}"
);
assert!(
get_body.contains("<Code>NoSuchResource</Code>"),
"a deleted group must return NoSuchResource, body: {get_body}"
);
assert!(
get_body.contains("<Message>group &apos;testgroup&apos; does not exist</Message>"),
"a deleted group returned an unexpected message: {get_body}"
);
info!("Confirmed testgroup no longer exists"); info!("Confirmed testgroup no longer exists");
Ok(()) Ok(())
@@ -142,7 +195,6 @@ async fn test_delete_group_requires_empty_membership() -> Result<(), Box<dyn std
/// Test that a user with only group membership (no explicit user policy) gets group policies /// Test that a user with only group membership (no explicit user policy) gets group policies
/// and can perform actions allowed by the group (regression test for #2028.1). /// and can perform actions allowed by the group (regression test for #2028.1).
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
#[ignore = "requires awscurl and spawns a real RustFS server"]
async fn test_user_with_only_group_gets_group_policies() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn test_user_with_only_group_gets_group_policies() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_logging(); init_logging();
@@ -160,39 +212,56 @@ async fn test_user_with_only_group_gets_group_policies() -> Result<(), Box<dyn s
"Statement": [{ "Statement": [{
"Effect": "Allow", "Effect": "Allow",
"Action": ["s3:ListAllMyBuckets"], "Action": ["s3:ListAllMyBuckets"],
"Resource": ["*"] "Resource": ["arn:aws:s3:::*"]
}] }]
}); });
let add_policy_url = format!("{}/rustfs/admin/v3/add-canned-policy?name={}", env.url, policy_name); admin_ok(
awscurl_put(&add_policy_url, &policy_doc.to_string(), &env.access_key, &env.secret_key).await?; &env,
http::Method::PUT,
&format!("/rustfs/admin/v3/add-canned-policy?name={policy_name}"),
Some(policy_doc.to_string()),
)
.await?;
info!("Created canned policy {}", policy_name); info!("Created canned policy {}", policy_name);
// 2. Create user with no explicit policy // 2. Create user with no explicit policy
let add_user_url = format!("{}/rustfs/admin/v3/add-user?accessKey={}", env.url, user_name);
let user_body = serde_json::json!({ let user_body = serde_json::json!({
"secretKey": user_secret, "secretKey": user_secret,
"status": "enabled" "status": "enabled"
}); });
awscurl_put(&add_user_url, &user_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
&format!("/rustfs/admin/v3/add-user?accessKey={user_name}"),
Some(user_body.to_string()),
)
.await?;
info!("Created user {} with no explicit policy", user_name); info!("Created user {} with no explicit policy", user_name);
// 3. Add user to group (creates group with this member; user_group_memberships must be updated) // 3. Add user to group (creates group with this member; user_group_memberships must be updated)
let update_members_url = format!("{}/rustfs/admin/v3/update-group-members", env.url);
let add_member_body = serde_json::json!({ let add_member_body = serde_json::json!({
"group": group_name, "group": group_name,
"members": [user_name], "members": [user_name],
"isRemove": false, "isRemove": false,
"groupStatus": "enabled" "groupStatus": "enabled"
}); });
awscurl_put(&update_members_url, &add_member_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
"/rustfs/admin/v3/update-group-members",
Some(add_member_body.to_string()),
)
.await?;
info!("Added {} to group {}", user_name, group_name); info!("Added {} to group {}", user_name, group_name);
// 4. Attach policy to group // 4. Attach policy to group
let set_policy_url = format!( admin_ok(
"{}/rustfs/admin/v3/set-user-or-group-policy?policyName={}&userOrGroup={}&isGroup=true", &env,
env.url, policy_name, group_name http::Method::PUT,
); &format!("/rustfs/admin/v3/set-user-or-group-policy?policyName={policy_name}&userOrGroup={group_name}&isGroup=true"),
awscurl_put(&set_policy_url, "", &env.access_key, &env.secret_key).await?; Some(String::new()),
)
.await?;
info!("Attached policy {} to group {}", policy_name, group_name); info!("Attached policy {} to group {}", policy_name, group_name);
// 5. User with only group (no user policy) should be able to list buckets // 5. User with only group (no user policy) should be able to list buckets
@@ -209,7 +278,6 @@ async fn test_user_with_only_group_gets_group_policies() -> Result<(), Box<dyn s
/// Test that after deleting a user who was the only member of a group, the group can be deleted /// Test that after deleting a user who was the only member of a group, the group can be deleted
/// (regression test for #2028.2: delete group uses backend membership, not stale cache). /// (regression test for #2028.2: delete group uses backend membership, not stale cache).
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
#[ignore = "requires awscurl and spawns a real RustFS server"]
async fn test_delete_group_after_deleting_user() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn test_delete_group_after_deleting_user() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_logging(); init_logging();
@@ -221,33 +289,47 @@ async fn test_delete_group_after_deleting_user() -> Result<(), Box<dyn std::erro
let group_name = "soledeletegroup"; let group_name = "soledeletegroup";
// 1. Create user // 1. Create user
let add_user_url = format!("{}/rustfs/admin/v3/add-user?accessKey={}", env.url, user_name);
let user_body = serde_json::json!({ let user_body = serde_json::json!({
"secretKey": user_secret, "secretKey": user_secret,
"status": "enabled" "status": "enabled"
}); });
awscurl_put(&add_user_url, &user_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
&format!("/rustfs/admin/v3/add-user?accessKey={user_name}"),
Some(user_body.to_string()),
)
.await?;
info!("Created user {}", user_name); info!("Created user {}", user_name);
// 2. Add user to group // 2. Add user to group
let update_members_url = format!("{}/rustfs/admin/v3/update-group-members", env.url);
let add_member_body = serde_json::json!({ let add_member_body = serde_json::json!({
"group": group_name, "group": group_name,
"members": [user_name], "members": [user_name],
"isRemove": false, "isRemove": false,
"groupStatus": "enabled" "groupStatus": "enabled"
}); });
awscurl_put(&update_members_url, &add_member_body.to_string(), &env.access_key, &env.secret_key).await?; admin_ok(
&env,
http::Method::PUT,
"/rustfs/admin/v3/update-group-members",
Some(add_member_body.to_string()),
)
.await?;
info!("Added {} to group {}", user_name, group_name); info!("Added {} to group {}", user_name, group_name);
// 3. Delete the user (backend and cache update so group membership becomes empty) // 3. Delete the user (backend and cache update so group membership becomes empty)
let remove_user_url = format!("{}/rustfs/admin/v3/remove-user?accessKey={}", env.url, user_name); admin_ok(
awscurl_delete(&remove_user_url, &env.access_key, &env.secret_key).await?; &env,
http::Method::DELETE,
&format!("/rustfs/admin/v3/remove-user?accessKey={user_name}"),
None,
)
.await?;
info!("Deleted user {}", user_name); info!("Deleted user {}", user_name);
// 4. Deleting the group should succeed (backend has empty members; no stale cache) // 4. Deleting the group should succeed (backend has empty members; no stale cache)
let delete_group_url = format!("{}/rustfs/admin/v3/group/{}", env.url, group_name); admin_ok(&env, http::Method::DELETE, &format!("/rustfs/admin/v3/group/{group_name}"), None).await?;
awscurl_delete(&delete_group_url, &env.access_key, &env.secret_key).await?;
info!("Deleted group {} after user was removed", group_name); info!("Deleted group {} after user was removed", group_name);
Ok(()) Ok(())
+2 -2
View File
@@ -52,7 +52,7 @@
| fault_proxy | 7 | | | fault_proxy | 7 | |
| get_codec_streaming_compat_test | 1 | | | get_codec_streaming_compat_test | 1 | |
| get_stream_failure_observability_test | 1 | | | get_stream_failure_observability_test | 1 | |
| group_delete_test | 1 | | | group_delete_test | 4 | |
| head_object_consistency_test | 1 | ✅ | | head_object_consistency_test | 1 | ✅ |
| head_object_range_test | 1 | ✅ | | head_object_range_test | 1 | ✅ |
| heal_erasure_disk_rebuild_test | 4 | 🌙 | | heal_erasure_disk_rebuild_test | 4 | 🌙 |
@@ -99,4 +99,4 @@
| tls_hot_reload_test | 1 | ✅ | | tls_hot_reload_test | 1 | ✅ |
| version_id_regression_test | 10 | ✅ | | version_id_regression_test | 10 | ✅ |
**Total listed: 575 tests across 82 modules · PR smoke: 163 tests / 36 modules · merge/main full: 453 tests / 73 modules · nightly replication: 55 tests · nightly cluster faults: 28 tests / 7 modules · nightly protocols: 16 tests** · updated 2026-08-23. **Total listed: 578 tests across 82 modules · PR smoke: 163 tests / 36 modules · merge/main full: 456 tests / 73 modules · nightly replication: 55 tests · nightly cluster faults: 28 tests / 7 modules · nightly protocols: 16 tests** · updated 2026-08-23.