Repository: tikv/tikv
File: components/cloud/aws/src/s3.rs (Rust)

Code before:
            ReplayEvent::new(
                http::Request::builder()
                    .header("content-length", "2")
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=UploadPart&partNumber=2&uploadId=1"
                    ))
                    .body(SdkBody::from("78"))
                    .unwrap(),
                http::Response::builder().status(200).body(SdkBody::from("")).unwrap()
            ),
            ReplayEvent::new(
                http::Request::builder()
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=UploadPart&partNumber=3&uploadId=1"
                    ))
                    .body(SdkBody::from("90"))
                    .unwrap(),
                http::Response::builder().status(404).body(SdkBody::from("Not Found")).unwrap()
            ),
            ReplayEvent::new(
                http::Request::builder()
                    .header("content-length", "0")
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=AbortMultipartUpload&uploadId=1"
                    ))
                    .body(SdkBody::from(""))
                    .unwrap(),
                http::Response::builder()
                    .status(200)
                    .body(SdkBody::from(
                        r#"<?xml version="1.0" encoding="UTF-8"?>
                            <CompleteMultipartUploadResult>
                                <Location>https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey</Location>
                                <Bucket>mybucket</Bucket>
                                <Key>mykey</Key>
                                <Etag></ETag>
                            </CompleteMultipartUploadResult>
                            "#
                    )).unwrap()
            ),
        ]);

        let creds = Credentials::from_keys("abc".to_string(), "xyz".to_string(), None);

        let s = S3Storage::new_with_creds_client(config.clone(), client.clone(), creds).unwrap();
        let err = s.put(
            "mykey",
            PutResource(Box::new(magic_contents.as_bytes())),
            magic_contents.len() as u64,
        )
        .await

Review comment (bug):
In `test_s3_storage_multi_part_abort`, the third mocked `UploadPart` request (partNumber=3) is missing the `Content-Length` header even though parts 1 and 2 include it. This inconsistency can cause `StaticReplayClient` request matching to fail if the actual request includes the header for all parts.

Code after:
                    .header("content-length", "2")
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=UploadPart&partNumber=1&uploadId=1"
                    ))
                    .body(SdkBody::from("56"))
                    .unwrap(),
                http::Response::builder().status(200).body(SdkBody::from("")).unwrap()
            ),
            ReplayEvent::new(
                http::Request::builder()
                    .header("content-length", "2")
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=UploadPart&partNumber=2&uploadId=1"
                    ))
                    .body(SdkBody::from("78"))
                    .unwrap(),
                http::Response::builder().status(200).body(SdkBody::from("")).unwrap()
            ),
            ReplayEvent::new(
                http::Request::builder()
                    .header("content-length", "2")
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=UploadPart&partNumber=3&uploadId=1"
                    ))
                    .body(SdkBody::from("90"))
                    .unwrap(),
                http::Response::builder().status(404).body(SdkBody::from("Not Found")).unwrap()
            ),
            ReplayEvent::new(
                http::Request::builder()
                    .uri(Uri::from_static(
                        "https://s3.cn-north-1.amazonaws.com.cn/mybucket/mykey?x-id=AbortMultipartUpload&uploadId=1"
                    ))
                    .body(SdkBody::from(""))
                    .unwrap(),
                http::Response::builder()
                    .status(200)
                    .body(SdkBody::from("")).unwrap()
            ),
        ]);

        let creds = Credentials::from_keys("abc".to_string(), "xyz".to_string(), None);

        let s = S3Storage::new_with_creds_client(config.clone(), client.clone(), creds).unwrap();
        let err = s
            .put(
                "mykey",
                PutResource(Box::new(magic_contents.as_bytes())),
                magic_contents.len() as u64,
            )
            .await