diff --git a/aws/rust-runtime/Cargo.lock b/aws/rust-runtime/Cargo.lock index 20cf7e15df..b94f091e46 100644 --- a/aws/rust-runtime/Cargo.lock +++ b/aws/rust-runtime/Cargo.lock @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.6.2" +version = "1.6.3" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -326,7 +326,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.1" +version = "1.7.2" dependencies = [ "aws-smithy-async", "aws-smithy-types", diff --git a/aws/sdk/integration-tests/codecatalyst/tests/sso_bearer_auth.rs b/aws/sdk/integration-tests/codecatalyst/tests/sso_bearer_auth.rs index 99b2b24812..b11861b555 100644 --- a/aws/sdk/integration-tests/codecatalyst/tests/sso_bearer_auth.rs +++ b/aws/sdk/integration-tests/codecatalyst/tests/sso_bearer_auth.rs @@ -27,5 +27,5 @@ async fn sso_bearer_auth() { let item = &response.items.unwrap()[0]; assert_eq!("somespacename", item.name); - replay.full_validate("application/json").await.unwrap(); + replay.relaxed_validate("application/json").await.unwrap(); } diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs index ed534f61bd..7f832bc9e9 100644 --- a/aws/sdk/integration-tests/kms/tests/integration.rs +++ b/aws/sdk/integration-tests/kms/tests/integration.rs @@ -8,7 +8,6 @@ use aws_sdk_kms::operation::RequestId; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime_api::client::result::SdkError; use aws_smithy_types::body::SdkBody; -use http::header::AUTHORIZATION; use http::Uri; use kms::config::{Config, Credentials, Region}; @@ -90,7 +89,7 @@ async fn generate_random() { .sum::(), 8562 ); - http_client.assert_requests_match(&[]); + http_client.relaxed_requests_match(); } #[tokio::test] @@ -166,5 +165,5 @@ async fn generate_random_keystore_not_found() { inner.request_id(), Some("bfe81a0a-9a08-4e71-9910-cdb5ab6ea3b6") ); - http_client.assert_requests_match(&[AUTHORIZATION.as_str()]); + http_client.relaxed_requests_match(); } diff --git a/aws/sdk/integration-tests/qldbsession/tests/integration.rs b/aws/sdk/integration-tests/qldbsession/tests/integration.rs index 6cdb32d1f1..b0df9190ae 100644 --- a/aws/sdk/integration-tests/qldbsession/tests/integration.rs +++ b/aws/sdk/integration-tests/qldbsession/tests/integration.rs @@ -55,5 +55,5 @@ async fn signv4_use_correct_service_name() { .await .expect("request should succeed"); - http_client.assert_requests_match(&[]); + http_client.assert_requests_match(&["authorization"]); } diff --git a/aws/sdk/integration-tests/s3/tests/checksums.rs b/aws/sdk/integration-tests/s3/tests/checksums.rs index 175f7c5f0f..695b141b83 100644 --- a/aws/sdk/integration-tests/s3/tests/checksums.rs +++ b/aws/sdk/integration-tests/s3/tests/checksums.rs @@ -96,7 +96,11 @@ async fn test_checksum_on_streaming_response( .await .unwrap(); - http_client.assert_requests_match(&["x-amz-checksum-mode", AUTHORIZATION.as_str()]); + http_client.assert_requests_match(&[ + "x-amz-checksum-mode", + "x-amz-user-agent", + AUTHORIZATION.as_str(), + ]); res } diff --git a/aws/sdk/integration-tests/s3/tests/content-length-enforcement.rs b/aws/sdk/integration-tests/s3/tests/content-length-enforcement.rs index 3518e8b116..15bc78b276 100644 --- a/aws/sdk/integration-tests/s3/tests/content-length-enforcement.rs +++ b/aws/sdk/integration-tests/s3/tests/content-length-enforcement.rs @@ -25,9 +25,10 @@ async fn test_content_length_enforcement_is_not_applied_to_head_request() { .await .expect("content length enforcement must not apply to HEAD requests"); - // The body returned will be empty, so we pass an empty string to full_validate. - // That way, it'll do a string equality check on the empty strings. - http_client.full_validate("").await.unwrap(); + // The body returned will be empty, so we pass an empty string for `media_type` to + // `validate_body_and_headers_except`. That way, it'll do a string equality check on the empty + // strings. + http_client.relaxed_validate("").await.unwrap(); } #[tokio::test] @@ -57,7 +58,10 @@ async fn test_content_length_enforcement_get_request_short() { // This will fail with a content-length mismatch error. let content_length_err = output.body.collect().await.unwrap_err(); - http_client.full_validate("application/text").await.unwrap(); + http_client + .relaxed_validate("application/text") + .await + .unwrap(); assert_eq!( DisplayErrorContext(content_length_err).to_string(), "streaming error: Invalid Content-Length: Expected 9999 bytes but 10000 bytes were received (Error { kind: StreamingError(ContentLengthError { expected: 9999, received: 10000 }) })" @@ -91,7 +95,10 @@ async fn test_content_length_enforcement_get_request_long() { // This will fail with a content-length mismatch error. let content_length_err = output.body.collect().await.unwrap_err(); - http_client.full_validate("application/text").await.unwrap(); + http_client + .relaxed_validate("application/text") + .await + .unwrap(); assert_eq!( DisplayErrorContext(content_length_err).to_string(), "streaming error: Invalid Content-Length: Expected 10001 bytes but 10000 bytes were received (Error { kind: StreamingError(ContentLengthError { expected: 10001, received: 10000 }) })" diff --git a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs index 89b15ba58c..1ab6a6ee5d 100644 --- a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs +++ b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs @@ -10,7 +10,6 @@ use aws_sdk_s3::Config; use aws_sdk_s3::{config::Credentials, config::Region, types::ObjectAttributes, Client}; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_types::body::SdkBody; -use http::header::AUTHORIZATION; const RESPONSE_BODY_XML: &[u8] = b"\ne1AsOh9IyGCa4hLN+2Od7jlnP14="; @@ -60,5 +59,5 @@ async fn ignore_invalid_xml_body_root() { .await .unwrap(); - http_client.assert_requests_match(&[AUTHORIZATION.as_str()]); + http_client.relaxed_requests_match(); } diff --git a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs index 3b6bd97002..22d09d1b1f 100644 --- a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs +++ b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs @@ -79,20 +79,10 @@ async fn test_s3_signer_with_naughty_string_metadata() { let _ = builder.send().await.unwrap(); + // As long as a request can be extracted and the `Authorization` header exits, we're good. + // We cannot compare a signature in the `Authorization` header between expected and actual + // because the signature is subject to change as we update the `x-amz-user-agent` header, e.g. + // due to the introduction of a new metric. let expected_req = rcvr.expect_request(); - let auth_header = expected_req - .headers() - .get("Authorization") - .unwrap() - .to_owned(); - - // This is a snapshot test taken from a known working test result - let snapshot_signature = - "Signature=a5115604df66219874a9e5a8eab4c9f7a28c992ab2d918037a285756c019f3b2"; - assert!( - auth_header .contains(snapshot_signature), - "authorization header signature did not match expected signature: got {}, expected it to contain {}", - auth_header, - snapshot_signature - ); + let _ = expected_req.headers().get("Authorization").unwrap(); } diff --git a/aws/sdk/integration-tests/s3/tests/no_auth.rs b/aws/sdk/integration-tests/s3/tests/no_auth.rs index 170332d00d..644e05addc 100644 --- a/aws/sdk/integration-tests/s3/tests/no_auth.rs +++ b/aws/sdk/integration-tests/s3/tests/no_auth.rs @@ -33,7 +33,7 @@ async fn list_objects() { dbg!(result).expect("success"); http_client - .validate_body_and_headers(None, "application/xml") + .relaxed_validate("application/xml") .await .unwrap(); } @@ -65,7 +65,7 @@ async fn list_objects_v2() { dbg!(result).expect("success"); http_client - .validate_body_and_headers(None, "application/xml") + .relaxed_validate("application/xml") .await .unwrap(); } @@ -96,7 +96,7 @@ async fn head_object() { dbg!(result).expect("success"); http_client - .validate_body_and_headers(None, "application/xml") + .relaxed_validate("application/xml") .await .unwrap(); } @@ -127,7 +127,7 @@ async fn get_object() { dbg!(result).expect("success"); http_client - .validate_body_and_headers(None, "application/xml") + .relaxed_validate("application/xml") .await .unwrap(); } diff --git a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs index 11ee0f7b09..135fb35df8 100644 --- a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs +++ b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs @@ -34,18 +34,7 @@ async fn test_operation_should_not_normalize_uri_path() { .unwrap(); let request = rx.expect_request(); - let actual_auth = - std::str::from_utf8(request.headers().get("authorization").unwrap().as_bytes()).unwrap(); - let actual_uri = request.uri(); let expected_uri = "https://test-bucket-ad7c9f01-7f7b-4669-b550-75cc6d4df0f1.s3.us-east-1.amazonaws.com/a/.././b.txt?x-id=PutObject"; - assert_eq!(actual_uri, expected_uri); - - let expected_sig = "Signature=2ac540538c84dc2616d92fb51d4fc6146ccd9ccc1ee85f518a1a686c5ef97b86"; - assert!( - actual_auth.contains(expected_sig), - "authorization header signature did not match expected signature: expected {} but not found in {}", - expected_sig, - actual_auth, - ); + assert_eq!(expected_uri, actual_uri); } diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs index 53560e3beb..31870702dd 100644 --- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs +++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs @@ -36,23 +36,12 @@ async fn test_s3_signer_query_string_with_all_valid_chars() { .send() .await; + // As long as a request can be extracted and the `Authorization` header exits, we're good. + // We cannot compare a signature in the `Authorization` header between expected and actual + // because the signature is subject to change as we update the `x-amz-user-agent` header, e.g. + // due to the introduction of a new metric. let expected_req = rcvr.expect_request(); - let auth_header = expected_req - .headers() - .get("Authorization") - .unwrap() - .to_owned(); - - // This is a snapshot test taken from a known working test result - let snapshot_signature = - "Signature=9a931d20606f93fa4e5553602866a9b5ccac2cd42b54ae5a4b17e4614fb443ce"; - assert!( - auth_header - .contains(snapshot_signature), - "authorization header signature did not match expected signature: got {}, expected it to contain {}", - auth_header, - snapshot_signature - ); + let _ = expected_req.headers().get("Authorization").unwrap(); } // This test can help identify individual characters that break the signing of query strings. This diff --git a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs index 47de27c437..c444aa5b88 100644 --- a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs +++ b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs @@ -103,11 +103,13 @@ async fn three_retries_and_then_success() { let resp = resp.expect("valid e2e test"); assert_eq!(resp.name(), Some("test-bucket")); http_client - .full_validate("application/xml") + .relaxed_validate("application/xml") .await - .expect("failed") + .unwrap(); } -// + +// TODO(simulate time): Currently commented out since the test is work in progress. +// Consider using `tick_advance_time_and_sleep` to simulate client and server times. // // # Client makes 3 separate SDK operation invocations // // # All succeed on first attempt. // // # Fast network, latency + server time is less than one second. @@ -190,7 +192,9 @@ async fn three_retries_and_then_success() { // assert_eq!(resp.name(), Some("test-bucket")); // conn.full_validate(MediaType::Xml).await.expect("failed") // } -// + +// TODO(simulate time): Currently commented out since the test is work in progress. +// Consider using `tick_advance_time_and_sleep` to simulate client and server times. // // # One SDK operation invocation. // // # Client retries 3 times, successful response on 3rd attempt. // // # Slow network, one way latency is 2 seconds. diff --git a/aws/sdk/integration-tests/s3/tests/signing-it.rs b/aws/sdk/integration-tests/s3/tests/signing-it.rs index 450f4ba43f..08f1cc29d8 100644 --- a/aws/sdk/integration-tests/s3/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3/tests/signing-it.rs @@ -10,6 +10,7 @@ use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::{Client, Config}; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_types::body::SdkBody; +use http::header::AUTHORIZATION; #[tokio::test] async fn test_signer() { @@ -37,5 +38,5 @@ async fn test_signer() { .send() .await; - http_client.assert_requests_match(&[]); + http_client.assert_requests_match(&[AUTHORIZATION.as_str()]); } diff --git a/aws/sdk/integration-tests/s3control/tests/signing-it.rs b/aws/sdk/integration-tests/s3control/tests/signing-it.rs index 6d258496b1..b235f7aa1d 100644 --- a/aws/sdk/integration-tests/s3control/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3control/tests/signing-it.rs @@ -8,6 +8,7 @@ use aws_sdk_s3control::config::{Credentials, Region}; use aws_sdk_s3control::{Client, Config}; use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_types::body::SdkBody; +use http::header::AUTHORIZATION; #[tokio::test] async fn test_signer() { @@ -39,5 +40,5 @@ async fn test_signer() { .await .expect_err("empty response"); - http_client.assert_requests_match(&[]); + http_client.assert_requests_match(&[AUTHORIZATION.as_str()]); } diff --git a/rust-runtime/Cargo.lock b/rust-runtime/Cargo.lock index b0711de2a3..aa7db1e7f1 100644 --- a/rust-runtime/Cargo.lock +++ b/rust-runtime/Cargo.lock @@ -304,7 +304,7 @@ dependencies = [ [[package]] name = "aws-smithy-cbor" -version = "0.60.6" +version = "0.60.7" dependencies = [ "aws-smithy-types 1.2.1", "criterion", @@ -363,7 +363,7 @@ version = "0.60.3" name = "aws-smithy-compression" version = "0.0.1" dependencies = [ - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "bytes", "bytes-utils", @@ -407,8 +407,8 @@ name = "aws-smithy-experimental" version = "0.1.3" dependencies = [ "aws-smithy-async 1.2.1", - "aws-smithy-runtime 1.6.2", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime 1.6.3", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "h2 0.4.5", "http 1.1.0", @@ -450,7 +450,7 @@ version = "0.60.9" dependencies = [ "async-stream", "aws-smithy-eventstream 0.60.4", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "bytes", "bytes-utils", @@ -479,7 +479,7 @@ dependencies = [ "aws-smithy-cbor", "aws-smithy-http 0.60.9", "aws-smithy-json 0.60.7", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "aws-smithy-xml 0.60.8", "bytes", @@ -569,7 +569,7 @@ name = "aws-smithy-mocks-experimental" version = "0.2.1" dependencies = [ "aws-sdk-s3", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "tokio", ] @@ -595,7 +595,7 @@ name = "aws-smithy-protocol-test" version = "0.62.0" dependencies = [ "assert-json-diff", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "base64-simd", "cbor-diag", "http 0.2.12", @@ -648,13 +648,13 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.6.2" +version = "1.6.3" dependencies = [ "approx", "aws-smithy-async 1.2.1", "aws-smithy-http 0.60.9", "aws-smithy-protocol-test 0.62.0", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "bytes", "fastrand", @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.7.1" +version = "1.7.2" dependencies = [ "aws-smithy-async 1.2.1", "aws-smithy-types 1.2.1", @@ -790,7 +790,7 @@ name = "aws-smithy-wasm" version = "0.1.3" dependencies = [ "aws-smithy-http 0.60.9", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "bytes", "http 1.1.0", @@ -1975,10 +1975,11 @@ checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" name = "inlineable" version = "0.1.0" dependencies = [ + "aws-smithy-cbor", "aws-smithy-compression", "aws-smithy-http 0.60.9", "aws-smithy-json 0.60.7", - "aws-smithy-runtime-api 1.7.1", + "aws-smithy-runtime-api 1.7.2", "aws-smithy-types 1.2.1", "aws-smithy-xml 0.60.8", "bytes", diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 0235893492..1a2d2adf42 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws-smithy-runtime" -version = "1.6.2" +version = "1.6.3" authors = ["AWS Rust SDK Team ", "Zelda Hessler "] description = "The new smithy runtime crate" edition = "2021" diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs index ada8a4befb..e4ad46494a 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs @@ -4,6 +4,7 @@ */ use super::{Action, ConnectionId, Direction, Event, NetworkTraffic}; +use crate::client::http::test_util::replay::DEFAULT_RELAXED_HEADERS; use aws_smithy_protocol_test::MediaType; use aws_smithy_runtime_api::client::connector_metadata::ConnectorMetadata; use aws_smithy_runtime_api::client::http::{ @@ -68,6 +69,11 @@ impl fmt::Debug for ReplayingClient { } } +enum HeadersToCheck<'a> { + Include(&'a [&'a str]), + Exclude(Option<&'a [&'a str]>), +} + impl ReplayingClient { fn next_id(&self) -> ConnectionId { ConnectionId(self.num_events.fetch_add(1, Ordering::Relaxed)) @@ -78,25 +84,60 @@ impl ReplayingClient { self.validate_body_and_headers(None, media_type).await } + /// Convenience method to validate that the bodies match, using a given [`MediaType`] for + /// comparison, and that the headers are also match excluding the default relaxed headers + /// + /// The current default relaxed headers: + /// - x-amz-user-agent + /// - authorization + pub async fn relaxed_validate(self, media_type: &str) -> Result<(), Box> { + self.validate_body_and_headers_except(DEFAULT_RELAXED_HEADERS, media_type) + .await + } + /// Validate actual requests against expected requests pub async fn validate( self, checked_headers: &[&str], body_comparer: impl Fn(&[u8], &[u8]) -> Result<(), Box>, ) -> Result<(), Box> { - self.validate_base(Some(checked_headers), body_comparer) + self.validate_base(HeadersToCheck::Include(checked_headers), body_comparer) .await } /// Validate that the bodies match, using a given [`MediaType`] for comparison /// - /// The specified headers are also validated + /// The specified headers are also validated. If `checked_headers` is a `None`, it means + /// checking all headers. pub async fn validate_body_and_headers( self, checked_headers: Option<&[&str]>, media_type: &str, ) -> Result<(), Box> { - self.validate_base(checked_headers, |b1, b2| { + let headers_to_check = match checked_headers { + Some(headers) => HeadersToCheck::Include(headers), + None => HeadersToCheck::Exclude(None), + }; + self.validate_base(headers_to_check, |b1, b2| { + aws_smithy_protocol_test::validate_body( + b1, + std::str::from_utf8(b2).unwrap(), + MediaType::from(media_type), + ) + .map_err(|e| Box::new(e) as _) + }) + .await + } + + /// Validate that the bodies match, using a given [`MediaType`] for comparison + /// + /// The headers are also validated unless listed in `excluded_headers` + pub async fn validate_body_and_headers_except( + self, + excluded_headers: &[&str], + media_type: &str, + ) -> Result<(), Box> { + self.validate_base(HeadersToCheck::Exclude(Some(excluded_headers)), |b1, b2| { aws_smithy_protocol_test::validate_body( b1, std::str::from_utf8(b2).unwrap(), @@ -109,7 +150,7 @@ impl ReplayingClient { async fn validate_base( self, - checked_headers: Option<&[&str]>, + checked_headers: HeadersToCheck<'_>, body_comparer: impl Fn(&[u8], &[u8]) -> Result<(), Box>, ) -> Result<(), Box> { let mut actual_requests = @@ -133,8 +174,11 @@ impl ReplayingClient { .keys() .map(|k| k.as_str()) .filter(|k| match checked_headers { - Some(list) => list.contains(k), - None => true, + HeadersToCheck::Include(headers) => headers.contains(k), + HeadersToCheck::Exclude(excluded) => match excluded { + Some(headers) => !headers.contains(k), + None => true, + }, }) .flat_map(|key| { let _ = expected.headers().get(key)?; diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs index b47bedfac0..adbd5aa5bf 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs @@ -18,6 +18,8 @@ use std::sync::{Arc, Mutex, MutexGuard}; type ReplayEvents = Vec; +pub(crate) const DEFAULT_RELAXED_HEADERS: &[&str] = &["x-amz-user-agent", "authorization"]; + /// Test data for the [`StaticReplayClient`]. /// /// Each `ReplayEvent` represents one HTTP request and response @@ -230,6 +232,17 @@ impl StaticReplayClient { self.requests().len() ); } + + /// Convenience method for `assert_requests_match` that excludes the pre-defined headers to + /// be ignored + /// + /// The pre-defined headers to be ignored: + /// - x-amz-user-agent + /// - authorization + #[track_caller] + pub fn relaxed_requests_match(&self) { + self.assert_requests_match(DEFAULT_RELAXED_HEADERS) + } } impl HttpConnector for StaticReplayClient {