From 9ae233d552c02b0ec3c773f50eb811a31405ccfb Mon Sep 17 00:00:00 2001 From: houseme Date: Tue, 7 Jul 2026 14:01:32 +0800 Subject: [PATCH] test(e2e): add SHA256 write and HEAD checksum regressions (#4354) test(e2e): add SHA256 verify-on-write & HEAD-checksum regression for #4341 Issue #4341 reported (on beta.8) that RustFS accepted mismatched S3 SHA256 checksums on PutObject (HTTP 200 instead of 400 BadDigest) and did not return ChecksumSHA256 on HeadObject with ChecksumMode=ENABLED. Both behaviors are already correct on current main, but the existing e2e coverage only asserted the happy path (upload succeeds + GetObject content matches). It did not encode the issue's two acceptance criteria, so a future regression would go undetected. Add two focused e2e tests that assert exactly the issue's contract: - test_put_object_rejects_mismatched_sha256: a body that does not match the declared x-amz-checksum-sha256 must be rejected with BadDigest and must not be stored. - test_head_object_returns_stored_sha256: after a correct upload, HeadObject with ChecksumMode=ENABLED must return the stored base64 SHA256 digest. Both tests pass against a real rustfs server (mismatch -> HTTP 400 BadDigest verified). Co-authored-by: heihutu --- crates/e2e_test/src/checksum_upload_test.rs | 95 +++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/crates/e2e_test/src/checksum_upload_test.rs b/crates/e2e_test/src/checksum_upload_test.rs index 6be69df85..e0dcf525f 100644 --- a/crates/e2e_test/src/checksum_upload_test.rs +++ b/crates/e2e_test/src/checksum_upload_test.rs @@ -136,6 +136,101 @@ mod tests { info!("PASSED: PutObject with checksum_sha256 and GetObject content match"); } + /// Regression test for issue #4341 (part 1: verify-on-write): + /// PutObject with a SHA256 checksum that does NOT match the body must be + /// rejected (BadDigest / checksum mismatch), NOT accepted with HTTP 200. + #[tokio::test] + #[serial] + async fn test_put_object_rejects_mismatched_sha256() { + init_logging(); + info!("TEST: PutObject rejects mismatched x-amz-checksum-sha256 (issue #4341)"); + + let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment"); + env.start_rustfs_server(vec![]).await.expect("Failed to start RustFS"); + + let client = create_s3_client(&env); + let bucket = "test-checksum-sha256-mismatch"; + create_bucket(&client, bucket).await.expect("Failed to create bucket"); + + let key = "obj-bad-sha256.txt"; + let content = b"Body bytes that do NOT match the declared checksum"; + // Checksum of a DIFFERENT payload -> deliberate mismatch. + let wrong_checksum = checksum_sha256_base64(b"some other payload entirely"); + + let result = client + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(content)) + .checksum_sha256(&wrong_checksum) + .send() + .await; + + assert!( + result.is_err(), + "PutObject with a mismatched SHA256 must be rejected, but it succeeded (issue #4341)" + ); + let err = result.err().unwrap(); + let msg = format!("{err:?}"); + info!("PutObject correctly rejected mismatched checksum: {msg}"); + assert!( + msg.contains("BadDigest") || msg.to_lowercase().contains("digest") || msg.to_lowercase().contains("checksum"), + "Expected a BadDigest/checksum error, got: {msg}" + ); + + // And the object must not have been stored. + let head = client.head_object().bucket(bucket).key(key).send().await; + assert!(head.is_err(), "Object must not exist after a rejected mismatched-checksum PutObject"); + info!("PASSED: PutObject rejects mismatched SHA256 and stores nothing"); + } + + /// Regression test for issue #4341 (part 2: retrieve-on-HEAD): + /// After PutObject with a correct SHA256 checksum, HeadObject with + /// ChecksumMode=ENABLED must return that stored base64 SHA256 digest. + #[tokio::test] + #[serial] + async fn test_head_object_returns_stored_sha256() { + init_logging(); + info!("TEST: HeadObject returns stored SHA256 with ChecksumMode=ENABLED (issue #4341)"); + + let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment"); + env.start_rustfs_server(vec![]).await.expect("Failed to start RustFS"); + + let client = create_s3_client(&env); + let bucket = "test-checksum-sha256-head"; + create_bucket(&client, bucket).await.expect("Failed to create bucket"); + + let key = "obj-head-sha256.txt"; + let content = b"Retrieve my SHA256 checksum on HEAD"; + let checksum = checksum_sha256_base64(content); + + client + .put_object() + .bucket(bucket) + .key(key) + .body(ByteStream::from_static(content)) + .checksum_sha256(&checksum) + .send() + .await + .expect("PutObject with correct SHA256 failed"); + + let head = client + .head_object() + .bucket(bucket) + .key(key) + .checksum_mode(ChecksumMode::Enabled) + .send() + .await + .expect("HeadObject failed"); + + assert_eq!( + head.checksum_sha256(), + Some(checksum.as_str()), + "HeadObject with ChecksumMode=ENABLED must return the stored base64 SHA256 (issue #4341)" + ); + info!("PASSED: HeadObject returns stored SHA256 digest"); + } + /// Multipart upload with checksum: CreateMultipartUpload, UploadPart(s) with checksum_sha256, CompleteMultipartUpload; then GetObject verifies content. /// Uses part size >= 5MB (server minimum) for two parts. #[tokio::test]