Skip to content

Commit 7ae55d1

Browse files
MuniekMglawmicha
andauthored
fix(DataStore): endless retry of mutation request when server responds with 401 error code (#3511) (#3512)
* fix(DataStore): endless retry of mutation request when server responds with 401 error code (#3511) * fix(DataStore): endless retry of mutation request when server responds with 401 error code (#3511) * fix(DataStore): endless retry of mutation request when server responds with 401 error code (#3511) Co-authored-by: Michael Law <[email protected]> --------- Co-authored-by: Michael Law <[email protected]>
1 parent b3ac27d commit 7ae55d1

File tree

4 files changed

+118
-2
lines changed

4 files changed

+118
-2
lines changed

AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public protocol AuthorizationTypeIterator {
5555

5656
/// Total number of values
5757
var count: Int { get }
58+
59+
/// Whether iterator has next available `AuthorizationType` to return or not
60+
var hasNext: Bool { get }
5861

5962
/// Next available `AuthorizationType` or `nil` if exhausted
6063
mutating func next() -> AuthorizationType?
@@ -66,18 +69,29 @@ public struct AWSAuthorizationTypeIterator: AuthorizationTypeIterator {
6669

6770
private var values: IndexingIterator<[AWSAuthorizationType]>
6871
private var _count: Int
72+
private var _position: Int
6973

7074
public init(withValues values: [AWSAuthorizationType]) {
7175
self.values = values.makeIterator()
7276
self._count = values.count
77+
self._position = 0
7378
}
7479

7580
public var count: Int {
7681
_count
7782
}
83+
84+
public var hasNext: Bool {
85+
_position < _count
86+
}
7887

7988
public mutating func next() -> AWSAuthorizationType? {
80-
values.next()
89+
if let value = values.next() {
90+
_position += 1
91+
return value
92+
}
93+
94+
return nil
8195
}
8296
}
8397

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
321321

322322
/// - Warning: Must be invoked from a locking context
323323
private func shouldRetryWithDifferentAuthType() -> RequestRetryAdvice {
324-
let shouldRetry = (authTypesIterator?.count ?? 0) > 0
324+
let shouldRetry = authTypesIterator?.hasNext == true
325325
return RequestRetryAdvice(shouldRetry: shouldRetry, retryInterval: .milliseconds(0))
326326
}
327327

AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift

+62
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,68 @@ class SyncMutationToCloudOperationTests: XCTestCase {
292292
XCTAssertTrue(advice.shouldRetry)
293293
}
294294

295+
/// Given: Model with multiple auth types. Mutation requests always fail with 401 error code
296+
/// When: Mutating model fails with 401
297+
/// Then: DataStore will try again with each auth type and eventually fails
298+
func testGetRetryAdviceForEachModelAuthTypeThenFail_HTTPStatusError401() async throws {
299+
var numberOfTimesEntered = 0
300+
let mutationEvent = try createMutationEvent()
301+
let authStrategy = MockMultiAuthModeStrategy()
302+
let expectedNumberOfTimesEntered = authStrategy.authTypesFor(schema: mutationEvent.schema, operation: .create).count
303+
304+
let expectCalllToApiMutateNTimesAndFail = expectation(description: "Call API.mutate \(expectedNumberOfTimesEntered) times and then fail")
305+
306+
let response = HTTPURLResponse(url: URL(string: "http://localhost")!,
307+
statusCode: 401,
308+
httpVersion: nil,
309+
headerFields: nil)!
310+
let error = APIError.httpStatusError(401, response)
311+
312+
let operation = await SyncMutationToCloudOperation(
313+
mutationEvent: mutationEvent,
314+
getLatestSyncMetadata: { nil },
315+
api: mockAPIPlugin,
316+
authModeStrategy: authStrategy,
317+
networkReachabilityPublisher: publisher,
318+
currentAttemptNumber: 1,
319+
completion: { result in
320+
if numberOfTimesEntered == expectedNumberOfTimesEntered {
321+
expectCalllToApiMutateNTimesAndFail.fulfill()
322+
323+
} else {
324+
XCTFail("API.mutate was called incorrect amount of times, expected: \(expectedNumberOfTimesEntered), was : \(numberOfTimesEntered)")
325+
}
326+
}
327+
)
328+
329+
let responder = MutateRequestListenerResponder<MutationSync<AnyModel>> { request, eventListener in
330+
let requestOptions = GraphQLOperationRequest<MutationSync<AnyModel>>.Options(pluginOptions: nil)
331+
let request = GraphQLOperationRequest<MutationSync<AnyModel>>(apiName: request.apiName,
332+
operationType: .mutation,
333+
document: request.document,
334+
variables: request.variables,
335+
responseType: request.responseType,
336+
options: requestOptions)
337+
let operation = MockGraphQLOperation(request: request, responseType: request.responseType)
338+
339+
numberOfTimesEntered += 1
340+
341+
DispatchQueue.global().sync {
342+
// Fail with 401 status code
343+
eventListener!(.failure(error))
344+
}
345+
346+
return operation
347+
}
348+
349+
mockAPIPlugin.responders[.mutateRequestListener] = responder
350+
351+
let queue = OperationQueue()
352+
queue.addOperation(operation)
353+
354+
await fulfillment(of: [expectCalllToApiMutateNTimesAndFail], timeout: defaultAsyncWaitTimeout)
355+
}
356+
295357
func testGetRetryAdvice_OperationErrorAuthErrorWithMultiAuth_RetryTrue() async throws {
296358
let operation = await SyncMutationToCloudOperation(
297359
mutationEvent: try createMutationEvent(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
import AWSPluginsCore
10+
11+
class AWSAuthorizationTypeIteratorTests: XCTestCase {
12+
13+
func testEmptyIterator_hasNextValue_false() throws {
14+
var iterator = AWSAuthorizationTypeIterator(withValues: [])
15+
16+
XCTAssertFalse(iterator.hasNext)
17+
XCTAssertNil(iterator.next())
18+
}
19+
20+
func testOneElementIterator_hasNextValue_once() throws {
21+
var iterator = AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools])
22+
23+
XCTAssertTrue(iterator.hasNext)
24+
XCTAssertNotNil(iterator.next())
25+
26+
XCTAssertFalse(iterator.hasNext)
27+
}
28+
29+
func testTwoElementsIterator_hasNextValue_twice() throws {
30+
var iterator = AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools, .apiKey])
31+
32+
XCTAssertTrue(iterator.hasNext)
33+
XCTAssertNotNil(iterator.next())
34+
35+
XCTAssertTrue(iterator.hasNext)
36+
XCTAssertNotNil(iterator.next())
37+
38+
XCTAssertFalse(iterator.hasNext)
39+
}
40+
}

0 commit comments

Comments
 (0)