1use crate::{MockResponse, Rule, RuleMode};
7use aws_smithy_http_client::test_util::infallible_client_fn;
8use aws_smithy_runtime_api::box_error::BoxError;
9use aws_smithy_runtime_api::client::http::SharedHttpClient;
10use aws_smithy_runtime_api::client::interceptors::context::{
11 BeforeSerializationInterceptorContextMut, BeforeTransmitInterceptorContextMut, Error,
12 FinalizerInterceptorContextMut, Output,
13};
14use aws_smithy_runtime_api::client::interceptors::Intercept;
15use aws_smithy_runtime_api::client::orchestrator::{HttpResponse, OrchestratorError};
16use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
17use aws_smithy_types::body::SdkBody;
18use aws_smithy_types::config_bag::{ConfigBag, Storable, StoreReplace};
19use std::collections::VecDeque;
20use std::fmt;
21use std::sync::{Arc, Mutex};
22
23#[derive(Debug, Clone)]
25struct ActiveRule(Rule);
26
27impl Storable for ActiveRule {
28 type Storer = StoreReplace<ActiveRule>;
29}
30
31pub struct MockResponseInterceptor {
33 rules: Arc<Mutex<VecDeque<Rule>>>,
34 rule_mode: RuleMode,
35 must_match: bool,
36 active_response: Arc<Mutex<Option<MockResponse<Output, Error>>>>,
37}
38
39impl fmt::Debug for MockResponseInterceptor {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{} rules", self.rules.lock().unwrap().len())
42 }
43}
44
45impl Default for MockResponseInterceptor {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl MockResponseInterceptor {
52 pub fn new() -> Self {
56 Self {
57 rules: Default::default(),
58 rule_mode: RuleMode::MatchAny,
59 must_match: true,
60 active_response: Default::default(),
61 }
62 }
63 pub fn with_rule(self, rule: &Rule) -> Self {
67 self.rules.lock().unwrap().push_back(rule.clone());
68 self
69 }
70
71 pub fn rule_mode(mut self, rule_mode: RuleMode) -> Self {
75 self.rule_mode = rule_mode;
76 self
77 }
78
79 pub fn allow_passthrough(mut self) -> Self {
84 self.must_match = false;
85 self
86 }
87}
88
89impl Intercept for MockResponseInterceptor {
90 fn name(&self) -> &'static str {
91 "MockResponseInterceptor"
92 }
93
94 fn modify_before_serialization(
95 &self,
96 context: &mut BeforeSerializationInterceptorContextMut<'_>,
97 _runtime_components: &RuntimeComponents,
98 cfg: &mut ConfigBag,
99 ) -> Result<(), BoxError> {
100 let mut rules = self.rules.lock().unwrap();
101 let input = context.inner().input().expect("input set");
102
103 let mut matching_rule = None;
105 let mut matching_response = None;
106
107 match self.rule_mode {
108 RuleMode::Sequential => {
109 let i = 0;
111 while i < rules.len() && matching_response.is_none() {
112 let rule = &rules[i];
113
114 if rule.is_exhausted() || (rule.is_simple() && rule.num_calls() > 0) {
120 rules.remove(i);
122 continue; }
124
125 if !(rule.matcher)(input) {
127 panic!(
129 "In order matching was enforced but rule did not match {:?}",
130 input
131 );
132 }
133
134 if let Some(response) = rule.next_response() {
136 matching_rule = Some(rule.clone());
137 matching_response = Some(response);
138 } else {
139 rules.remove(i);
141 continue; }
143
144 break;
146 }
147 }
148 RuleMode::MatchAny => {
149 for rule in rules.iter() {
151 if rule.is_exhausted() {
153 continue;
154 }
155
156 if (rule.matcher)(input) {
157 if let Some(response) = rule.next_response() {
158 matching_rule = Some(rule.clone());
159 matching_response = Some(response);
160 break;
161 }
162 }
163 }
164 }
165 };
166
167 match (matching_rule, matching_response) {
168 (Some(rule), Some(response)) => {
169 cfg.interceptor_state().store_put(ActiveRule(rule));
171 let mut active_resp = self.active_response.lock().unwrap();
174 let _ = std::mem::replace(&mut *active_resp, Some(response));
175 }
176 _ => {
177 if self.must_match {
179 panic!(
180 "must_match was enabled but no rules matched or all rules were exhausted for {:?}",
181 input
182 );
183 }
184 }
185 }
186
187 Ok(())
188 }
189
190 fn modify_before_transmit(
191 &self,
192 context: &mut BeforeTransmitInterceptorContextMut<'_>,
193 _runtime_components: &RuntimeComponents,
194 cfg: &mut ConfigBag,
195 ) -> Result<(), BoxError> {
196 let mut state = self.active_response.lock().unwrap();
197 let mut active_response = (*state).take();
198 if active_response.is_none() {
199 if let Some(active_rule) = cfg.load::<ActiveRule>() {
201 let next_resp = active_rule.0.next_response();
202 active_response = next_resp;
203 }
204 }
205
206 if let Some(resp) = active_response {
207 match resp {
208 MockResponse::Http(http_resp) => {
210 context
211 .request_mut()
212 .add_extension(MockHttpResponse(Arc::new(http_resp)));
213 }
214 _ => {
215 let _ = std::mem::replace(&mut *state, Some(resp));
217 }
218 }
219 }
220
221 Ok(())
222 }
223
224 fn modify_before_attempt_completion(
225 &self,
226 context: &mut FinalizerInterceptorContextMut<'_>,
227 _runtime_components: &RuntimeComponents,
228 _cfg: &mut ConfigBag,
229 ) -> Result<(), BoxError> {
230 let mut state = self.active_response.lock().unwrap();
232 let active_response = (*state).take();
233 if let Some(resp) = active_response {
234 match resp {
235 MockResponse::Output(output) => {
236 context.inner_mut().set_output_or_error(Ok(output));
237 }
238 MockResponse::Error(error) => {
239 context
240 .inner_mut()
241 .set_output_or_error(Err(OrchestratorError::operation(error)));
242 }
243 MockResponse::Http(_) => {
244 }
246 }
247 }
248
249 Ok(())
250 }
251}
252
253#[derive(Clone)]
255struct MockHttpResponse(Arc<HttpResponse>);
256
257pub fn create_mock_http_client() -> SharedHttpClient {
259 infallible_client_fn(|mut req| {
260 if let Some(mock_response) = req.extensions_mut().remove::<MockHttpResponse>() {
262 let http_resp =
263 Arc::try_unwrap(mock_response.0).expect("mock HTTP response has single reference");
264 return http_resp.try_into_http1x().unwrap();
265 }
266
267 http::Response::builder()
269 .status(418)
270 .body(SdkBody::from("Mock HTTP client dummy response"))
271 .unwrap()
272 })
273}
274
275#[cfg(test)]
276mod tests {
277 use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep};
278 use aws_smithy_runtime::client::orchestrator::operation::Operation;
279 use aws_smithy_runtime::client::retries::classifiers::HttpStatusCodeClassifier;
280 use aws_smithy_runtime_api::client::orchestrator::{
281 HttpRequest, HttpResponse, OrchestratorError,
282 };
283 use aws_smithy_runtime_api::client::result::SdkError;
284 use aws_smithy_runtime_api::http::StatusCode;
285 use aws_smithy_types::body::SdkBody;
286 use aws_smithy_types::retry::RetryConfig;
287 use aws_smithy_types::timeout::TimeoutConfig;
288
289 use crate::{create_mock_http_client, MockResponseInterceptor, RuleBuilder, RuleMode};
290 use std::time::Duration;
291
292 #[derive(Debug)]
294 struct TestInput {
295 bucket: String,
296 key: String,
297 }
298 impl TestInput {
299 fn new(bucket: &str, key: &str) -> Self {
300 Self {
301 bucket: bucket.to_string(),
302 key: key.to_string(),
303 }
304 }
305 }
306
307 #[derive(Debug, PartialEq)]
308 struct TestOutput {
309 content: String,
310 }
311
312 impl TestOutput {
313 fn new(content: &str) -> Self {
314 Self {
315 content: content.to_string(),
316 }
317 }
318 }
319
320 #[derive(Debug)]
321 struct TestError {
322 message: String,
323 }
324
325 impl TestError {
326 fn new(message: &str) -> Self {
327 Self {
328 message: message.to_string(),
329 }
330 }
331 }
332
333 impl std::fmt::Display for TestError {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 write!(f, "{}", self.message)
336 }
337 }
338
339 impl std::error::Error for TestError {}
340
341 fn create_rule_builder() -> RuleBuilder<TestInput, TestOutput, TestError> {
343 RuleBuilder::new_from_mock(
344 || TestInput {
345 bucket: "".to_string(),
346 key: "".to_string(),
347 },
348 || {
349 let fut: std::future::Ready<Result<TestOutput, SdkError<TestError, HttpResponse>>> =
350 std::future::ready(Ok(TestOutput {
351 content: "".to_string(),
352 }));
353 fut
354 },
355 )
356 }
357
358 fn create_test_operation(
360 interceptor: MockResponseInterceptor,
361 enable_retries: bool,
362 ) -> Operation<TestInput, TestOutput, TestError> {
363 let builder = Operation::builder()
364 .service_name("test")
365 .operation_name("test")
366 .http_client(create_mock_http_client())
367 .endpoint_url("http://localhost:1234")
368 .no_auth()
369 .sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
370 .timeout_config(TimeoutConfig::disabled())
371 .interceptor(interceptor)
372 .serializer(|input: TestInput| {
373 let mut request = HttpRequest::new(SdkBody::empty());
374 request
375 .set_uri(format!("/{}/{}", input.bucket, input.key))
376 .expect("valid URI");
377 Ok(request)
378 })
379 .deserializer::<TestOutput, TestError>(|response| {
380 if response.status().is_success() {
381 let body = std::str::from_utf8(response.body().bytes().unwrap())
382 .unwrap_or("empty body")
383 .to_string();
384 Ok(TestOutput { content: body })
385 } else {
386 Err(OrchestratorError::operation(TestError {
387 message: format!("Error: {}", response.status()),
388 }))
389 }
390 });
391
392 if enable_retries {
393 let retry_config = RetryConfig::standard()
394 .with_max_attempts(5)
395 .with_initial_backoff(Duration::from_millis(1))
396 .with_max_backoff(Duration::from_millis(5));
397
398 builder
399 .retry_classifier(HttpStatusCodeClassifier::default())
400 .standard_retry(&retry_config)
401 .build()
402 } else {
403 builder.no_retry().build()
404 }
405 }
406
407 #[tokio::test]
408 async fn test_retry_sequence() {
409 let rule = create_rule_builder()
411 .match_requests(|input| input.bucket == "test-bucket" && input.key == "test-key")
412 .sequence()
413 .http_status(503, None)
414 .times(2)
415 .output(|| TestOutput::new("success after retries"))
416 .build();
417
418 let interceptor = MockResponseInterceptor::new()
420 .rule_mode(RuleMode::Sequential)
421 .with_rule(&rule);
422
423 let operation = create_test_operation(interceptor, true);
424
425 let result = operation
427 .invoke(TestInput::new("test-bucket", "test-key"))
428 .await;
429
430 assert!(
432 result.is_ok(),
433 "Expected success but got error: {:?}",
434 result.err()
435 );
436 assert_eq!(
437 result.unwrap(),
438 TestOutput {
439 content: "success after retries".to_string()
440 }
441 );
442
443 assert_eq!(rule.num_calls(), 3);
445 }
446
447 #[should_panic(
448 expected = "must_match was enabled but no rules matched or all rules were exhausted for"
449 )]
450 #[tokio::test]
451 async fn test_exhausted_rules_sequential() {
452 let rule = create_rule_builder().then_output(|| TestOutput::new("only response"));
454
455 let interceptor = MockResponseInterceptor::new()
457 .rule_mode(RuleMode::Sequential)
458 .with_rule(&rule);
459
460 let operation = create_test_operation(interceptor, false);
461
462 let result1 = operation
464 .invoke(TestInput::new("test-bucket", "test-key"))
465 .await;
466 assert!(result1.is_ok());
467
468 let _result2 = operation
470 .invoke(TestInput::new("test-bucket", "test-key"))
471 .await;
472 }
473
474 #[tokio::test]
475 async fn test_rule_mode_match_any() {
476 let rule1 = create_rule_builder()
478 .match_requests(|input| input.bucket == "bucket1")
479 .then_output(|| TestOutput::new("response1"));
480
481 let rule2 = create_rule_builder()
482 .match_requests(|input| input.bucket == "bucket2")
483 .then_output(|| TestOutput::new("response2"));
484
485 let interceptor = MockResponseInterceptor::new()
487 .rule_mode(RuleMode::MatchAny)
488 .with_rule(&rule1)
489 .with_rule(&rule2);
490
491 let operation = create_test_operation(interceptor, false);
492
493 let result1 = operation
495 .invoke(TestInput::new("bucket1", "test-key"))
496 .await;
497 assert!(result1.is_ok());
498 assert_eq!(result1.unwrap(), TestOutput::new("response1"));
499
500 let result2 = operation
502 .invoke(TestInput::new("bucket2", "test-key"))
503 .await;
504 assert!(result2.is_ok());
505 assert_eq!(result2.unwrap(), TestOutput::new("response2"));
506
507 assert_eq!(rule1.num_calls(), 1);
509 assert_eq!(rule2.num_calls(), 1);
510
511 let result1 = operation
513 .invoke(TestInput::new("bucket1", "test-key"))
514 .await;
515 assert!(result1.is_ok());
516 assert_eq!(result1.unwrap(), TestOutput::new("response1"));
517 assert_eq!(rule1.num_calls(), 2);
518 }
519
520 #[tokio::test]
521 async fn test_mixed_response_types() {
522 let rule = create_rule_builder()
524 .sequence()
525 .output(|| TestOutput::new("first output"))
526 .error(|| TestError::new("expected error"))
527 .http_response(|| {
528 HttpResponse::new(
529 StatusCode::try_from(200).unwrap(),
530 SdkBody::from("http response"),
531 )
532 })
533 .build();
534
535 let interceptor = MockResponseInterceptor::new()
537 .rule_mode(RuleMode::Sequential)
538 .with_rule(&rule);
539
540 let operation = create_test_operation(interceptor, false);
541
542 let result1 = operation
544 .invoke(TestInput::new("test-bucket", "test-key"))
545 .await;
546 assert!(result1.is_ok());
547 assert_eq!(result1.unwrap(), TestOutput::new("first output"));
548
549 let result2 = operation
551 .invoke(TestInput::new("test-bucket", "test-key"))
552 .await;
553 assert!(result2.is_err());
554 let sdk_err = result2.unwrap_err();
555 let err = sdk_err.as_service_error().expect("expected service error");
556 assert_eq!(err.to_string(), "expected error");
557
558 let result3 = operation
560 .invoke(TestInput::new("test-bucket", "test-key"))
561 .await;
562 assert!(result3.is_ok());
563 assert_eq!(result3.unwrap(), TestOutput::new("http response"));
564
565 assert_eq!(rule.num_calls(), 3);
567 }
568 #[tokio::test]
569 async fn test_exhausted_sequence_match_any() {
570 let rule = create_rule_builder()
572 .match_requests(|input| input.bucket == "bucket-1")
573 .sequence()
574 .output(|| TestOutput::new("response 1"))
575 .output(|| TestOutput::new("response 2"))
576 .build();
577
578 let fallback_rule =
580 create_rule_builder().then_output(|| TestOutput::new("fallback response"));
581
582 let interceptor = MockResponseInterceptor::new()
584 .rule_mode(RuleMode::MatchAny)
585 .with_rule(&rule)
586 .with_rule(&fallback_rule);
587
588 let operation = create_test_operation(interceptor, false);
589
590 let result1 = operation
592 .invoke(TestInput::new("bucket-1", "test-key"))
593 .await;
594 assert!(result1.is_ok());
595 assert_eq!(result1.unwrap(), TestOutput::new("response 1"));
596
597 let result2 = operation
599 .invoke(TestInput::new("other-bucket", "test-key"))
600 .await;
601 assert!(result2.is_ok());
602 assert_eq!(result2.unwrap(), TestOutput::new("fallback response"));
603
604 let result3 = operation
606 .invoke(TestInput::new("bucket-1", "test-key"))
607 .await;
608 assert!(result3.is_ok());
609 assert_eq!(result3.unwrap(), TestOutput::new("response 2"));
610
611 let result4 = operation
613 .invoke(TestInput::new("bucket-1", "test-key"))
614 .await;
615 assert!(result4.is_ok());
616 assert_eq!(result4.unwrap(), TestOutput::new("fallback response"));
617
618 assert_eq!(rule.num_calls(), 2);
620 assert_eq!(fallback_rule.num_calls(), 2);
621 }
622
623 #[tokio::test]
624 async fn test_exhausted_sequence_sequential() {
625 let rule = create_rule_builder()
627 .sequence()
628 .output(|| TestOutput::new("response 1"))
629 .output(|| TestOutput::new("response 2"))
630 .build();
631
632 let fallback_rule =
634 create_rule_builder().then_output(|| TestOutput::new("fallback response"));
635
636 let interceptor = MockResponseInterceptor::new()
638 .rule_mode(RuleMode::Sequential)
639 .with_rule(&rule)
640 .with_rule(&fallback_rule);
641
642 let operation = create_test_operation(interceptor, false);
643
644 let result1 = operation
646 .invoke(TestInput::new("test-bucket", "test-key"))
647 .await;
648 assert!(result1.is_ok());
649 assert_eq!(result1.unwrap(), TestOutput::new("response 1"));
650
651 let result2 = operation
652 .invoke(TestInput::new("test-bucket", "test-key"))
653 .await;
654 assert!(result2.is_ok());
655 assert_eq!(result2.unwrap(), TestOutput::new("response 2"));
656
657 let result3 = operation
659 .invoke(TestInput::new("test-bucket", "test-key"))
660 .await;
661 assert!(result3.is_ok());
662 assert_eq!(result3.unwrap(), TestOutput::new("fallback response"));
663
664 assert_eq!(rule.num_calls(), 2);
666 assert_eq!(fallback_rule.num_calls(), 1);
667 }
668
669 #[tokio::test]
670 async fn test_concurrent_usage() {
671 use std::sync::Arc;
672 use tokio::task;
673
674 let rule = Arc::new(
676 create_rule_builder()
677 .sequence()
678 .output(|| TestOutput::new("response 1"))
679 .output(|| TestOutput::new("response 2"))
680 .output(|| TestOutput::new("response 3"))
681 .build(),
682 );
683
684 let interceptor = MockResponseInterceptor::new()
686 .rule_mode(RuleMode::Sequential)
687 .with_rule(&rule);
688
689 let operation = Arc::new(create_test_operation(interceptor, false));
690
691 let mut handles = vec![];
693 for i in 0..3 {
694 let op = operation.clone();
695 let handle = task::spawn(async move {
696 let result = op
697 .invoke(TestInput::new(&format!("bucket-{}", i), "test-key"))
698 .await;
699 result.unwrap()
700 });
701 handles.push(handle);
702 }
703
704 let mut results = vec![];
706 for handle in handles {
707 results.push(handle.await.unwrap());
708 }
709
710 results.sort_by(|a, b| a.content.cmp(&b.content));
712
713 assert_eq!(results.len(), 3);
715 assert_eq!(results[0], TestOutput::new("response 1"));
716 assert_eq!(results[1], TestOutput::new("response 2"));
717 assert_eq!(results[2], TestOutput::new("response 3"));
718
719 assert_eq!(rule.num_calls(), 3);
721 }
722
723 #[tokio::test]
724 async fn test_sequential_rule_removal() {
725 let rule1 = create_rule_builder()
727 .match_requests(|input| input.bucket == "test-bucket" && input.key != "correct-key")
728 .then_http_response(|| {
729 HttpResponse::new(
730 StatusCode::try_from(404).unwrap(),
731 SdkBody::from("not found"),
732 )
733 });
734
735 let rule2 = create_rule_builder()
737 .match_requests(|input| input.bucket == "test-bucket" && input.key == "correct-key")
738 .then_output(|| TestOutput::new("success"));
739
740 let interceptor = MockResponseInterceptor::new()
742 .rule_mode(RuleMode::Sequential)
743 .with_rule(&rule1)
744 .with_rule(&rule2);
745
746 let operation = create_test_operation(interceptor, true);
747
748 let result1 = operation.invoke(TestInput::new("test-bucket", "foo")).await;
750 assert!(result1.is_err());
751 assert_eq!(rule1.num_calls(), 1);
752
753 let result2 = operation
756 .invoke(TestInput::new("test-bucket", "correct-key"))
757 .await;
758
759 assert!(result2.is_ok());
761 assert_eq!(result2.unwrap(), TestOutput::new("success"));
762 assert_eq!(rule2.num_calls(), 1);
763 }
764
765 #[tokio::test]
766 async fn test_simple_rule_in_match_any_mode() {
767 let rule = create_rule_builder().then_output(|| TestOutput::new("simple response"));
768
769 let interceptor = MockResponseInterceptor::new()
770 .rule_mode(RuleMode::MatchAny)
771 .with_rule(&rule);
772
773 let operation = create_test_operation(interceptor, false);
774
775 for i in 0..5 {
776 let result = operation
777 .invoke(TestInput::new("test-bucket", "test-key"))
778 .await;
779 assert!(result.is_ok(), "Call {} should succeed", i);
780 assert_eq!(result.unwrap(), TestOutput::new("simple response"));
781 }
782 assert_eq!(rule.num_calls(), 5);
783 assert!(!rule.is_exhausted());
784 }
785
786 #[tokio::test]
787 async fn test_simple_rule_in_sequential_mode() {
788 let rule1 = create_rule_builder().then_output(|| TestOutput::new("first response"));
789 let rule2 = create_rule_builder().then_output(|| TestOutput::new("second response"));
790
791 let interceptor = MockResponseInterceptor::new()
792 .rule_mode(RuleMode::Sequential)
793 .with_rule(&rule1)
794 .with_rule(&rule2);
795
796 let operation = create_test_operation(interceptor, false);
797
798 let result1 = operation
799 .invoke(TestInput::new("test-bucket", "test-key"))
800 .await;
801 assert!(result1.is_ok());
802 assert_eq!(result1.unwrap(), TestOutput::new("first response"));
803
804 let result2 = operation
806 .invoke(TestInput::new("test-bucket", "test-key"))
807 .await;
808 assert!(result2.is_ok());
809 assert_eq!(result2.unwrap(), TestOutput::new("second response"));
810
811 assert_eq!(rule1.num_calls(), 1);
812 assert_eq!(rule2.num_calls(), 1);
813 }
814
815 #[tokio::test]
816 async fn test_repeatedly_method() {
817 let rule = create_rule_builder()
818 .sequence()
819 .output(|| TestOutput::new("first response"))
820 .output(|| TestOutput::new("repeated response"))
821 .repeatedly()
822 .build();
823
824 let interceptor = MockResponseInterceptor::new()
825 .rule_mode(RuleMode::Sequential)
826 .with_rule(&rule);
827
828 let operation = create_test_operation(interceptor, false);
829
830 let result1 = operation
831 .invoke(TestInput::new("test-bucket", "test-key"))
832 .await;
833 assert!(result1.is_ok());
834 assert_eq!(result1.unwrap(), TestOutput::new("first response"));
835
836 for i in 0..10 {
838 let result = operation
839 .invoke(TestInput::new("test-bucket", "test-key"))
840 .await;
841 assert!(result.is_ok(), "Call {} should succeed", i);
842 assert_eq!(result.unwrap(), TestOutput::new("repeated response"));
843 }
844 assert_eq!(rule.num_calls(), 11);
845 assert!(!rule.is_exhausted());
846 }
847
848 #[should_panic(expected = "times(n) called before adding a response to the sequence")]
849 #[test]
850 fn test_times_validation() {
851 let _rule = create_rule_builder()
853 .sequence()
854 .times(3)
855 .output(|| TestOutput::new("response"))
856 .build();
857 }
858
859 #[should_panic(expected = "repeatedly() called before adding a response to the sequence")]
860 #[test]
861 fn test_repeatedly_validation() {
862 let _rule = create_rule_builder().sequence().repeatedly().build();
864 }
865
866 #[test]
867 fn test_total_responses_overflow() {
868 let rule = create_rule_builder()
870 .sequence()
871 .output(|| TestOutput::new("response"))
872 .times(usize::MAX / 2)
873 .output(|| TestOutput::new("another response"))
874 .repeatedly()
875 .build();
876 assert_eq!(rule.max_responses, usize::MAX);
877 }
878}