Skip to content

Commit

Permalink
fix(datastore): Continue initial sync if atleast one model from schem…
Browse files Browse the repository at this point in the history
…a succeeds
  • Loading branch information
thisisabhash committed Sep 15, 2023
1 parent 9a05bdd commit 45bddcd
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,16 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
allMessages.joined(separator: "\n"),
underlyingError
)
return .failure(syncError)

// send success if atleast one model succeeded
let syncableModelsCount = ModelRegistry.modelSchemas.filter { $0.isSyncable }.count
if syncableModelsCount == syncErrors.count {
return .failure(syncError)
} else {
self.log.verbose("\(#function) Atleast one model sync succeeded. Sending completion result as .success with error: \(syncError)")
return .successfulVoid
}

}

private func dispatchSyncQueriesStarted(for modelNames: [String]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,113 @@ class InitialSyncOrchestratorTests: XCTestCase {
sink.cancel()
}

/// - Given: An InitialSyncOrchestrator with a model dependency graph, API is expected to return an error for all models
/// - When:
/// - The orchestrator starts up
/// - Then:
/// - Finish with an error when all sync queries fail
func testFinishWithAPIErrorWhenAllSyncQueriesFail() async throws {
ModelRegistry.reset()
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
let responder = QueryRequestListenerResponder<PaginatedList<AnyModel>> { request, listener in
if request.document.contains("SyncPosts") {
let event: GraphQLOperation<PaginatedList<AnyModel>>.OperationResult =
.failure(APIError.operationError("", "", nil))
listener?(event)
} else if request.document.contains("SyncComments") {
let event: GraphQLOperation<PaginatedList<AnyModel>>.OperationResult =
.failure(APIError.operationError("", "", nil))
listener?(event)
}

return nil
}

let apiPlugin = MockAPICategoryPlugin()
apiPlugin.responders[.queryRequestListener] = responder

let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)

let reconciliationQueue = MockReconciliationQueue()

let orchestrator: AWSInitialSyncOrchestrator =
AWSInitialSyncOrchestrator(dataStoreConfiguration: .default,
authModeStrategy: AWSDefaultAuthModeStrategy(),
api: apiPlugin,
reconciliationQueue: reconciliationQueue,
storageAdapter: storageAdapter)

let syncCallbackReceived = expectation(description: "Sync callback received, sync operation is complete")
let syncQueriesStartedReceived = expectation(description: "syncQueriesStarted received")

let filter = HubFilters.forEventName(HubPayload.EventName.DataStore.syncQueriesStarted)
let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { payload in
guard let syncQueriesStartedEvent = payload.data as? SyncQueriesStartedEvent else {
XCTFail("Failed to cast payload data as SyncQueriesStartedEvent")
return
}
XCTAssertEqual(syncQueriesStartedEvent.models.count, 2)
syncQueriesStartedReceived.fulfill()
}

guard try await HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else {
XCTFail("Listener not registered for hub")
return
}

let syncStartedReceived = expectation(description: "Sync started received, sync operation started")
syncStartedReceived.expectedFulfillmentCount = 2
let finishedReceived = expectation(description: "InitialSyncOperation finished paginating and offering")
finishedReceived.expectedFulfillmentCount = 2
let failureCompletionReceived = expectation(description: "InitialSyncOrchestrator completed with failure")
let sink = orchestrator
.publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
XCTFail("Should have finished with failure")
case .failure:
failureCompletionReceived.fulfill()
}
}, receiveValue: { value in
switch value {
case .started:
syncStartedReceived.fulfill()
case .finished(let modelName, let error):
if modelName == "Post" {
guard case .api(let apiError, _) = error, case .operationError = apiError as? APIError else {
XCTFail("Should be api error")
return
}
} else if modelName == "Comment" {
guard case .api(let apiError, _) = error, case .operationError = apiError as? APIError else {
XCTFail("Should be api error")
return
}
}
finishedReceived.fulfill()
default:
break
}
})

orchestrator.sync { _ in
syncCallbackReceived.fulfill()
}

await waitForExpectations(timeout: 1)
XCTAssertEqual(orchestrator.syncOperationQueue.maxConcurrentOperationCount, 1)
Amplify.Hub.removeListener(hubListener)
sink.cancel()
}

/// - Given: An InitialSyncOrchestrator with a model dependency graph, API is expected to return an error for certain models
/// - When:
/// - The orchestrator starts up
/// - Then:
/// - Finish with an error for each sync query that fails.
func testFinishWithAPIError() async throws {
/// - Finish with a success when at least one sync query succeeds
func testFinishWithAPISuccessWhenAtlestOneSyncQuerySucceeds() async throws {
ModelRegistry.reset()
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
let responder = QueryRequestListenerResponder<PaginatedList<AnyModel>> { request, listener in
Expand Down Expand Up @@ -162,15 +263,15 @@ class InitialSyncOrchestratorTests: XCTestCase {
syncStartedReceived.expectedFulfillmentCount = 2
let finishedReceived = expectation(description: "InitialSyncOperation finished paginating and offering")
finishedReceived.expectedFulfillmentCount = 2
let failureCompletionReceived = expectation(description: "InitialSyncOrchestrator completed with failure")
let successCompletionReceived = expectation(description: "InitialSyncOrchestrator completed with success")
let sink = orchestrator
.publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
XCTFail("Should have finished with failure")
successCompletionReceived.fulfill()
case .failure:
failureCompletionReceived.fulfill()
XCTFail("Should have finished with success")
}
}, receiveValue: { value in
switch value {
Expand Down
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/aws-amplify/aws-appsync-realtime-client-ios.git",
"state" : {
"revision" : "c7ec93dcbbcd8abc90c74203937f207a7fcaa611",
"version" : "3.1.1"
"revision" : "b036e83716789c13a3480eeb292b70caa54114f2",
"version" : "3.1.0"
}
},
{
Expand Down Expand Up @@ -95,8 +95,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed",
"version" : "1.5.3"
"revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
"version" : "1.5.2"
}
},
{
Expand Down

0 comments on commit 45bddcd

Please sign in to comment.