@@ -15,6 +15,7 @@ import SmithyRetriesAPI
15
15
import SmithyRetries
16
16
@_spi ( SmithyReadWrite) import SmithyJSON
17
17
@_spi ( SmithyReadWrite) import SmithyReadWrite
18
+ import SmithyStreams
18
19
19
20
class OrchestratorTests : XCTestCase {
20
21
struct TestInput {
@@ -167,20 +168,23 @@ class OrchestratorTests: XCTestCase {
167
168
}
168
169
169
170
class TraceExecuteRequest : ExecuteRequest {
170
- var succeedAfter : Int
171
+ let succeedAfter : Int
171
172
var trace : Trace
172
173
174
+ private( set) var requestCount = 0
175
+
173
176
init ( succeedAfter: Int = 0 , trace: Trace ) {
174
177
self . succeedAfter = succeedAfter
175
178
self . trace = trace
176
179
}
177
180
178
181
public func execute( request: HTTPRequest , attributes: Context ) async throws -> HTTPResponse {
179
182
trace. append ( " executeRequest " )
180
- if succeedAfter <= 0 {
183
+ if succeedAfter - requestCount <= 0 {
184
+ requestCount += 1
181
185
return HTTPResponse ( body: request. body, statusCode: . ok)
182
186
} else {
183
- succeedAfter - = 1
187
+ requestCount + = 1
184
188
return HTTPResponse ( body: request. body, statusCode: . internalServerError)
185
189
}
186
190
}
@@ -233,7 +237,7 @@ class OrchestratorTests: XCTestCase {
233
237
throw try UnknownHTTPServiceError . makeError ( baseError: baseError)
234
238
}
235
239
} )
236
- . retryStrategy ( DefaultRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ExponentialBackoffStrategy ( ) ) ) )
240
+ . retryStrategy ( DefaultRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ImmediateBackoffStrategy ( ) ) ) )
237
241
. retryErrorInfoProvider ( { e in
238
242
trace. append ( " errorInfo " )
239
243
return DefaultRetryErrorInfoProvider . errorInfo ( for: e)
@@ -530,7 +534,7 @@ class OrchestratorTests: XCTestCase {
530
534
let initialTokenTrace = Trace ( )
531
535
let initialToken = await asyncResult {
532
536
return try await self . traceOrchestrator ( trace: initialTokenTrace)
533
- . retryStrategy ( ThrowingRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ExponentialBackoffStrategy ( ) ) ) )
537
+ . retryStrategy ( ThrowingRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ImmediateBackoffStrategy ( ) ) ) )
534
538
. build ( )
535
539
. execute ( input: TestInput ( foo: " " ) )
536
540
}
@@ -1315,4 +1319,60 @@ class OrchestratorTests: XCTestCase {
1315
1319
}
1316
1320
}
1317
1321
}
1322
+
1323
+ /// Used in retry tests to perform the next retry without waiting, so that tests complete without delay.
1324
+ private struct ImmediateBackoffStrategy : RetryBackoffStrategy {
1325
+ func computeNextBackoffDelay( attempt: Int ) -> TimeInterval { 0.0 }
1326
+ }
1327
+
1328
+ func test_retry_retriesDataBody( ) async throws {
1329
+ let input = TestInput ( foo: " bar " )
1330
+ let trace = Trace ( )
1331
+ let executeRequest = TraceExecuteRequest ( succeedAfter: 2 , trace: trace)
1332
+ let orchestrator = traceOrchestrator ( trace: trace)
1333
+ . retryStrategy ( DefaultRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ImmediateBackoffStrategy ( ) ) ) )
1334
+ . serialize ( { ( input: TestInput , builder: HTTPRequestBuilder , context) in
1335
+ builder. withBody ( . data( Data ( " \" \( input. foo) \" " . utf8) ) )
1336
+ } )
1337
+ . executeRequest ( executeRequest)
1338
+ let result = await asyncResult {
1339
+ return try await orchestrator. build ( ) . execute ( input: input)
1340
+ }
1341
+ XCTAssertNoThrow ( try result. get ( ) )
1342
+ XCTAssertEqual ( executeRequest. requestCount, 3 )
1343
+ }
1344
+
1345
+ func test_retry_doesntRetryNonSeekableStreamBody( ) async throws {
1346
+ let input = TestInput ( foo: " bar " )
1347
+ let trace = Trace ( )
1348
+ let executeRequest = TraceExecuteRequest ( succeedAfter: 2 , trace: trace)
1349
+ let orchestrator = traceOrchestrator ( trace: trace)
1350
+ . retryStrategy ( DefaultRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ImmediateBackoffStrategy ( ) ) ) )
1351
+ . serialize ( { ( input: TestInput , builder: HTTPRequestBuilder , context) in
1352
+ builder. withBody ( . stream( BufferedStream ( data: Data ( " \" \( input. foo) \" " . utf8) , isClosed: true ) ) )
1353
+ } )
1354
+ . executeRequest ( executeRequest)
1355
+ let result = await asyncResult {
1356
+ return try await orchestrator. build ( ) . execute ( input: input)
1357
+ }
1358
+ XCTAssertThrowsError ( try result. get ( ) )
1359
+ XCTAssertEqual ( executeRequest. requestCount, 1 )
1360
+ }
1361
+
1362
+ func test_retry_nonSeekableStreamBodySucceeds( ) async throws {
1363
+ let input = TestInput ( foo: " bar " )
1364
+ let trace = Trace ( )
1365
+ let executeRequest = TraceExecuteRequest ( succeedAfter: 0 , trace: trace)
1366
+ let orchestrator = traceOrchestrator ( trace: trace)
1367
+ . retryStrategy ( DefaultRetryStrategy ( options: RetryStrategyOptions ( backoffStrategy: ImmediateBackoffStrategy ( ) ) ) )
1368
+ . serialize ( { ( input: TestInput , builder: HTTPRequestBuilder , context) in
1369
+ builder. withBody ( . stream( BufferedStream ( data: Data ( " \" \( input. foo) \" " . utf8) , isClosed: true ) ) )
1370
+ } )
1371
+ . executeRequest ( executeRequest)
1372
+ let result = await asyncResult {
1373
+ return try await orchestrator. build ( ) . execute ( input: input)
1374
+ }
1375
+ XCTAssertNoThrow ( try result. get ( ) )
1376
+ XCTAssertEqual ( executeRequest. requestCount, 1 )
1377
+ }
1318
1378
}
0 commit comments