1313
1414namespace RoachPHP \Tests \Downloader \Middleware ;
1515
16+ use InvalidArgumentException ;
17+ use PHPUnit \Framework \Attributes \DataProvider ;
1618use PHPUnit \Framework \TestCase ;
1719use RoachPHP \Downloader \Middleware \RetryMiddleware ;
1820use RoachPHP \Scheduling \ArrayRequestScheduler ;
1921use RoachPHP \Scheduling \Timing \ClockInterface ;
20- use RoachPHP \Scheduling \Timing \FakeClock ;
2122use RoachPHP \Testing \Concerns \InteractsWithRequestsAndResponses ;
2223use RoachPHP \Testing \FakeLogger ;
2324
@@ -69,7 +70,7 @@ public function testRetriesARetryableResponse(): void
6970 $ this ->middleware ->configure ([
7071 'retryOnStatus ' => [503 ],
7172 'maxRetries ' => 2 ,
72- 'initialDelay ' => 500 ,
73+ 'backoff ' => [ 1 , 2 , 3 ] ,
7374 ]);
7475
7576 $ result = $ this ->middleware ->handleResponse ($ response );
@@ -82,14 +83,14 @@ public function testRetriesARetryableResponse(): void
8283 $ retriedRequest = $ retriedRequests [0 ];
8384 self ::assertSame (1 , $ retriedRequest ->getMeta ('retry_count ' ));
8485 self ::assertSame ('https://example.com ' , $ retriedRequest ->getUri ());
85- self ::assertSame (500 , $ retriedRequest ->getOptions ()['delay ' ]);
86+ self ::assertSame (1000 , $ retriedRequest ->getOptions ()['delay ' ]);
8687 }
8788
8889 public function testStopsRetryingAfterMaxRetries (): void
8990 {
9091 $ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 3 );
9192 $ response = $ this ->makeResponse (request: $ request , status: 500 );
92- $ this ->middleware ->configure (['maxRetries ' => 3 ]);
93+ $ this ->middleware ->configure (['maxRetries ' => 3 , ' backoff ' => [ 1 , 2 , 3 ] ]);
9394
9495 $ result = $ this ->middleware ->handleResponse ($ response );
9596
@@ -98,20 +99,61 @@ public function testStopsRetryingAfterMaxRetries(): void
9899 self ::assertCount (0 , $ this ->scheduler ->forceNextRequests (10 ));
99100 }
100101
101- public function testCalculatesExponentialBackoffCorrectly (): void
102+ public function testUsesBackoffArrayForDelay (): void
102103 {
103104 $ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 2 );
104105 $ response = $ this ->makeResponse (request: $ request , status: 500 );
105- $ this ->middleware ->configure ([
106- 'initialDelay ' => 1000 , // 1s
107- 'delayMultiplier ' => 2.0 ,
108- ]);
106+ $ this ->middleware ->configure (['backoff ' => [1 , 5 , 10 ]]);
107+
108+ $ this ->middleware ->handleResponse ($ response );
109+
110+ $ retriedRequest = $ this ->scheduler ->forceNextRequests (10 )[0 ];
111+ self ::assertSame (10000 , $ retriedRequest ->getOptions ()['delay ' ]);
112+ }
113+
114+ public function testUsesLastBackoffValueIfRetriesExceedBackoffCount (): void
115+ {
116+ $ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 5 );
117+ $ response = $ this ->makeResponse (request: $ request , status: 500 );
118+ $ this ->middleware ->configure (['backoff ' => [1 , 5 , 10 ], 'maxRetries ' => 6 ]);
119+
120+ $ this ->middleware ->handleResponse ($ response );
121+
122+ $ retriedRequest = $ this ->scheduler ->forceNextRequests (10 )[0 ];
123+ self ::assertSame (10000 , $ retriedRequest ->getOptions ()['delay ' ]);
124+ }
125+
126+ public function testUsesIntegerBackoffForDelay (): void
127+ {
128+ $ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 2 );
129+ $ response = $ this ->makeResponse (request: $ request , status: 500 );
130+ $ this ->middleware ->configure (['backoff ' => 5 ]);
109131
110132 $ this ->middleware ->handleResponse ($ response );
111133
112- // initialDelay * (delayMultiplier ^ retry_count)
113- // 1000 * (2.0 ^ 2) = 1000 * 4 = 4000ms
114134 $ retriedRequest = $ this ->scheduler ->forceNextRequests (10 )[0 ];
115- self ::assertSame (4000 , $ retriedRequest ->getOptions ()['delay ' ]);
135+ self ::assertSame (5000 , $ retriedRequest ->getOptions ()['delay ' ]);
136+ }
137+
138+ public static function invalidBackoffProvider (): array
139+ {
140+ return [
141+ 'empty array ' => [[], 'backoff array cannot be empty. ' ],
142+ 'array with non-int ' => [[1 , 'a ' , 3 ], 'backoff array must contain only integers. Found: string ' ],
143+ 'string ' => ['not-an-array ' , 'backoff must be an integer or array, string given. ' ],
144+ 'float ' => [1.23 , 'backoff must be an integer or array, double given. ' ],
145+ ];
146+ }
147+
148+ #[DataProvider('invalidBackoffProvider ' )]
149+ public function testThrowsExceptionOnInvalidBackoff (mixed $ backoff , string $ expectedMessage ): void
150+ {
151+ $ this ->expectException (InvalidArgumentException::class);
152+ $ this ->expectExceptionMessage ($ expectedMessage );
153+
154+ $ response = $ this ->makeResponse (status: 500 );
155+ $ this ->middleware ->configure (['backoff ' => $ backoff ]);
156+
157+ $ this ->middleware ->handleResponse ($ response );
116158 }
117159}
0 commit comments