@@ -111,8 +111,12 @@ impl Intercept for MockResponseInterceptor {
111111 while i < rules. len ( ) && matching_response. is_none ( ) {
112112 let rule = & rules[ i] ;
113113
114- // Check if the rule is already exhausted
115- if rule. is_exhausted ( ) {
114+ // Check if the rule is already exhausted or if it's a simple rule used once
115+ //
116+ // In `aws-smithy-mocks-experimental` all rules were infinite sequences
117+ // but were only usable once in sequential mode. We retain that here for
118+ // backwards compatibility.
119+ if rule. is_exhausted ( ) || ( rule. is_simple ( ) && rule. num_calls ( ) > 0 ) {
116120 // Rule is exhausted, remove it and try the next one
117121 rules. remove ( i) ;
118122 continue ; // Don't increment i since we removed an element
@@ -444,7 +448,7 @@ mod tests {
444448 expected = "must_match was enabled but no rules matched or all rules were exhausted for"
445449 ) ]
446450 #[ tokio:: test]
447- async fn test_exhausted_rules ( ) {
451+ async fn test_exhausted_rules_sequential ( ) {
448452 // Create a rule with a single response
449453 let rule = create_rule_builder ( ) . then_output ( || TestOutput :: new ( "only response" ) ) ;
450454
@@ -503,6 +507,14 @@ mod tests {
503507 // Verify the rules were used the expected number of times
504508 assert_eq ! ( rule1. num_calls( ) , 1 ) ;
505509 assert_eq ! ( rule2. num_calls( ) , 1 ) ;
510+
511+ // Calling with bucket1 again should match rule1 a second time
512+ 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 ) ;
506518 }
507519
508520 #[ tokio:: test]
@@ -553,9 +565,63 @@ mod tests {
553565 // Verify the rule was used the expected number of times
554566 assert_eq ! ( rule. num_calls( ) , 3 ) ;
555567 }
568+ #[ tokio:: test]
569+ async fn test_exhausted_sequence_match_any ( ) {
570+ // Create a rule with a sequence that will be exhausted
571+ 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+ // Create another rule to use after the first one is exhausted
579+ let fallback_rule =
580+ create_rule_builder ( ) . then_output ( || TestOutput :: new ( "fallback response" ) ) ;
581+
582+ // Create an interceptor with both rules
583+ 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+ // First two calls should use the first rule
591+ 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+ // second should use our fallback rule
598+ 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+ // Third call should use the first rule again and exhaust it
605+ 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+ // first rule is exhausted so the matcher shouldn't matter and we should hit our fallback rule
612+ 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+ // Verify the rules were used the expected number of times
619+ assert_eq ! ( rule. num_calls( ) , 2 ) ;
620+ assert_eq ! ( fallback_rule. num_calls( ) , 2 ) ;
621+ }
556622
557623 #[ tokio:: test]
558- async fn test_exhausted_sequence ( ) {
624+ async fn test_exhausted_sequence_sequential ( ) {
559625 // Create a rule with a sequence that will be exhausted
560626 let rule = create_rule_builder ( )
561627 . sequence ( )
@@ -695,4 +761,118 @@ mod tests {
695761 assert_eq ! ( result2. unwrap( ) , TestOutput :: new( "success" ) ) ;
696762 assert_eq ! ( rule2. num_calls( ) , 1 ) ;
697763 }
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+ // Second call should use rule2 (rule1 should be removed after one use in Sequential mode)
805+ 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+ // all subsequent calls should return "repeated response"
837+ 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+ // This should panic because times() is called before adding any responses
852+ 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+ // This should panic because repeatedly() is called before adding any responses
863+ let _rule = create_rule_builder ( ) . sequence ( ) . repeatedly ( ) . build ( ) ;
864+ }
865+
866+ #[ test]
867+ fn test_total_responses_overflow ( ) {
868+ // Create a rule with a large number of repetitions to test overflow handling
869+ 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+ }
698878}
0 commit comments