// // Copyright Amazon.com Inc. or its affiliates. // All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // import XCTest import SQLite import Combine @testable import Amplify @testable import AWSPluginsCore @testable import AmplifyTestCommon @testable import AWSDataStorePlugin /// Tests behavior of local DataStore subscriptions (as opposed to remote API subscription behaviors) class LocalSubscriptionTests: XCTestCase { override func setUp() async throws { try await super.setUp() await Amplify.reset() Amplify.Logging.logLevel = .warn let storageAdapter: SQLiteStorageEngineAdapter let storageEngine: StorageEngine var stateMachine: MockStateMachine<RemoteSyncEngine.State, RemoteSyncEngine.Action>! let validAPIPluginKey = "MockAPICategoryPlugin" let validAuthPluginKey = "MockAuthCategoryPlugin" do { let connection = try Connection(.inMemory) storageAdapter = try SQLiteStorageEngineAdapter(connection: connection) try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas) let outgoingMutationQueue = NoOpMutationQueue() let mutationDatabaseAdapter = try AWSMutationDatabaseAdapter(storageAdapter: storageAdapter) let awsMutationEventPublisher = AWSMutationEventPublisher(eventSource: mutationDatabaseAdapter) stateMachine = MockStateMachine(initialState: .notStarted, resolver: RemoteSyncEngine.Resolver.resolve(currentState:action:)) let syncEngine = RemoteSyncEngine( storageAdapter: storageAdapter, dataStoreConfiguration: .default, authModeStrategy: AWSDefaultAuthModeStrategy(), outgoingMutationQueue: outgoingMutationQueue, mutationEventIngester: mutationDatabaseAdapter, mutationEventPublisher: awsMutationEventPublisher, initialSyncOrchestratorFactory: NoOpInitialSyncOrchestrator.factory, reconciliationQueueFactory: MockAWSIncomingEventReconciliationQueue.factory, stateMachine: stateMachine, networkReachabilityPublisher: nil, requestRetryablePolicy: MockRequestRetryablePolicy() ) storageEngine = StorageEngine(storageAdapter: storageAdapter, dataStoreConfiguration: .default, syncEngine: syncEngine, validAPIPluginKey: validAPIPluginKey, validAuthPluginKey: validAuthPluginKey) } catch { XCTFail(String(describing: error)) return } let storageEngineBehaviorFactory: StorageEngineBehaviorFactory = {_, _, _, _, _, _ throws in return storageEngine } let dataStorePublisher = DataStorePublisher() let dataStorePlugin = AWSDataStorePlugin(modelRegistration: TestModelRegistration(), storageEngineBehaviorFactory: storageEngineBehaviorFactory, dataStorePublisher: dataStorePublisher, validAPIPluginKey: validAPIPluginKey, validAuthPluginKey: validAuthPluginKey) let dataStoreConfig = DataStoreCategoryConfiguration(plugins: [ "awsDataStorePlugin": true ]) // Since these tests use syncable models, we have to set up an API category also let apiConfig = APICategoryConfiguration(plugins: ["MockAPICategoryPlugin": true]) let apiPlugin = MockAPICategoryPlugin() let amplifyConfig = AmplifyConfiguration(api: apiConfig, dataStore: dataStoreConfig) do { try Amplify.add(plugin: apiPlugin) try Amplify.add(plugin: dataStorePlugin) try Amplify.configure(amplifyConfig) } catch { XCTFail(String(describing: error)) return } } /// - Given: A configured Amplify system on iOS 13 or higher /// - When: /// - I get an async sequence observing a model /// - Then: /// - I receive notifications for updates to that model func testObserve() async throws { let receivedMutationEvent = asyncExpectation(description: "Received mutation event") let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await _ in mutationEvents { await receivedMutationEvent.fulfill() } } catch { XCTFail("Unexpected error: \(error)") } } let model = Post(id: UUID().uuidString, title: "Test Post", content: "Test Post Content", createdAt: .now(), updatedAt: nil, draft: false, rating: nil, comments: []) _ = try await Amplify.DataStore.save(model) await waitForExpectations([receivedMutationEvent], timeout: 1.0) subscription.cancel() } /// - Given: A configured DataStore /// - When: /// - I subscribe to model events /// - Then: /// - I am notified of `create` mutations func testCreate() async throws { let receivedMutationEvent = asyncExpectation(description: "Received mutation event") let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.mutationType == MutationEvent.MutationType.create.rawValue { await receivedMutationEvent.fulfill() } } } catch { XCTFail("Unexpected error: \(error)") } } let model = Post(id: UUID().uuidString, title: "Test Post", content: "Test Post Content", createdAt: .now(), updatedAt: nil, draft: false, rating: nil, comments: []) _ = try await Amplify.DataStore.save(model) await waitForExpectations([receivedMutationEvent], timeout: 1.0) subscription.cancel() } /// - Given: A configured DataStore /// - When: /// - I subscribe to model events /// - Then: /// - I am notified of `update` mutations func testUpdate() async throws { let originalContent = "Content as of \(Date())" let model = Post(id: UUID().uuidString, title: "Test Post", content: originalContent, createdAt: .now(), updatedAt: nil, draft: false, rating: nil, comments: []) _ = try await Amplify.DataStore.save(model) let newContent = "Updated content as of \(Date())" var newModel = model newModel.content = newContent newModel.updatedAt = .now() let receivedMutationEvent = asyncExpectation(description: "Received mutation event") let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await _ in mutationEvents { await receivedMutationEvent.fulfill() } } catch { XCTFail("Unexpected error: \(error)") } } _ = try await Amplify.DataStore.save(newModel) await waitForExpectations([receivedMutationEvent], timeout: 1.0) subscription.cancel() } /// - Given: A configured DataStore /// - When: /// - I subscribe to model events /// - Then: /// - I am notified of `delete` mutations func testDelete() async throws { let receivedMutationEvent = asyncExpectation(description: "Received mutation event") let subscription = Task { let mutationEvents = Amplify.DataStore.observe(Post.self) do { for try await mutationEvent in mutationEvents { if mutationEvent.mutationType == MutationEvent.MutationType.delete.rawValue { await receivedMutationEvent.fulfill() } } } catch { XCTFail("Unexpected error: \(error)") } } let model = Post(title: "Test Post", content: "Test Post Content", createdAt: .now()) _ = try await Amplify.DataStore.save(model) _ = try await Amplify.DataStore.delete(model) await waitForExpectations([receivedMutationEvent], timeout: 1.0) subscription.cancel() } }