13
13
14
14
namespace RoachPHP \Tests \Downloader \Middleware ;
15
15
16
+ use InvalidArgumentException ;
17
+ use PHPUnit \Framework \Attributes \DataProvider ;
16
18
use PHPUnit \Framework \TestCase ;
17
19
use RoachPHP \Downloader \Middleware \RetryMiddleware ;
18
20
use RoachPHP \Scheduling \ArrayRequestScheduler ;
19
21
use RoachPHP \Scheduling \Timing \ClockInterface ;
20
- use RoachPHP \Scheduling \Timing \FakeClock ;
21
22
use RoachPHP \Testing \Concerns \InteractsWithRequestsAndResponses ;
22
23
use RoachPHP \Testing \FakeLogger ;
23
24
@@ -69,7 +70,7 @@ public function testRetriesARetryableResponse(): void
69
70
$ this ->middleware ->configure ([
70
71
'retryOnStatus ' => [503 ],
71
72
'maxRetries ' => 2 ,
72
- 'initialDelay ' => 500 ,
73
+ 'backoff ' => [ 1 , 2 , 3 ] ,
73
74
]);
74
75
75
76
$ result = $ this ->middleware ->handleResponse ($ response );
@@ -82,14 +83,14 @@ public function testRetriesARetryableResponse(): void
82
83
$ retriedRequest = $ retriedRequests [0 ];
83
84
self ::assertSame (1 , $ retriedRequest ->getMeta ('retry_count ' ));
84
85
self ::assertSame ('https://example.com ' , $ retriedRequest ->getUri ());
85
- self ::assertSame (500 , $ retriedRequest ->getOptions ()['delay ' ]);
86
+ self ::assertSame (1000 , $ retriedRequest ->getOptions ()['delay ' ]);
86
87
}
87
88
88
89
public function testStopsRetryingAfterMaxRetries (): void
89
90
{
90
91
$ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 3 );
91
92
$ response = $ this ->makeResponse (request: $ request , status: 500 );
92
- $ this ->middleware ->configure (['maxRetries ' => 3 ]);
93
+ $ this ->middleware ->configure (['maxRetries ' => 3 , ' backoff ' => [ 1 , 2 , 3 ] ]);
93
94
94
95
$ result = $ this ->middleware ->handleResponse ($ response );
95
96
@@ -98,20 +99,61 @@ public function testStopsRetryingAfterMaxRetries(): void
98
99
self ::assertCount (0 , $ this ->scheduler ->forceNextRequests (10 ));
99
100
}
100
101
101
- public function testCalculatesExponentialBackoffCorrectly (): void
102
+ public function testUsesBackoffArrayForDelay (): void
102
103
{
103
104
$ request = $ this ->makeRequest ()->withMeta ('retry_count ' , 2 );
104
105
$ 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 ]);
109
131
110
132
$ this ->middleware ->handleResponse ($ response );
111
133
112
- // initialDelay * (delayMultiplier ^ retry_count)
113
- // 1000 * (2.0 ^ 2) = 1000 * 4 = 4000ms
114
134
$ 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 );
116
158
}
117
159
}
0 commit comments