mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 17:28:12 +00:00
test(e2e): require exact object lock rejection oracles (#6580)
This commit is contained in:
@@ -92,14 +92,29 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_invalid_object_lock_retention_pair<T, E: std::fmt::Debug>(result: Result<T, E>, context: &str) {
|
fn assert_invalid_object_lock_retention_pair<T, E, R>(result: Result<T, R>, context: &str)
|
||||||
let err = match result {
|
where
|
||||||
Ok(_) => panic!("{context}"),
|
T: std::fmt::Debug,
|
||||||
Err(err) => format!("{err:?}"),
|
E: ProvideErrorMetadata + std::fmt::Debug,
|
||||||
};
|
R: Borrow<SdkError<E>> + std::fmt::Debug,
|
||||||
assert!(
|
{
|
||||||
err.contains("InvalidRequest") || err.contains("must both be supplied"),
|
let error = result.expect_err(context);
|
||||||
"{context}: expected invalid paired retention headers, got: {err}"
|
let sdk_error = error.borrow();
|
||||||
|
assert_eq!(
|
||||||
|
sdk_error.raw_response().map(|response| response.status().as_u16()),
|
||||||
|
Some(400),
|
||||||
|
"{context}: expected HTTP 400, got: {error:?}"
|
||||||
|
);
|
||||||
|
let service_error = sdk_error.as_service_error().expect("expected an S3 service error");
|
||||||
|
assert_eq!(
|
||||||
|
service_error.code(),
|
||||||
|
Some("InvalidRequest"),
|
||||||
|
"{context}: expected InvalidRequest, got: {error:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
service_error.message(),
|
||||||
|
Some("x-amz-object-lock-retain-until-date and x-amz-object-lock-mode must both be supplied"),
|
||||||
|
"{context}: unexpected error message: {error:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2304,9 +2319,9 @@ async fn test_versioning_auto_enabled_with_object_lock() {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_error_message_distinguishes_legal_hold_from_retention() {
|
async fn test_legal_hold_and_retention_delete_errors_are_exact_and_non_mutating() {
|
||||||
init_logging();
|
init_logging();
|
||||||
info!("🧪 Test: Error messages distinguish Legal Hold from Retention");
|
info!("🧪 Test: Legal Hold and Retention reject deletes without mutating objects");
|
||||||
|
|
||||||
let mut env = ObjectLockTestEnvironment::new().await.unwrap();
|
let mut env = ObjectLockTestEnvironment::new().await.unwrap();
|
||||||
env.start_rustfs().await.unwrap();
|
env.start_rustfs().await.unwrap();
|
||||||
@@ -2331,7 +2346,6 @@ async fn test_error_message_distinguishes_legal_hold_from_retention() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Delete legal hold object - check error
|
|
||||||
let lh_delete_result = client
|
let lh_delete_result = client
|
||||||
.delete_object()
|
.delete_object()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
@@ -2339,18 +2353,8 @@ async fn test_error_message_distinguishes_legal_hold_from_retention() {
|
|||||||
.version_id(&lh_version)
|
.version_id(&lh_version)
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
assert_access_denied(lh_delete_result, "Legal Hold must reject deleting the protected version");
|
||||||
|
|
||||||
if let Err(e) = lh_delete_result {
|
|
||||||
let error_str = format!("{:?}", e);
|
|
||||||
info!("Legal hold delete error: {}", error_str);
|
|
||||||
// Error should mention legal hold
|
|
||||||
assert!(
|
|
||||||
error_str.to_lowercase().contains("legal") || error_str.to_lowercase().contains("hold"),
|
|
||||||
"Error should mention legal hold"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete retention object - check error
|
|
||||||
let ret_delete_result = client
|
let ret_delete_result = client
|
||||||
.delete_object()
|
.delete_object()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
@@ -2358,16 +2362,24 @@ async fn test_error_message_distinguishes_legal_hold_from_retention() {
|
|||||||
.version_id(&ret_version)
|
.version_id(&ret_version)
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
assert_access_denied(ret_delete_result, "COMPLIANCE retention must reject deleting the protected version");
|
||||||
|
|
||||||
if let Err(e) = ret_delete_result {
|
for (key, version_id) in [(legal_hold_key, &lh_version), (retention_key, &ret_version)] {
|
||||||
let error_str = format!("{:?}", e);
|
let body = client
|
||||||
info!("Retention delete error: {}", error_str);
|
.get_object()
|
||||||
// Error should mention retention
|
.bucket(bucket)
|
||||||
assert!(
|
.key(key)
|
||||||
error_str.to_lowercase().contains("retention") || error_str.to_lowercase().contains("compliance"),
|
.version_id(version_id)
|
||||||
"Error should mention retention"
|
.send()
|
||||||
);
|
.await
|
||||||
|
.expect("rejected delete must leave the protected version readable")
|
||||||
|
.body
|
||||||
|
.collect()
|
||||||
|
.await
|
||||||
|
.expect("protected version body should remain readable")
|
||||||
|
.into_bytes();
|
||||||
|
assert_eq!(body.as_ref(), b"data", "rejected delete mutated protected object {key}");
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("✅ Test passed: Error messages distinguish lock types");
|
info!("✅ Test passed: protected deletes are exact and non-mutating");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user