From 4077e16c9133f5a9b2b5ee5e825a6c9865c9e0d4 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Wed, 29 Nov 2023 12:09:42 -0800 Subject: [PATCH] resolve comments --- .../DataStore/Subscribe/MutationEvent.swift | 2 - .../OutgoingMutationQueue.swift | 10 +- .../SyncMutationToCloudOperation.swift | 8 +- .../Support/MutationEvent+Extensions.swift | 104 ----- .../SyncMutationToCloudOperationTests.swift | 6 +- .../MutationEventExtensionsTests.swift | 404 ------------------ 6 files changed, 10 insertions(+), 524 deletions(-) delete mode 100644 AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Extensions.swift delete mode 100644 AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift diff --git a/Amplify/Categories/DataStore/Subscribe/MutationEvent.swift b/Amplify/Categories/DataStore/Subscribe/MutationEvent.swift index b14a346afc..988ba31994 100644 --- a/Amplify/Categories/DataStore/Subscribe/MutationEvent.swift +++ b/Amplify/Categories/DataStore/Subscribe/MutationEvent.swift @@ -17,8 +17,6 @@ public struct MutationEvent: Model { public var json: String public var mutationType: String public var createdAt: Temporal.DateTime - - @available(*, deprecated, message: "version field is deprecated") public var version: Int? public var inProcess: Bool public var graphQLFilterJSON: String? diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift index 9408b0825a..1bb8cd7b99 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift @@ -201,10 +201,9 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { } Task { - let latestSyncMetadata = try? storageAdapter.queryMutationSyncMetadata(for: mutationEvent.modelId, modelName: mutationEvent.modelName) let syncMutationToCloudOperation = await SyncMutationToCloudOperation( mutationEvent: mutationEvent, - latestSyncMetadata: latestSyncMetadata, + getLatestSyncMetadata: { try? self.storageAdapter.queryMutationSyncMetadata(for: mutationEvent.modelId, modelName: mutationEvent.modelName) }, api: api, authModeStrategy: authModeStrategy ) { [weak self] result in @@ -258,12 +257,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior { return } reconciliationQueue.offer([mutationSync], modelName: mutationEvent.modelName) - MutationEvent.reconcilePendingMutationEventsVersion( - sent: mutationEvent, - received: mutationSync, - storageAdapter: storageAdapter) { _ in - self.completeProcessingEvent(mutationEvent, mutationSync: mutationSync) - } + completeProcessingEvent(mutationEvent, mutationSync: mutationSync) } else { completeProcessingEvent(mutationEvent) } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift index c823b363fe..77434f51ef 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift @@ -19,7 +19,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation { private weak var api: APICategoryGraphQLBehaviorExtended? private let mutationEvent: MutationEvent - private let latestSyncMetadata: MutationSyncMetadata? + private let getLatestSyncMetadata: () -> MutationSyncMetadata? private let completion: GraphQLOperation>.ResultListener private let requestRetryablePolicy: RequestRetryablePolicy @@ -32,7 +32,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation { private var authTypesIterator: AWSAuthorizationTypeIterator? init(mutationEvent: MutationEvent, - latestSyncMetadata: MutationSyncMetadata?, + getLatestSyncMetadata: @escaping () -> MutationSyncMetadata?, api: APICategoryGraphQLBehaviorExtended, authModeStrategy: AuthModeStrategy, networkReachabilityPublisher: AnyPublisher? = nil, @@ -40,7 +40,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation { requestRetryablePolicy: RequestRetryablePolicy? = RequestRetryablePolicy(), completion: @escaping GraphQLOperation>.ResultListener) async { self.mutationEvent = mutationEvent - self.latestSyncMetadata = latestSyncMetadata + self.getLatestSyncMetadata = getLatestSyncMetadata self.api = api self.networkReachabilityPublisher = networkReachabilityPublisher self.completion = completion @@ -60,6 +60,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation { override func main() { log.verbose(#function) + sendMutationToCloud(withAuthType: authTypesIterator?.next()) } @@ -111,6 +112,7 @@ class SyncMutationToCloudOperation: AsynchronousOperation { mutationType: GraphQLMutationType, authType: AWSAuthorizationType? = nil ) -> GraphQLRequest>? { + let latestSyncMetadata = getLatestSyncMetadata() var request: GraphQLRequest> do { diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Extensions.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Extensions.swift deleted file mode 100644 index 7d2057528e..0000000000 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Extensions.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Amplify -import Dispatch -import AWSPluginsCore - -extension MutationEvent { - // Consecutive operations that modify a model results in a sequence of pending mutation events that - // have the current version of the model. The first mutation event has the correct version of the model, - // while the subsequent events will have lower versions if the first mutation event is successfully synced - // to the cloud. By reconciling the pending mutation events after syncing the first mutation event, - // we attempt to update the pending version to the latest version from the response. - // The before and after conditions for consecutive update scenarios are as below: - // - Save, then immediately update - // Queue Before - [(version: nil, inprocess: true, type: .create), - // (version: nil, inprocess: false, type: .update)] - // Response - [version: 1, type: .create] - // Queue After - [(version: 1, inprocess: false, type: .update)] - // - Save, then immediately delete - // Queue Before - [(version: nil, inprocess: true, type: .create), - // (version: nil, inprocess: false, type: .delete)] - // Response - [version: 1, type: .create] - // Queue After - [(version: 1, inprocess: false, type: .delete)] - // - Save, sync, then immediately update and delete - // Queue Before (After save, sync) - // - [(version: 1, inprocess: true, type: .update), (version: 1, inprocess: false, type: .delete)] - // Response - [version: 2, type: .update] - // Queue After - [(version: 2, inprocess: false, type: .delete)] - // - // For a given model `id`, checks the version of the head of pending mutation event queue - // against the API response version in `mutationSync` and saves it in the mutation event table if - // the response version is a newer one - static func reconcilePendingMutationEventsVersion(sent mutationEvent: MutationEvent, - received mutationSync: MutationSync, - storageAdapter: StorageEngineAdapter, - completion: @escaping DataStoreCallback) { - MutationEvent.pendingMutationEvents( - forMutationEvent: mutationEvent, - storageAdapter: storageAdapter - ) { queryResult in - switch queryResult { - case .failure(let dataStoreError): - completion(.failure(dataStoreError)) - case .success(let localMutationEvents): - guard let existingEvent = localMutationEvents.first else { - completion(.success(())) - return - } - - guard let reconciledEvent = reconcile(pendingMutationEvent: existingEvent, - with: mutationEvent, - responseMutationSync: mutationSync) else { - completion(.success(())) - return - } - - storageAdapter.save(reconciledEvent, condition: nil, eagerLoad: true) { result in - switch result { - case .failure(let dataStoreError): - completion(.failure(dataStoreError)) - case .success: - completion(.success(())) - } - } - } - } - } - - static func reconcile(pendingMutationEvent: MutationEvent, - with requestMutationEvent: MutationEvent, - responseMutationSync: MutationSync) -> MutationEvent? { - // return if version of the pending mutation event is not nil and - // is >= version contained in the response - if pendingMutationEvent.version != nil && - pendingMutationEvent.version! >= responseMutationSync.syncMetadata.version { - return nil - } - - do { - let responseModel = responseMutationSync.model.instance - let requestModel = try requestMutationEvent.decodeModel() - - // check if the data sent in the request is the same as the response - // if it is, update the pending mutation event version to the response version - guard let modelSchema = ModelRegistry.modelSchema(from: requestMutationEvent.modelName), - modelSchema.compare(responseModel, requestModel) else { - return nil - } - - var pendingMutationEvent = pendingMutationEvent - pendingMutationEvent.version = responseMutationSync.syncMetadata.version - return pendingMutationEvent - } catch { - Amplify.log.verbose("Error decoding models: \(error)") - return nil - } - } - -} diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift index 60013f795b..7b66f29c7d 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift @@ -69,7 +69,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { let operation = await SyncMutationToCloudOperation( mutationEvent: mutationEvent, - latestSyncMetadata: nil, + getLatestSyncMetadata: { nil }, api: mockAPIPlugin, authModeStrategy: AWSDefaultAuthModeStrategy(), networkReachabilityPublisher: publisher, @@ -144,7 +144,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { } let operation = await SyncMutationToCloudOperation( mutationEvent: mutationEvent, - latestSyncMetadata: nil, + getLatestSyncMetadata: { nil }, api: mockAPIPlugin, authModeStrategy: AWSDefaultAuthModeStrategy(), networkReachabilityPublisher: publisher, @@ -220,7 +220,7 @@ class SyncMutationToCloudOperationTests: XCTestCase { } let operation = await SyncMutationToCloudOperation( mutationEvent: mutationEvent, - latestSyncMetadata: nil, + getLatestSyncMetadata: { nil }, api: mockAPIPlugin, authModeStrategy: AWSDefaultAuthModeStrategy(), networkReachabilityPublisher: publisher, diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift deleted file mode 100644 index e709ebe364..0000000000 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift +++ /dev/null @@ -1,404 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import Foundation -import SQLite -import XCTest - -@testable import Amplify -@testable import AmplifyTestCommon -@testable import AWSDataStorePlugin -@testable import AWSPluginsCore - -// TODO: This flaky test has been disabled, tracking issue: https://github.com/aws-amplify/amplify-ios/issues/1831 -// swiftlint:disable type_body_length -class MutationEventExtensionsTest: BaseDataStoreTests { - - /// - Given: A pending mutation events queue with event containing `nil` version, a sent mutation - /// event model that matches the received mutation sync model. The received mutation sync has version 1. - /// - When: The sent model matches the received model and the first pending mutation event version is `nil`. - /// - Then: The pending mutation event version should be updated to the received model version of 1. - func testSentModelWithNilVersion_Reconciled() throws { - throw XCTSkip("TODO: fix this test") - let modelId = UUID().uuidString - let post = Post(id: modelId, title: "title", content: "content", createdAt: .now()) - let requestMutationEvent = try createMutationEvent(model: post, - mutationType: .create, - createdAt: .now(), - version: nil, - inProcess: true) - let pendingMutationEvent = try createMutationEvent(model: post, - mutationType: .update, - createdAt: .now().add(value: 1, to: .second), - version: nil) - let responseMutationSync = createMutationSync(model: post, version: 1) - - setUpPendingMutationQueue(post, [requestMutationEvent, pendingMutationEvent], pendingMutationEvent) - - let reconciledEvent = MutationEvent.reconcile(pendingMutationEvent: pendingMutationEvent, - with: requestMutationEvent, - responseMutationSync: responseMutationSync) - XCTAssertNotNil(reconciledEvent) - XCTAssertEqual(reconciledEvent?.version, responseMutationSync.syncMetadata.version) - - let queryAfterUpdatingVersionExpectation = expectation(description: "update mutation should be latest version") - let updatingVersionExpectation = expectation(description: "update latest mutation event with response version") - - // update the version of head of mutation event table for given model id to the version of `mutationSync` - MutationEvent.reconcilePendingMutationEventsVersion(sent: requestMutationEvent, - received: responseMutationSync, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success: - updatingVersionExpectation.fulfill() - } - } - wait(for: [updatingVersionExpectation], timeout: 1) - - // query for head of mutation event table for given model id and check if it has the updated version - MutationEvent.pendingMutationEvents(forModel: post, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let mutationEvents): - guard !mutationEvents.isEmpty, let head = mutationEvents.first else { - XCTFail("Failure while updating version") - return - } - XCTAssertEqual(head.version, responseMutationSync.syncMetadata.version) - XCTAssertEqual(head.mutationType, MutationEvent.MutationType.update.rawValue) - queryAfterUpdatingVersionExpectation.fulfill() - } - } - wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1) - } - - /// - Given: A pending mutation events queue with two events(update and delete) containing `nil` version, - /// a sent mutation event model that matches the received mutation sync model. The received mutation - /// sync has version 1. - /// - When: The sent model matches the received model, the first pending mutation event(update) version is `nil` and - /// the second pending mutation event(delete) version is `nil`. - /// - Then: The first pending mutation event(update) version should be updated to the received model version of 1 - /// and the second pending mutation event version(delete) should not be updated. - func testSentModelWithNilVersion_SecondPendingEventNotReconciled() throws { - throw XCTSkip("TODO: fix this test") - let modelId = UUID().uuidString - let post = Post(id: modelId, title: "title", content: "content", createdAt: .now()) - let requestMutationEvent = try createMutationEvent(model: post, - mutationType: .create, - createdAt: .now(), - version: nil, - inProcess: true) - let pendingUpdateMutationEvent = try createMutationEvent(model: post, - mutationType: .update, - createdAt: .now().add(value: 1, to: .second), - version: nil) - let pendingDeleteMutationEvent = try createMutationEvent(model: post, - mutationType: .delete, - createdAt: .now().add(value: 2, to: .second), - version: nil) - let responseMutationSync = createMutationSync(model: post, version: 1) - - setUpPendingMutationQueue(post, - [requestMutationEvent, pendingUpdateMutationEvent, pendingDeleteMutationEvent], - pendingUpdateMutationEvent) - - let reconciledEvent = MutationEvent.reconcile(pendingMutationEvent: pendingUpdateMutationEvent, - with: requestMutationEvent, - responseMutationSync: responseMutationSync) - XCTAssertNotNil(reconciledEvent) - XCTAssertEqual(reconciledEvent?.version, responseMutationSync.syncMetadata.version) - - let queryAfterUpdatingVersionExpectation = expectation(description: "update mutation should be latest version") - let updatingVersionExpectation = expectation(description: "update latest mutation event with response version") - - // update the version of head of mutation event table for given model id to the version of `mutationSync` - MutationEvent.reconcilePendingMutationEventsVersion(sent: requestMutationEvent, - received: responseMutationSync, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success: - updatingVersionExpectation.fulfill() - } - } - wait(for: [updatingVersionExpectation], timeout: 1) - - // query for head of mutation event table for given model id and check if it has the updated version - MutationEvent.pendingMutationEvents(forModel: post, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let mutationEvents): - guard !mutationEvents.isEmpty, let head = mutationEvents.first, let last = mutationEvents.last else { - XCTFail("Failure while updating version") - return - } - XCTAssertEqual(head.version, responseMutationSync.syncMetadata.version) - XCTAssertEqual(head.mutationType, MutationEvent.MutationType.update.rawValue) - XCTAssertEqual(last, pendingDeleteMutationEvent) - queryAfterUpdatingVersionExpectation.fulfill() - } - } - wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1) - } - - /// - Given: A pending mutation events queue with event containing version 2, a sent mutation event model - /// that matches the received mutation sync model having version 2. The received mutation sync has - /// version 1. - /// - When: The sent model matches the received model and the first pending mutation event version is 2. - /// - Then: The first pending mutation event version should NOT be updated. - func testSentModelVersionNewerThanResponseVersion_PendingEventNotReconciled() throws { - throw XCTSkip("TODO: fix this test") - let modelId = UUID().uuidString - let post1 = Post(id: modelId, title: "title1", content: "content1", createdAt: .now()) - let post2 = Post(id: modelId, title: "title2", content: "content2", createdAt: .now()) - let requestMutationEvent = try createMutationEvent(model: post1, - mutationType: .create, - createdAt: .now(), - version: 2, - inProcess: true) - let pendingMutationEvent = try createMutationEvent(model: post2, - mutationType: .update, - createdAt: .now().add(value: 1, to: .second), - version: 2) - let responseMutationSync = createMutationSync(model: post1, version: 1) - - setUpPendingMutationQueue(post1, [requestMutationEvent, pendingMutationEvent], pendingMutationEvent) - - let reconciledEvent = MutationEvent.reconcile(pendingMutationEvent: pendingMutationEvent, - with: requestMutationEvent, - responseMutationSync: responseMutationSync) - XCTAssertNil(reconciledEvent) - - let queryAfterUpdatingVersionExpectation = expectation(description: "update mutation should have version 2") - let updatingVersionExpectation = - expectation(description: "don't update latest mutation event with response version") - - MutationEvent.reconcilePendingMutationEventsVersion(sent: requestMutationEvent, - received: responseMutationSync, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success: - updatingVersionExpectation.fulfill() - } - } - wait(for: [updatingVersionExpectation], timeout: 1) - - // query for head of mutation event table for given model id and check if it has the correct version - MutationEvent.pendingMutationEvents(forModel: post1, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let mutationEvents): - guard !mutationEvents.isEmpty, let head = mutationEvents.first else { - XCTFail("Failure while updating version") - return - } - XCTAssertNotEqual(head.version, responseMutationSync.syncMetadata.version) - XCTAssertEqual(head, pendingMutationEvent) - queryAfterUpdatingVersionExpectation.fulfill() - } - } - wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1) - } - - /// - Given: A pending mutation events queue with event containing version 1, a sent mutation event model - /// that doesn't match the received mutation sync model having version 1. The received mutation - /// sync has version 2. - /// - When: The sent model doesn't match the received model and the first pending mutation event version is 1. - /// - Then: The first pending mutation event version should NOT be updated. - func testSentModelNotEqualToResponseModel_PendingEventNotReconciled() throws { - throw XCTSkip("TODO: fix this test") - let modelId = UUID().uuidString - let post1 = Post(id: modelId, title: "title1", content: "content1", createdAt: .now()) - let post2 = Post(id: modelId, title: "title2", content: "content2", createdAt: .now()) - let post3 = Post(id: modelId, title: "title3", content: "content3", createdAt: .now()) - let requestMutationEvent = try createMutationEvent(model: post1, - mutationType: .update, - createdAt: .now(), - version: 1, - inProcess: true) - let pendingMutationEvent = try createMutationEvent(model: post2, - mutationType: .update, - createdAt: .now().add(value: 1, to: .second), - version: 1) - let responseMutationSync = createMutationSync(model: post3, version: 2) - - setUpPendingMutationQueue(post1, [requestMutationEvent, pendingMutationEvent], pendingMutationEvent) - - let reconciledEvent = MutationEvent.reconcile(pendingMutationEvent: pendingMutationEvent, - with: requestMutationEvent, - responseMutationSync: responseMutationSync) - XCTAssertNil(reconciledEvent) - - let queryAfterUpdatingVersionExpectation = expectation(description: "update mutation should have version 1") - let updatingVersionExpectation = - expectation(description: "don't update latest mutation event with response version") - - MutationEvent.reconcilePendingMutationEventsVersion(sent: requestMutationEvent, - received: responseMutationSync, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success: - updatingVersionExpectation.fulfill() - } - } - wait(for: [updatingVersionExpectation], timeout: 1) - - // query for head of mutation event table for given model id and check if it has the correct version - MutationEvent.pendingMutationEvents(forModel: post1, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let mutationEvents): - guard !mutationEvents.isEmpty, let head = mutationEvents.first else { - XCTFail("Failure while updating version") - return - } - XCTAssertNotEqual(head.version, responseMutationSync.syncMetadata.version) - XCTAssertEqual(head, pendingMutationEvent) - queryAfterUpdatingVersionExpectation.fulfill() - } - } - wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1) - } - - /// - Given: A pending mutation events queue with event containing version 1, a sent mutation event model - /// that matches the received mutation sync model having version 1. The received mutation sync - /// has version 2. - /// - When: The sent model matches the received model and the first pending mutation event version is 1. - /// - Then: The first pending mutation event version should be updated to received mutation sync version i.e. 2. - func testPendingVersionReconciledSuccess() throws { - throw XCTSkip("TODO: fix this test") - let modelId = UUID().uuidString - let post1 = Post(id: modelId, title: "title1", content: "content1", createdAt: .now()) - let post2 = Post(id: modelId, title: "title2", content: "content2", createdAt: .now()) - let requestMutationEvent = try createMutationEvent(model: post1, - mutationType: .update, - createdAt: .now(), - version: 1, - inProcess: true) - let pendingMutationEvent = try createMutationEvent(model: post2, - mutationType: .update, - createdAt: .now().add(value: 1, to: .second), - version: 1) - let responseMutationSync = createMutationSync(model: post1, version: 2) - - setUpPendingMutationQueue(post1, [requestMutationEvent, pendingMutationEvent], pendingMutationEvent) - - let reconciledEvent = MutationEvent.reconcile(pendingMutationEvent: pendingMutationEvent, - with: requestMutationEvent, - responseMutationSync: responseMutationSync) - XCTAssertNotNil(reconciledEvent) - XCTAssertEqual(reconciledEvent?.version, responseMutationSync.syncMetadata.version) - - let queryAfterUpdatingVersionExpectation = expectation(description: "update mutation should have version 2") - let updatingVersionExpectation = expectation(description: "update latest mutation event with response version") - - MutationEvent.reconcilePendingMutationEventsVersion(sent: requestMutationEvent, - received: responseMutationSync, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success: - updatingVersionExpectation.fulfill() - } - } - wait(for: [updatingVersionExpectation], timeout: 1) - - // query for head of mutation event table for given model id and check if it has the correct version - MutationEvent.pendingMutationEvents(forModel: post1, - storageAdapter: storageAdapter) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let mutationEvents): - guard !mutationEvents.isEmpty, let head = mutationEvents.first else { - XCTFail("Failure while updating version") - return - } - XCTAssertEqual(head.version, responseMutationSync.syncMetadata.version) - XCTAssertEqual(head.mutationType, MutationEvent.MutationType.update.rawValue) - queryAfterUpdatingVersionExpectation.fulfill() - } - } - wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1) - } - - private func createMutationEvent(model: Model, - mutationType: MutationEvent.MutationType, - createdAt: Temporal.DateTime, - version: Int? = nil, - inProcess: Bool = false) throws -> MutationEvent { - return MutationEvent(id: UUID().uuidString, - modelId: model.identifier(schema: MutationEvent.schema).stringValue, - modelName: model.modelName, - json: try model.toJSON(), - mutationType: mutationType, - createdAt: createdAt, - version: version, - inProcess: inProcess) - } - - private func createMutationSync(model: Model, version: Int = 1) -> MutationSync { - let metadata = MutationSyncMetadata(modelId: model.identifier(schema: MutationEvent.schema).stringValue, - modelName: model.modelName, - deleted: false, - lastChangedAt: Int(Date().timeIntervalSince1970), - version: version) - return MutationSync(model: AnyModel(model), syncMetadata: metadata) - } - - private func setUpPendingMutationQueue(_ model: Model, - _ mutationEvents: [MutationEvent], - _ expectedHeadOfQueue: MutationEvent) { - for mutationEvent in mutationEvents { - let mutationEventSaveExpectation = expectation(description: "save mutation event success") - storageAdapter.save(mutationEvent) { result in - guard case .success = result else { - XCTFail("Failed to save metadata") - return - } - mutationEventSaveExpectation.fulfill() - } - wait(for: [mutationEventSaveExpectation], timeout: 1) - } - - // verify the head of queue is expected - let headOfQueueExpectation = expectation(description: "head of mutation event queue is as expected") - MutationEvent.pendingMutationEvents( - forModel: model, - storageAdapter: storageAdapter - ) { result in - switch result { - case .failure(let error): - XCTFail("Error : \(error)") - case .success(let events): - guard !events.isEmpty, let head = events.first else { - XCTFail("Failure while fetching mutation events") - return - } - XCTAssertEqual(head, expectedHeadOfQueue) - headOfQueueExpectation.fulfill() - } - } - wait(for: [headOfQueueExpectation], timeout: 1) - } -}