diff --git a/Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift b/Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift index 8ce8aa7479..71efda83ed 100644 --- a/Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift +++ b/Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift @@ -38,6 +38,14 @@ extension DataStoreCategory: DataStoreBaseBehavior { plugin.delete(modelType, withId: id, completion: completion) } + public func start(completion: @escaping DataStoreCallback) { + plugin.start(completion: completion) + } + + public func stop(completion: @escaping DataStoreCallback) { + plugin.stop(completion: completion) + } + public func clear(completion: @escaping DataStoreCallback) { plugin.clear(completion: completion) } diff --git a/Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift b/Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift index ac4a2bc1db..37cf5b4441 100644 --- a/Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift +++ b/Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift @@ -34,6 +34,10 @@ public protocol DataStoreBaseBehavior { withId id: String, completion: @escaping DataStoreCallback) + func start(completion: @escaping DataStoreCallback) + + func stop(completion: @escaping DataStoreCallback) + func clear(completion: @escaping DataStoreCallback) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin+DataStoreBaseBehavior.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin+DataStoreBaseBehavior.swift index 6692393c49..982e27cc2c 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin+DataStoreBaseBehavior.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin+DataStoreBaseBehavior.swift @@ -168,6 +168,24 @@ extension AWSDataStorePlugin: DataStoreBaseBehavior { predicate: predicate, completion: onCompletion) } + public func start(completion: @escaping DataStoreCallback) { + reinitStorageEngineIfNeeded(completion: completion) + } + + public func stop(completion: @escaping DataStoreCallback) { + if storageEngine == nil { + completion(.successfulVoid) + return + } + storageEngine.stopSync { result in + self.storageEngine = nil + if #available(iOS 13.0, *) { + self.dataStorePublisher?.sendFinished() + } + self.dataStorePublisher = nil + completion(result) + } + } public func clear(completion: @escaping DataStoreCallback) { if storageEngine == nil { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift index 88a48fec79..ce1a558356 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin.swift @@ -29,6 +29,7 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { /// The local storage provider. Resolved during configuration phase var storageEngine: StorageEngineBehavior! + var storageEngineBehaviorFactory: StorageEngineBehaviorFactory var iStorageEngineSink: Any? @available(iOS 13.0, *) @@ -52,7 +53,8 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { self.isSyncEnabled = false self.validAPIPluginKey = "awsAPIPlugin" self.validAuthPluginKey = "awsCognitoAuthPlugin" - + self.storageEngineBehaviorFactory = + StorageEngine.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:) if #available(iOS 13.0, *) { self.dataStorePublisher = DataStorePublisher() } else { @@ -64,6 +66,7 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { init(modelRegistration: AmplifyModelRegistration, configuration dataStoreConfiguration: DataStoreConfiguration = .default, storageEngine: StorageEngineBehavior, + storageEngineBehaviorFactory: StorageEngineBehaviorFactory? = nil, dataStorePublisher: ModelSubcriptionBehavior, validAPIPluginKey: String, validAuthPluginKey: String) { @@ -71,6 +74,8 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { self.dataStoreConfiguration = dataStoreConfiguration self.isSyncEnabled = false self.storageEngine = storageEngine + self.storageEngineBehaviorFactory = storageEngineBehaviorFactory ?? + StorageEngine.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:) self.dataStorePublisher = dataStorePublisher self.validAPIPluginKey = validAPIPluginKey self.validAuthPluginKey = validAuthPluginKey @@ -84,45 +89,46 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { resolveSyncEnabled() try resolveStorageEngine(dataStoreConfiguration: dataStoreConfiguration) - try storageEngine.setUp(modelSchemas: ModelRegistry.modelSchemas) + } - let filter = HubFilters.forEventName(HubPayload.EventName.Amplify.configured) - var token: UnsubscribeToken? - token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in - self.storageEngine.startSync() - if let token = token { - Amplify.Hub.removeListener(token) - } + func reinitStorageEngineIfNeeded(completion: @escaping DataStoreCallback = {_ in}) { + if storageEngine == nil { + reinitStorageEngine(completion: completion) + } else { + startSyncEngine(completion: completion) } } - func reinitStorageEngineIfNeeded() { - if storageEngine != nil { - return - } + func reinitStorageEngine(completion: @escaping DataStoreCallback) { do { if #available(iOS 13.0, *) { self.dataStorePublisher = DataStorePublisher() } try resolveStorageEngine(dataStoreConfiguration: dataStoreConfiguration) try storageEngine.setUp(modelSchemas: ModelRegistry.modelSchemas) - storageEngine.startSync() + startSyncEngine(completion: completion) } catch { log.error(error: error) } } + func startSyncEngine(completion: @escaping DataStoreCallback) { + storageEngine.startSync(completion: completion) + } + func resolveStorageEngine(dataStoreConfiguration: DataStoreConfiguration) throws { guard storageEngine == nil else { return } - storageEngine = try StorageEngine(isSyncEnabled: isSyncEnabled, - dataStoreConfiguration: dataStoreConfiguration, - validAPIPluginKey: validAPIPluginKey, - validAuthPluginKey: validAuthPluginKey, - modelRegistryVersion: modelRegistration.version) + storageEngine = try storageEngineBehaviorFactory(isSyncEnabled, + dataStoreConfiguration, + validAPIPluginKey, + validAuthPluginKey, + modelRegistration.version, + UserDefaults.standard) + if #available(iOS 13.0, *) { setupStorageSink() } @@ -184,4 +190,3 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin { } } - diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine+SyncRequirement.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine+SyncRequirement.swift index 80a48341e8..9eaaa40cd6 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine+SyncRequirement.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine+SyncRequirement.swift @@ -12,25 +12,38 @@ import AWSPluginsCore extension StorageEngine { - func startSync() { - guard let api = tryGetAPIPlugin() else { - log.info("Unable to find suitable API plugin for syncEngine. syncEngine will not be started") - return - } + func startSync(completion: @escaping DataStoreCallback) { + syncEngineStartSerialQueue.async { + guard let api = self.tryGetAPIPlugin() else { + self.log.info("Unable to find suitable API plugin for syncEngine. syncEngine will not be started") + completion(.failure(.configuration("Unable to find suitable API plugin for syncEngine. syncEngine will not be started", + "Ensure the API category has been setup and configured for your project", nil))) + return + } - let authPluginRequired = requiresAuthPlugin(api: api) + let authPluginRequired = self.requiresAuthPlugin(api: api) - guard authPluginRequired else { - syncEngine?.start(api: api, auth: nil) - return - } + guard authPluginRequired else { + if !self.syncEngineCalledStart { + self.syncEngineCalledStart = true + self.syncEngine?.start(api: api, auth: nil) + } + completion(.successfulVoid) + return + } - guard let auth = tryGetAuthPlugin() else { - log.warn("Unable to find suitable Auth plugin for syncEngine. Models require auth") - return + guard let auth = self.tryGetAuthPlugin() else { + self.log.warn("Unable to find suitable Auth plugin for syncEngine. Models require auth") + completion(.failure(.configuration("Unable to find suitable Auth plugin for syncEngine. Models require auth", + "Ensure the Auth category has been setup and configured for your project", nil))) + return + } + if !self.syncEngineCalledStart { + self.syncEngineCalledStart = true + self.syncEngine?.start(api: api, auth: auth) + } + completion(.successfulVoid) } - - syncEngine?.start(api: api, auth: auth) } private func tryGetAPIPlugin() -> APICategoryGraphQLBehavior? { diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift index a17da94074..32759baee6 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift @@ -10,6 +10,14 @@ import Combine import Foundation import AWSPluginsCore +typealias StorageEngineBehaviorFactory = + (Bool, + DataStoreConfiguration, + String, + String, + String, + UserDefaults) throws -> StorageEngineBehavior + // swiftlint:disable type_body_length final class StorageEngine: StorageEngineBehavior { @@ -17,6 +25,8 @@ final class StorageEngine: StorageEngineBehavior { let storageAdapter: StorageEngineAdapter private let dataStoreConfiguration: DataStoreConfiguration var syncEngine: RemoteSyncEngineBehavior? + var syncEngineCalledStart: Bool + let syncEngineStartSerialQueue: DispatchQueue let validAPIPluginKey: String let validAuthPluginKey: String var signInListener: UnsubscribeToken? @@ -75,6 +85,8 @@ final class StorageEngine: StorageEngineBehavior { self.syncEngine = syncEngine self.validAPIPluginKey = validAPIPluginKey self.validAuthPluginKey = validAuthPluginKey + self.syncEngineCalledStart = false + self.syncEngineStartSerialQueue = DispatchQueue(label: "com.amazonaws.DataStore.StorageEngineStartSerialQueue") } convenience init(isSyncEnabled: Bool, @@ -386,6 +398,16 @@ final class StorageEngine: StorageEngineBehavior { } } + func stopSync(completion: @escaping DataStoreCallback) { + if let syncEngine = syncEngine { + syncEngine.stop { _ in + completion(.successfulVoid) + } + } else { + completion(.successfulVoid) + } + } + func reset(onComplete: () -> Void) { // TOOD: Perform cleanup on StorageAdapter, including releasing its `Connection` if needed let group = DispatchGroup() diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngineBehavior.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngineBehavior.swift index b481b70064..80d9ea76d1 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngineBehavior.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngineBehavior.swift @@ -20,7 +20,7 @@ protocol StorageEngineBehavior: class, ModelStorageBehavior { var publisher: AnyPublisher { get } /// start remote sync, based on if sync is enabled and/or authentication is required - func startSync() - + func startSync(completion: @escaping DataStoreCallback) + func stopSync(completion: @escaping DataStoreCallback) func clear(completion: @escaping DataStoreCallback) } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Core/AWSAPICategoryPluginTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Core/AWSAPICategoryPluginTests.swift new file mode 100644 index 0000000000..e3eb76c110 --- /dev/null +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Core/AWSAPICategoryPluginTests.swift @@ -0,0 +1,264 @@ +// +// Copyright 2018-2020 Amazon.com, +// Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest + +@testable import Amplify +@testable import AWSDataStoreCategoryPlugin + +class AWSAPICategoryPluginTests: XCTestCase { + func testStorageEngineDoesNotStartsOnConfigure() throws { + let startExpectation = expectation(description: "Start Sync should not be called") + startExpectation.isInverted = true + let storageEngine = MockStorageEngineBehavior() + storageEngine.responders[.startSync] = StartSyncResponder { _ in + startExpectation.fulfill() + } + let dataStorePublisher = DataStorePublisher() + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func testStorageEngineStartsOnPluginStart() throws { + let startExpectation = expectation(description: "Start Sync should be called") + let storageEngine = MockStorageEngineBehavior() + storageEngine.responders[.startSync] = StartSyncResponder { _ in + startExpectation.fulfill() + } + let dataStorePublisher = DataStorePublisher() + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + plugin.start(completion: {_ in}) + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func testStorageEngineStartsOnQuery() throws { + let startExpectation = expectation(description: "Start Sync should be called with Query") + let storageEngine = MockStorageEngineBehavior() + storageEngine.responders[.startSync] = StartSyncResponder { _ in + startExpectation.fulfill() + } + let dataStorePublisher = DataStorePublisher() + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + plugin.query(ExampleWithEveryType.self) + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func testStorageEngineStartStopStart() throws { + let startExpectation = expectation(description: "Start Sync should be called with start") + let stopExpectation = expectation(description: "stop should be called") + let initExpectation = expectation(description: "init should be called") + let startExpectationOnSecondStart = expectation(description: "Start Sync should be called again") + let storageEngine = MockStorageEngineBehavior() + var count = 0 + storageEngine.responders[.startSync] = StartSyncResponder { _ in + count = self.expect(startExpectation, count, 1) + } + storageEngine.responders[.stopSync] = StopSyncResponder { _ in + count = self.expect(stopExpectation, count, 2) + } + MockStorageEngineBehavior.staticResponders[.initConstructor] = InitResponder { newlyConstructedStorageEngine in + count = self.expect(initExpectation, count, 3) + newlyConstructedStorageEngine.responders[.startSync] = StartSyncResponder {_ in + count = self.expect(startExpectationOnSecondStart, count, 4) + } + } + + let dataStorePublisher = DataStorePublisher() + let storageEngineBehaviorFactory = + MockStorageEngineBehavior.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:) + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + storageEngineBehaviorFactory: storageEngineBehaviorFactory, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + + let semaphore = DispatchSemaphore(value: 0) + plugin.start(completion: {_ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.stop(completion: { _ in + XCTAssert(plugin.storageEngine == nil) + XCTAssert(plugin.dataStorePublisher == nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.start(completion: { _ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + }) + + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func testStorageEngineStartClearStart() throws { + let startExpectation = expectation(description: "Start Sync should be called with start") + let clearExpectation = expectation(description: "Clear should be called") + let initExpectation = expectation(description: "init should be called") + let startExpectationOnSecondStart = expectation(description: "Start Sync should be called again") + let storageEngine = MockStorageEngineBehavior() + var count = 0 + storageEngine.responders[.startSync] = StartSyncResponder { _ in + count = self.expect(startExpectation, count, 1) + } + storageEngine.responders[.clear] = ClearResponder { _ in + count = self.expect(clearExpectation, count, 2) + } + MockStorageEngineBehavior.staticResponders[.initConstructor] = InitResponder { newlyConstructedStorageEngine in + count = self.expect(initExpectation, count, 3) + newlyConstructedStorageEngine.responders[.startSync] = StartSyncResponder {_ in + count = self.expect(startExpectationOnSecondStart, count, 4) + } + } + + let dataStorePublisher = DataStorePublisher() + let storageEngineBehaviorFactory = + MockStorageEngineBehavior.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:) + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + storageEngineBehaviorFactory: storageEngineBehaviorFactory, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + + let semaphore = DispatchSemaphore(value: 0) + plugin.start(completion: {_ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.clear(completion: { _ in + XCTAssert(plugin.storageEngine == nil) + XCTAssert(plugin.dataStorePublisher == nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.start(completion: { _ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + }) + + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func testStorageEngineQueryClearQuery() throws { + let startExpectation = expectation(description: "Start Sync should be called with Query") + let clearExpectation = expectation(description: "Clear should be called") + let initExpectation = expectation(description: "init should be called") + let startExpectationOnQuery = expectation(description: "Start Sync should be called again with Query") + let storageEngine = MockStorageEngineBehavior() + var count = 0 + storageEngine.responders[.query] = QueryResponder { _ in + count = self.expect(startExpectation, count, 1) + } + storageEngine.responders[.clear] = ClearResponder { _ in + count = self.expect(clearExpectation, count, 2) + } + MockStorageEngineBehavior.staticResponders[.initConstructor] = InitResponder { newlyConstructedStorageEngine in + count = self.expect(initExpectation, count, 3) + newlyConstructedStorageEngine.responders[.query] = QueryResponder {_ in + count = self.expect(startExpectationOnQuery, count, 4) + } + } + + let dataStorePublisher = DataStorePublisher() + let storageEngineBehaviorFactory = + MockStorageEngineBehavior.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:) + let plugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), + storageEngine: storageEngine, + storageEngineBehaviorFactory: storageEngineBehaviorFactory, + dataStorePublisher: dataStorePublisher, + validAPIPluginKey: "MockAPICategoryPlugin", + validAuthPluginKey: "MockAuthCategoryPlugin") + do { + try plugin.configure(using: nil) + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + + let semaphore = DispatchSemaphore(value: 0) + plugin.query(ExampleWithEveryType.self, completion: {_ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.clear(completion: { _ in + XCTAssert(plugin.storageEngine == nil) + XCTAssert(plugin.dataStorePublisher == nil) + semaphore.signal() + }) + semaphore.wait() + + plugin.query(ExampleWithEveryType.self, completion: { _ in + XCTAssert(plugin.storageEngine != nil) + XCTAssert(plugin.dataStorePublisher != nil) + }) + + } catch { + XCTFail("DataStore configuration should not fail with nil configuration. \(error)") + } + waitForExpectations(timeout: 1.0) + } + + func expect(_ expectation: XCTestExpectation, _ currCount: Int, _ expectedCount: Int) -> Int { + let count = currCount + 1 + if count == expectedCount { + expectation.fulfill() + } + return count + } +} diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift index f0686bb282..ad36b50754 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/RemoteSync/RemoteSyncAPIInvocationTests.swift @@ -104,7 +104,7 @@ class RemoteSyncAPIInvocationTests: XCTestCase { } try Amplify.configure(amplifyConfig) - + Amplify.DataStore.start(completion: {_ in}) waitForExpectations(timeout: 1.0) } // TODO: Implement the test below diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift index f6ca40ca2a..ca724e58f1 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift @@ -206,6 +206,25 @@ class MockSQLiteStorageEngineAdapter: StorageEngineAdapter { } class MockStorageEngineBehavior: StorageEngineBehavior { + var responders = [ResponderKeys: Any]() + static var staticResponders = [ResponderKeys: Any]() + + init() { + if let responder = MockStorageEngineBehavior.staticResponders[.initConstructor] as? InitResponder { + responder.callback(self) + } + } + + init(isSyncEnabled: Bool, + dataStoreConfiguration: DataStoreConfiguration, + validAPIPluginKey: String = "awsAPIPlugin", + validAuthPluginKey: String = "awsCognitoAuthPlugin", + modelRegistryVersion: String, + userDefault: UserDefaults = UserDefaults.standard) throws { + if let responder = MockStorageEngineBehavior.staticResponders[.initConstructor] as? InitResponder { + responder.callback(self) + } + } func setupPublisher() { @@ -215,7 +234,18 @@ class MockStorageEngineBehavior: StorageEngineBehavior { return PassthroughSubject().eraseToAnyPublisher() } - func startSync() { + func startSync(completion: @escaping DataStoreCallback) { + completion(.successfulVoid) + if let responder = responders[.startSync] as? StartSyncResponder { + return responder.callback("") + } + } + + func stopSync(completion: @escaping DataStoreCallback) { + completion(.successfulVoid) + if let responder = responders[.stopSync] as? StopSyncResponder { + return responder.callback("") + } } func setUp(modelSchemas: [ModelSchema]) throws { @@ -251,7 +281,10 @@ class MockStorageEngineBehavior: StorageEngineBehavior { sort: [QuerySortDescriptor]?, paginationInput: QueryPaginationInput?, completion: DataStoreCallback<[M]>) { - //TODO: Find way to mock this + completion(.success([])) + if let responder = responders[.query] as? QueryResponder { + return responder.callback("") + } } func query(_ modelType: M.Type, @@ -260,10 +293,16 @@ class MockStorageEngineBehavior: StorageEngineBehavior { sort: [QuerySortDescriptor]?, paginationInput: QueryPaginationInput?, completion: (DataStoreResult<[M]>) -> Void) { - //TODO: Find way to mock this + completion(.success([])) + if let responder = responders[.query] as? QueryResponder { + return responder.callback("") + } } func clear(completion: @escaping DataStoreCallback) { - //TODO: Find way to mock this + completion(.successfulVoid) + if let responder = responders[.clear] as? ClearResponder { + return responder.callback("") + } } } diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapterResponders.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapterResponders.swift index 07d854101e..a40f82d580 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapterResponders.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapterResponders.swift @@ -35,3 +35,19 @@ typealias SaveModelCompletionResponder = MockResponder<(M, DataStoreCa typealias SaveUntypedModelResponder = MockResponder<(Model, DataStoreCallback), Void> typealias DeleteUntypedModelCompletionResponder = MockResponder<(Model.Type, String), Void> + +extension MockStorageEngineBehavior { + enum ResponderKeys { + case initConstructor + case startSync + case stopSync + case clear + case query + } +} + +typealias InitResponder = MockResponder +typealias StartSyncResponder = MockResponder +typealias StopSyncResponder = MockResponder +typealias ClearResponder = MockResponder +typealias QueryResponder = MockResponder diff --git a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/SyncEngineTestBase.swift b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/SyncEngineTestBase.swift index 998f285c63..5b5716b560 100644 --- a/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/SyncEngineTestBase.swift +++ b/AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginTests/TestSupport/SyncEngineTestBase.swift @@ -135,6 +135,7 @@ class SyncEngineTestBase: XCTestCase { /// Starts amplify by invoking `Amplify.configure(amplifyConfig)` func startAmplify() throws { try Amplify.configure(amplifyConfig) + Amplify.DataStore.start(completion: {_ in}) } /// Starts amplify by invoking `Amplify.configure(amplifyConfig)`, and waits to receive a `syncStarted` Hub message diff --git a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj index 9363fff23e..d220baf7bb 100755 --- a/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/DataStore/DataStoreCategoryPlugin.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 6BC4FDAA23A899680027D20C /* MockRequestRetryablePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC4FDA923A899680027D20C /* MockRequestRetryablePolicy.swift */; }; 6BDC224023E21A4E007C8410 /* RemoteSyncEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDC223F23E21A4E007C8410 /* RemoteSyncEngineTests.swift */; }; 6BDC224223E27324007C8410 /* MockAWSInitialSyncOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BDC224123E27324007C8410 /* MockAWSInitialSyncOrchestrator.swift */; }; + 6BE7431C2575B8B000009FCB /* AWSAPICategoryPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE7431B2575B8B000009FCB /* AWSAPICategoryPluginTests.swift */; }; 8141657E756CC7B2EE1CE851 /* Pods_HostApp_AWSDataStoreCategoryPluginAuthIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA320D973669D3843FDF755E /* Pods_HostApp_AWSDataStoreCategoryPluginAuthIntegrationTests.framework */; }; A209418EED69D7B1BB8A55C2 /* Pods_AWSDataStoreCategoryPlugin_AWSDataStoreCategoryPluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C63AE18F2569D004E94BD550 /* Pods_AWSDataStoreCategoryPlugin_AWSDataStoreCategoryPluginTests.framework */; }; A569484A6FBAE7EC7ADF3FD4 /* Pods_AWSDataStoreCategoryPlugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6A1D332BE6CF885805360B3D /* Pods_AWSDataStoreCategoryPlugin.framework */; }; @@ -312,6 +313,7 @@ 6BC4FDA923A899680027D20C /* MockRequestRetryablePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRequestRetryablePolicy.swift; sourceTree = ""; }; 6BDC223F23E21A4E007C8410 /* RemoteSyncEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSyncEngineTests.swift; sourceTree = ""; }; 6BDC224123E27324007C8410 /* MockAWSInitialSyncOrchestrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAWSInitialSyncOrchestrator.swift; sourceTree = ""; }; + 6BE7431B2575B8B000009FCB /* AWSAPICategoryPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSAPICategoryPluginTests.swift; sourceTree = ""; }; 9D42A96449A4B73735566C07 /* Pods-AWSDataStoreCategoryPlugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AWSDataStoreCategoryPlugin.debug.xcconfig"; path = "Target Support Files/Pods-AWSDataStoreCategoryPlugin/Pods-AWSDataStoreCategoryPlugin.debug.xcconfig"; sourceTree = ""; }; 9F987EC33AAD7C0904A598CA /* Pods_HostApp_AWSDataStoreCategoryPluginIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HostApp_AWSDataStoreCategoryPluginIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B40EF02724BF68C900F2264C /* ConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; @@ -806,6 +808,7 @@ FAD2BDF423957F35006EB065 /* Core */ = { isa = PBXGroup; children = ( + 6BE7431B2575B8B000009FCB /* AWSAPICategoryPluginTests.swift */, B40EF02724BF68C900F2264C /* ConfigurationTests.swift */, B9FAA13F238C600A009414B4 /* ListTests.swift */, B912D1B9242984D10028F05C /* QueryPaginationInputTests.swift */, @@ -1629,6 +1632,7 @@ 2149E5FA238869CF00873955 /* APICategoryDependencyTests.swift in Sources */, FA4B8E942391C2CD009FC10F /* MutationIngesterConflictResolutionTests.swift in Sources */, FA4A9557239ACAD7008E876E /* ModelReconciliationQueueBehaviorTests.swift in Sources */, + 6BE7431C2575B8B000009FCB /* AWSAPICategoryPluginTests.swift in Sources */, FA4A955D239AD810008E876E /* MockSQLiteStorageEngineAdapterResponders.swift in Sources */, D80064F62499297800935DA3 /* MockFileManager.swift in Sources */, 6B4E3DF42397269C00AD962B /* OutgoingMutationQueueTestsWithMockStateMachine.swift in Sources */, diff --git a/AmplifyTestCommon/Mocks/MockDataStoreCategoryPlugin.swift b/AmplifyTestCommon/Mocks/MockDataStoreCategoryPlugin.swift index 718e3dcf64..cdd60ac33c 100644 --- a/AmplifyTestCommon/Mocks/MockDataStoreCategoryPlugin.swift +++ b/AmplifyTestCommon/Mocks/MockDataStoreCategoryPlugin.swift @@ -59,6 +59,14 @@ class MockDataStoreCategoryPlugin: MessageReporter, DataStoreCategoryPlugin { notify("clear") } + func start(completion: @escaping DataStoreCallback) { + notify("start") + } + + func stop(completion: @escaping DataStoreCallback) { + notify("stop") + } + @available(iOS 13.0, *) func publisher(for modelType: M.Type) -> AnyPublisher {