diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
index 6970be959c..5fda4b76eb 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Amplify-Package.xcscheme
@@ -482,6 +482,48 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ /// operation that to be eval
+ typealias Operation = () async throws -> T
+ typealias OnError = (Error) -> Bool
+
+ private let operations: AsyncStream
+ private var shouldTryNextOnError: OnError = { _ in true }
+ private var cancellables = Set()
+ private var task: Task?
+
+ deinit {
+ cancel()
+ }
+
+ init(operations: AsyncStream, shouldTryNextOnError: OnError? = nil) {
+ self.operations = operations
+ if let shouldTryNextOnError {
+ self.shouldTryNextOnError = shouldTryNextOnError
+ }
+ }
+
+ convenience init(
+ operationStream: AnyPublisher,
+ shouldTryNextOnError: OnError? = nil
+ ) {
+ var cancellables = Set()
+ let (asyncStream, continuation) = AsyncStream.makeStream(of: Operation.self)
+ operationStream.sink { _ in
+ continuation.finish()
+ } receiveValue: {
+ continuation.yield($0)
+ }.store(in: &cancellables)
+
+ self.init(
+ operations: asyncStream,
+ shouldTryNextOnError: shouldTryNextOnError
+ )
+ self.cancellables = cancellables
+ }
+
+ /// Synchronous version of executing the operations
+ func execute() -> Future {
+ Future { [weak self] promise in
+ self?.task = Task { [weak self] in
+ do {
+ if let self {
+ promise(.success(try await self.run()))
+ } else {
+ promise(.failure(NondeterminsticOperationError.cancelled))
+ }
+ } catch {
+ promise(.failure(error))
+ }
+ }
+ }
+ }
+
+ /// Asynchronous version of executing the operations
+ func run() async throws -> T {
+ for await operation in operations {
+ if Task.isCancelled {
+ throw NondeterminsticOperationError.cancelled
+ }
+ do {
+ return try await operation()
+ } catch {
+ if shouldTryNextOnError(error) {
+ continue
+ } else {
+ throw error
+ }
+ }
+ }
+ throw NondeterminsticOperationError.totalFailure
+ }
+
+ /// Cancel the operation
+ func cancel() {
+ task?.cancel()
+ cancellables = Set()
+ }
+}
diff --git a/Amplify/Categories/API/Operation/RetryableGraphQLOperation.swift b/Amplify/Categories/API/Operation/RetryableGraphQLOperation.swift
index ed2a6e2753..d746ba4905 100644
--- a/Amplify/Categories/API/Operation/RetryableGraphQLOperation.swift
+++ b/Amplify/Categories/API/Operation/RetryableGraphQLOperation.swift
@@ -6,183 +6,150 @@
//
import Foundation
+import Combine
-/// Convenience protocol to handle any kind of GraphQLOperation
-public protocol AnyGraphQLOperation {
- associatedtype Success
- associatedtype Failure: Error
- typealias ResultListener = (Result) -> Void
-}
-
-/// Abastraction for a retryable GraphQLOperation.
-public protocol RetryableGraphQLOperationBehavior: Operation, DefaultLogger {
- associatedtype Payload: Decodable
-
- /// GraphQLOperation concrete type
- associatedtype OperationType: AnyGraphQLOperation
-
- typealias RequestFactory = () async -> GraphQLRequest
- typealias OperationFactory = (GraphQLRequest, @escaping OperationResultListener) -> OperationType
- typealias OperationResultListener = OperationType.ResultListener
-
- /// Operation unique identifier
- var id: UUID { get }
-
- /// Number of attempts (min 1)
- var attempts: Int { get set }
-
- /// Underlying GraphQL operation instantiated by `operationFactory`
- var underlyingOperation: AtomicValue { get set }
- /// Maximum number of allowed retries
- var maxRetries: Int { get }
-
- /// GraphQLRequest factory, invoked to create a new operation
- var requestFactory: RequestFactory { get }
-
- /// GraphQL operation factory, invoked with a newly created GraphQL request
- /// and a wrapped result listener.
- var operationFactory: OperationFactory { get }
+// MARK: - RetryableGraphQLOperation
+public final class RetryableGraphQLOperation {
+ public typealias Payload = Payload
- var resultListener: OperationResultListener { get }
+ private let nondeterminsticOperation: NondeterminsticOperation.Success>
- init(requestFactory: @escaping RequestFactory,
- maxRetries: Int,
- resultListener: @escaping OperationResultListener,
- _ operationFactory: @escaping OperationFactory)
+ public init(
+ requestStream: AsyncStream<() async throws -> GraphQLTask.Success>
+ ) {
+ self.nondeterminsticOperation = NondeterminsticOperation(
+ operations: requestStream,
+ shouldTryNextOnError: Self.onError(_:)
+ )
+ }
- func start(request: GraphQLRequest)
+ deinit {
+ cancel()
+ }
- func shouldRetry(error: APIError?) -> Bool
-}
+ static func onError(_ error: Error) -> Bool {
+ guard let error = error as? APIError,
+ let authError = error.underlyingError as? AuthError
+ else {
+ return false
+ }
-extension RetryableGraphQLOperationBehavior {
- public static var log: Logger {
- Amplify.Logging.logger(forCategory: CategoryType.api.displayName, forNamespace: String(describing: self))
- }
- public var log: Logger {
- Self.log
+ switch authError {
+ case .notAuthorized: return true
+ default: return false
+ }
}
-}
-// MARK: RetryableGraphQLOperationBehavior + default implementation
-extension RetryableGraphQLOperationBehavior {
- public func start(request: GraphQLRequest) {
- attempts += 1
- log.debug("[\(id)] - Try [\(attempts)/\(maxRetries)]")
- let wrappedResultListener: OperationResultListener = { result in
- if case let .failure(error) = result, self.shouldRetry(error: error as? APIError) {
- self.log.debug("\(error)")
- Task {
- self.start(request: await self.requestFactory())
- }
- return
- }
-
- if case let .failure(error) = result {
- self.log.debug("\(error)")
- self.log.debug("[\(self.id)] - Failed")
+ public func execute(
+ _ operationType: GraphQLOperationType
+ ) -> AnyPublisher.Success, APIError> {
+ nondeterminsticOperation.execute().mapError {
+ if let apiError = $0 as? APIError {
+ return apiError
+ } else {
+ return APIError.operationError("Failed to execute GraphQL operation", "", $0)
}
+ }.eraseToAnyPublisher()
+ }
- if case .success = result {
- self.log.debug("[Operation \(self.id)] - Success")
+ public func run() async -> Result.Success, APIError> {
+ do {
+ let result = try await nondeterminsticOperation.run()
+ return .success(result)
+ } catch {
+ if let apiError = error as? APIError {
+ return .failure(apiError)
+ } else {
+ return .failure(.operationError("Failed to execute GraphQL operation", "", error))
}
- self.resultListener(result)
}
- underlyingOperation.set(operationFactory(request, wrappedResultListener))
}
+
+ public func cancel() {
+ nondeterminsticOperation.cancel()
+ }
+
}
-// MARK: - RetryableGraphQLOperation
-public final class RetryableGraphQLOperation: Operation, RetryableGraphQLOperationBehavior {
+public final class RetryableGraphQLSubscriptionOperation {
+
public typealias Payload = Payload
- public typealias OperationType = GraphQLOperation
-
- public var id: UUID
- public var maxRetries: Int
- public var attempts: Int = 0
- public var requestFactory: RequestFactory
- public var underlyingOperation: AtomicValue?> = AtomicValue(initialValue: nil)
- public var resultListener: OperationResultListener
- public var operationFactory: OperationFactory
-
- public init(requestFactory: @escaping RequestFactory,
- maxRetries: Int,
- resultListener: @escaping OperationResultListener,
- _ operationFactory: @escaping OperationFactory) {
- self.id = UUID()
- self.maxRetries = max(1, maxRetries)
- self.requestFactory = requestFactory
- self.operationFactory = operationFactory
- self.resultListener = resultListener
+ public typealias SubscriptionEvents = GraphQLSubscriptionEvent
+ private var task: Task?
+ private let nondeterminsticOperation: NondeterminsticOperation>
+
+ public init(
+ requestStream: AsyncStream<() async throws -> AmplifyAsyncThrowingSequence>
+ ) {
+ self.nondeterminsticOperation = NondeterminsticOperation(operations: requestStream)
}
- public override func main() {
- Task {
- start(request: await requestFactory())
- }
+ deinit {
+ cancel()
}
- override public func cancel() {
- self.underlyingOperation.get()?.cancel()
+ public func subscribe() -> AnyPublisher {
+ let subject = PassthroughSubject()
+ self.task = Task { await self.trySubscribe(subject) }
+ return subject.eraseToAnyPublisher()
}
- public func shouldRetry(error: APIError?) -> Bool {
- guard case let .operationError(_, _, underlyingError) = error,
- let authError = underlyingError as? AuthError else {
- return false
- }
+ private func trySubscribe(_ subject: PassthroughSubject) async {
+ var apiError: APIError?
+ do {
+ try Task.checkCancellation()
+ let sequence = try await self.nondeterminsticOperation.run()
+ defer { sequence.cancel() }
+ for try await event in sequence {
+ try Task.checkCancellation()
+ subject.send(event)
+ }
+ } catch is CancellationError {
+ subject.send(completion: .finished)
+ } catch {
+ if let error = error as? APIError {
+ apiError = error
+ }
+ Self.log.debug("Failed with subscription request: \(error)")
+ }
- switch authError {
- case .signedOut, .notAuthorized:
- return attempts < maxRetries
- default:
- return false
+ if apiError != nil {
+ subject.send(completion: .failure(apiError!))
+ } else {
+ subject.send(completion: .finished)
}
}
-}
-
-// MARK: - RetryableGraphQLSubscriptionOperation
-public final class RetryableGraphQLSubscriptionOperation: Operation,
- RetryableGraphQLOperationBehavior {
- public typealias OperationType = GraphQLSubscriptionOperation
- public typealias Payload = Payload
-
- public var id: UUID
- public var maxRetries: Int
- public var attempts: Int = 0
- public var underlyingOperation: AtomicValue?> = AtomicValue(initialValue: nil)
- public var requestFactory: RequestFactory
- public var resultListener: OperationResultListener
- public var operationFactory: OperationFactory
-
- public init(requestFactory: @escaping RequestFactory,
- maxRetries: Int,
- resultListener: @escaping OperationResultListener,
- _ operationFactory: @escaping OperationFactory) {
- self.id = UUID()
- self.maxRetries = max(1, maxRetries)
- self.requestFactory = requestFactory
- self.operationFactory = operationFactory
- self.resultListener = resultListener
+ public func cancel() {
+ self.task?.cancel()
+ self.nondeterminsticOperation.cancel()
}
- public override func main() {
- Task {
- start(request: await requestFactory())
+}
+
+extension AsyncSequence {
+ fileprivate var asyncStream: AsyncStream {
+ AsyncStream { continuation in
+ Task {
+ var it = self.makeAsyncIterator()
+ do {
+ while let ele = try await it.next() {
+ continuation.yield(ele)
+ }
+ continuation.finish()
+ } catch {
+ continuation.finish()
+ }
+ }
}
}
+}
- public override func cancel() {
- self.underlyingOperation.get()?.cancel()
+extension RetryableGraphQLSubscriptionOperation {
+ public static var log: Logger {
+ Amplify.Logging.logger(forCategory: CategoryType.api.displayName, forNamespace: String(describing: self))
}
-
- public func shouldRetry(error: APIError?) -> Bool {
- return attempts < maxRetries
+ public var log: Logger {
+ Self.log
}
-
}
-
-// MARK: GraphQLOperation - GraphQLSubscriptionOperation + AnyGraphQLOperation
-extension GraphQLOperation: AnyGraphQLOperation {}
-extension GraphQLSubscriptionOperation: AnyGraphQLOperation {}
diff --git a/Amplify/Categories/API/Request/GraphQLOperationType.swift b/Amplify/Categories/API/Request/GraphQLOperationType.swift
index 38160a72f2..7e9e2735ed 100644
--- a/Amplify/Categories/API/Request/GraphQLOperationType.swift
+++ b/Amplify/Categories/API/Request/GraphQLOperationType.swift
@@ -6,7 +6,7 @@
//
/// The type of a GraphQL operation
-public enum GraphQLOperationType {
+public enum GraphQLOperationType: String {
/// A GraphQL Query operation
case query
diff --git a/Amplify/Core/Support/AmplifyAsyncThrowingSequence.swift b/Amplify/Core/Support/AmplifyAsyncThrowingSequence.swift
index 3bb58d9f64..5ff7d388eb 100644
--- a/Amplify/Core/Support/AmplifyAsyncThrowingSequence.swift
+++ b/Amplify/Core/Support/AmplifyAsyncThrowingSequence.swift
@@ -47,4 +47,5 @@ public class AmplifyAsyncThrowingSequence: AsyncSequence, Can
parent?.cancel()
finish()
}
+
}
diff --git a/Amplify/Core/Support/AmplifyTask+OperationTaskAdapters.swift b/Amplify/Core/Support/AmplifyTask+OperationTaskAdapters.swift
index fb56c6df18..50e505bce9 100644
--- a/Amplify/Core/Support/AmplifyTask+OperationTaskAdapters.swift
+++ b/Amplify/Core/Support/AmplifyTask+OperationTaskAdapters.swift
@@ -20,7 +20,9 @@ public class AmplifyOperationTaskAdapter) {
self.operation = operation
self.childTask = ChildTask(parent: operation)
- resultToken = operation.subscribe(resultListener: resultListener)
+ resultToken = operation.subscribe { [weak self] in
+ self?.resultListener($0)
+ }
}
deinit {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin+Configure.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin+Configure.swift
index ec27a34b41..e63ed08dcb 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin+Configure.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin+Configure.swift
@@ -7,6 +7,7 @@
@_spi(InternalAmplifyConfiguration) import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import AwsCommonRuntimeKit
public extension AWSAPIPlugin {
@@ -55,7 +56,7 @@ extension AWSAPIPlugin {
/// A holder for AWSAPIPlugin dependencies that provides sane defaults for
/// production
struct ConfigurationDependencies {
- let authService: AWSAuthServiceBehavior
+ let authService: AWSAuthCredentialsProviderBehavior
let pluginConfig: AWSAPICategoryPluginConfiguration
let appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol
let logLevel: Amplify.LogLevel
@@ -63,7 +64,7 @@ extension AWSAPIPlugin {
init(
configurationValues: JSONValue,
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior? = nil,
+ authService: AWSAuthCredentialsProviderBehavior? = nil,
appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol? = nil,
logLevel: Amplify.LogLevel? = nil
) throws {
@@ -90,7 +91,7 @@ extension AWSAPIPlugin {
init(
configuration: AmplifyOutputsData,
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior = AWSAuthService(),
+ authService: AWSAuthCredentialsProviderBehavior = AWSAuthService(),
appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol? = nil,
logLevel: Amplify.LogLevel = Amplify.Logging.logLevel
) throws {
@@ -111,7 +112,7 @@ extension AWSAPIPlugin {
init(
pluginConfig: AWSAPICategoryPluginConfiguration,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol,
logLevel: Amplify.LogLevel
) {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin.swift
index e7ea03dc09..dcdcd7095a 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/AWSAPIPlugin.swift
@@ -7,9 +7,10 @@
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
-final public class AWSAPIPlugin: NSObject, APICategoryPlugin, APICategoryGraphQLBehaviorExtended, AWSAPIAuthInformation {
+final public class AWSAPIPlugin: NSObject, APICategoryPlugin, AWSAPIAuthInformation {
/// Used for the default GraphQL API represented by the `data` category in `amplify_outputs.json`
/// This constant is not used for APIs present in `amplifyconfiguration.json` since they always have names.
public static let defaultGraphQLAPI = "defaultGraphQLAPI"
@@ -25,7 +26,7 @@ final public class AWSAPIPlugin: NSObject, APICategoryPlugin, APICategoryGraphQL
/// The provider for Auth services required to access protected APIs. This will be
/// populated during the configuration phase, and is clearable by `reset()`.
- var authService: AWSAuthServiceBehavior!
+ var authService: AWSAuthCredentialsProviderBehavior!
/// The provider for network connections and operations. This will be populated
/// during initialization, and is clearable by `reset()`.
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPICategoryPluginConfiguration.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPICategoryPluginConfiguration.swift
index 1ea015dd7f..07005a7034 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPICategoryPluginConfiguration.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPICategoryPluginConfiguration.swift
@@ -8,6 +8,7 @@
@_spi(InternalAmplifyConfiguration) import Amplify
import Foundation
import AWSPluginsCore
+import InternalAmplifyCredentials
// Convenience typealias
typealias APIEndpointName = String
@@ -17,11 +18,11 @@ public struct AWSAPICategoryPluginConfiguration {
private var interceptors: [APIEndpointName: AWSAPIEndpointInterceptors]
private var apiAuthProviderFactory: APIAuthProviderFactory?
- private var authService: AWSAuthServiceBehavior?
+ private var authService: AWSAuthCredentialsProviderBehavior?
init(jsonValue: JSONValue,
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior) throws {
+ authService: AWSAuthCredentialsProviderBehavior) throws {
guard case .object(let config) = jsonValue else {
throw PluginError.pluginConfigurationError(
"Could not cast incoming configuration to a JSONValue `.object`",
@@ -50,7 +51,7 @@ public struct AWSAPICategoryPluginConfiguration {
init(configuration: AmplifyOutputsData,
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior) throws {
+ authService: AWSAuthCredentialsProviderBehavior) throws {
guard let data = configuration.data else {
throw PluginError.pluginConfigurationError(
@@ -95,7 +96,7 @@ public struct AWSAPICategoryPluginConfiguration {
internal init(endpoints: [APIEndpointName: EndpointConfig],
interceptors: [APIEndpointName: AWSAPIEndpointInterceptors] = [:],
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior) {
+ authService: AWSAuthCredentialsProviderBehavior) {
self.endpoints = endpoints
self.interceptors = interceptors
self.apiAuthProviderFactory = apiAuthProviderFactory
@@ -215,7 +216,7 @@ public struct AWSAPICategoryPluginConfiguration {
/// - Returns: dictionary of AWSAPIEndpointInterceptors indexed by API endpoint name
private static func makeInterceptors(forEndpoints endpoints: [APIEndpointName: EndpointConfig],
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior) throws -> [APIEndpointName: AWSAPIEndpointInterceptors] {
+ authService: AWSAuthCredentialsProviderBehavior) throws -> [APIEndpointName: AWSAPIEndpointInterceptors] {
var interceptors: [APIEndpointName: AWSAPIEndpointInterceptors] = [:]
for (name, config) in endpoints {
var interceptorsConfig = AWSAPIEndpointInterceptors(endpointName: name,
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift
index 5d37dc6c6b..99191e40dc 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Configuration/AWSAPIEndpointInterceptors.swift
@@ -8,6 +8,7 @@
import Amplify
import Foundation
import AWSPluginsCore
+import InternalAmplifyCredentials
/// The order of interceptor decoration is as follows:
/// 1. **prelude interceptors**
@@ -22,7 +23,7 @@ struct AWSAPIEndpointInterceptors {
let apiEndpointName: APIEndpointName
let apiAuthProviderFactory: APIAuthProviderFactory
- let authService: AWSAuthServiceBehavior?
+ let authService: AWSAuthCredentialsProviderBehavior?
var preludeInterceptors: [URLRequestInterceptor] = []
@@ -46,7 +47,7 @@ struct AWSAPIEndpointInterceptors {
init(endpointName: APIEndpointName,
apiAuthProviderFactory: APIAuthProviderFactory,
- authService: AWSAuthServiceBehavior? = nil) {
+ authService: AWSAuthCredentialsProviderBehavior? = nil) {
self.apiEndpointName = endpointName
self.apiAuthProviderFactory = apiAuthProviderFactory
self.authService = authService
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift
index e737479d9c..225d597c04 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/APIKeyURLRequestInterceptor.swift
@@ -7,6 +7,7 @@
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
struct APIKeyURLRequestInterceptor: URLRequestInterceptor {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenProviderWrapper.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenProviderWrapper.swift
index bc5be0d627..9137c018b5 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenProviderWrapper.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenProviderWrapper.swift
@@ -7,6 +7,7 @@
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
class AuthTokenProviderWrapper: AuthTokenProvider {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift
index 80ba255a4f..9ed5783c28 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/AuthTokenURLRequestInterceptor.swift
@@ -7,6 +7,7 @@
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
struct AuthTokenURLRequestInterceptor: URLRequestInterceptor {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift
index fa90ff71f4..5b354abb1d 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/RequestInterceptor/IAMURLRequestInterceptor.swift
@@ -7,6 +7,7 @@
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
import ClientRuntime
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
index c3d33320c2..cd023676c7 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Interceptor/SubscriptionInterceptor/IAMAuthInterceptor.swift
@@ -7,6 +7,7 @@
import Foundation
@_spi(WebSocket) import AWSPluginsCore
+import InternalAmplifyCredentials
import Amplify
import AWSClientRuntime
import ClientRuntime
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
index 44e2cf378d..96347697d4 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Operation/AWSGraphQLSubscriptionTaskRunner.swift
@@ -8,6 +8,7 @@
import Amplify
import Foundation
import AWSPluginsCore
+import InternalAmplifyCredentials
import Combine
public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner, InternalTaskAsyncThrowingSequence, InternalTaskThrowingChannel {
@@ -25,7 +26,7 @@ public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner,
}
let appSyncClientFactory: AppSyncRealTimeClientFactoryProtocol
let pluginConfig: AWSAPICategoryPluginConfiguration
- let authService: AWSAuthServiceBehavior
+ let authService: AWSAuthCredentialsProviderBehavior
var apiAuthProviderFactory: APIAuthProviderFactory
private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib
private let subscriptionId = UUID().uuidString
@@ -35,7 +36,7 @@ public class AWSGraphQLSubscriptionTaskRunner: InternalTaskRunner,
init(request: Request,
pluginConfig: AWSAPICategoryPluginConfiguration,
appSyncClientFactory: AppSyncRealTimeClientFactoryProtocol,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
apiAuthProviderFactory: APIAuthProviderFactory) {
self.request = request
self.pluginConfig = pluginConfig
@@ -185,7 +186,7 @@ final public class AWSGraphQLSubscriptionOperation: GraphQLSubscri
let pluginConfig: AWSAPICategoryPluginConfiguration
let appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol
- let authService: AWSAuthServiceBehavior
+ let authService: AWSAuthCredentialsProviderBehavior
private let userAgent = AmplifyAWSServiceConfiguration.userAgentLib
var appSyncRealTimeClient: AppSyncRealTimeClientProtocol?
@@ -201,7 +202,7 @@ final public class AWSGraphQLSubscriptionOperation: GraphQLSubscri
init(request: GraphQLOperationRequest,
pluginConfig: AWSAPICategoryPluginConfiguration,
appSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
apiAuthProviderFactory: APIAuthProviderFactory,
inProcessListener: AWSGraphQLSubscriptionOperation.InProcessListener?,
resultListener: AWSGraphQLSubscriptionOperation.ResultListener?) {
diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
index 1666312feb..57a3708e1e 100644
--- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
+++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/SubscriptionFactory/AppSyncRealTimeClientFactory.swift
@@ -9,13 +9,14 @@
import Foundation
import Amplify
import Combine
+import InternalAmplifyCredentials
@_spi(WebSocket) import AWSPluginsCore
protocol AppSyncRealTimeClientFactoryProtocol {
func getAppSyncRealTimeClient(
for endpointConfig: AWSAPICategoryPluginConfiguration.EndpointConfig,
endpoint: URL,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
authType: AWSAuthorizationType?,
apiAuthProviderFactory: APIAuthProviderFactory
) async throws -> AppSyncRealTimeClientProtocol
@@ -40,7 +41,7 @@ actor AppSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol {
public func getAppSyncRealTimeClient(
for endpointConfig: AWSAPICategoryPluginConfiguration.EndpointConfig,
endpoint: URL,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
authType: AWSAuthorizationType? = nil,
apiAuthProviderFactory: APIAuthProviderFactory
) throws -> AppSyncRealTimeClientProtocol {
@@ -90,7 +91,7 @@ actor AppSyncRealTimeClientFactory: AppSyncRealTimeClientFactoryProtocol {
private func getInterceptor(
for authorizationConfiguration: AWSAuthorizationConfiguration,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
apiAuthProviderFactory: APIAuthProviderFactory
) throws -> AppSyncRequestInterceptor & WebSocketInterceptor {
switch authorizationConfiguration {
diff --git a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
index 5e1ed6b31c..c151f9c50c 100644
--- a/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
+++ b/AmplifyPlugins/API/Tests/APIHostApp/AWSAPIPluginFunctionalTests/AppSyncRealTimeClientTests.swift
@@ -11,6 +11,7 @@ import Combine
@testable import Amplify
@testable import AWSAPIPlugin
@testable @_spi(WebSocket) import AWSPluginsCore
+@testable import InternalAmplifyCredentials
class AppSyncRealTimeClientTests: XCTestCase {
let subscriptionRequest = """
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift
index 2974af9f4d..ac458b9992 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+InterceptorBehaviorTests.swift
@@ -8,6 +8,7 @@
import XCTest
@testable import AWSAPIPlugin
import AWSPluginsCore
+import InternalAmplifyCredentials
// swiftlint:disable:next type_name
class AWSAPICategoryPluginInterceptorBehaviorTests: AWSAPICategoryPluginTestBase {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+ReachabilityTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+ReachabilityTests.swift
index 6123db2a2e..2d005cbdf1 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+ReachabilityTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/AWSAPICategoryPlugin+ReachabilityTests.swift
@@ -82,7 +82,7 @@ class AWSAPICategoryPluginReachabilityTests: XCTestCase {
XCTAssertEqual(reachability.key, graphQLAPI)
}
- func testReachabilityConcurrentPerform() throws {
+ func testReachabilityConcurrentPerform() async throws {
let graphQLAPI = "graphQLAPI"
let restAPI = "restAPI"
do {
@@ -114,7 +114,7 @@ class AWSAPICategoryPluginReachabilityTests: XCTestCase {
concurrentPerformCompleted.fulfill()
}
- wait(for: [concurrentPerformCompleted], timeout: 1)
+ await fulfillment(of: [concurrentPerformCompleted], timeout: 1)
XCTAssertEqual(apiPlugin.reachabilityMap.count, 2)
}
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift
index a2f7503f01..b71e5491a8 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPICategoryPluginConfigurationTests.swift
@@ -12,6 +12,7 @@ import Foundation
@testable import AWSAPIPlugin
@testable import AWSPluginsTestCommon
import AWSPluginsCore
+import InternalAmplifyCredentials
class AWSAPICategoryPluginConfigurationTests: XCTestCase {
let graphQLAPI = "graphQLAPI"
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift
index a48d5fa96e..03520086ef 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Configuration/AWSAPIEndpointInterceptorsTests.swift
@@ -8,6 +8,7 @@
import XCTest
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
@testable import AmplifyTestCommon
@testable import AWSAPIPlugin
@testable import AWSPluginsTestCommon
@@ -102,7 +103,7 @@ class AWSAPIEndpointInterceptorsTests: XCTestCase {
// MARK: - Test Helpers
- func createAPIInterceptorConfig(authService: AWSAuthServiceBehavior = MockAWSAuthService()) -> AWSAPIEndpointInterceptors {
+ func createAPIInterceptorConfig(authService: AWSAuthCredentialsProviderBehavior = MockAWSAuthService()) -> AWSAPIEndpointInterceptors {
return AWSAPIEndpointInterceptors(
endpointName: endpointName,
apiAuthProviderFactory: APIAuthProviderFactory(),
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderPaginationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderPaginationTests.swift
index 57843d9c92..410077d90d 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderPaginationTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderPaginationTests.swift
@@ -32,11 +32,11 @@ extension AppSyncListProviderTests {
}
XCTAssertFalse(provider.hasNextPage())
}
-
+
func testNotLoadedStateHasNextPageFalse() {
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
- apiName: "apiName",
+ apiName: "apiName",
authMode: nil)
let provider = AppSyncListProvider(metadata: modelMetadata)
guard case .notLoaded = provider.loadedState else {
@@ -53,8 +53,7 @@ extension AppSyncListProviderTests {
let nextPage = List(elements: [Comment4(content: "content"),
Comment4(content: "content"),
Comment4(content: "content")])
- let event: GraphQLOperation>.OperationResult = .success(.success(nextPage))
- return event
+ return .success(nextPage)
}
let elements = [Comment4(content: "content")]
let provider = AppSyncListProvider(elements: elements, nextToken: "nextToken")
@@ -85,10 +84,8 @@ extension AppSyncListProviderTests {
}
func testLoadedStateGetNextPageFailure_APIError() async {
- mockAPIPlugin.responders[.queryRequestResponse] =
- QueryRequestResponder> { _ in
- let event: GraphQLOperation>.OperationResult = .failure(APIError.unknown("", "", nil))
- return event
+ mockAPIPlugin.responders[.queryRequestResponse] = QueryRequestResponder> { _ in
+ throw APIError.unknown("", "", nil)
}
let elements = [Comment4(content: "content")]
let provider = AppSyncListProvider(elements: elements, nextToken: "nextToken")
@@ -97,7 +94,7 @@ extension AppSyncListProviderTests {
XCTFail("Should be loaded")
return
}
-
+
do {
_ = try await provider.getNextPage()
XCTFail("Should have failed")
@@ -111,20 +108,19 @@ extension AppSyncListProviderTests {
func testLoadedStateGetNextPageFailure_GraphQLErrorResponse() async {
mockAPIPlugin.responders[.queryRequestResponse] =
QueryRequestResponder> { request in
-
XCTAssertEqual(request.apiName, "apiName")
XCTAssertEqual(request.authMode as? AWSAuthorizationType, .amazonCognitoUserPools)
- let event: GraphQLOperation>.OperationResult = .success(
- .failure(GraphQLResponseError.error([GraphQLError]())))
- return event
+
+ return .failure(GraphQLResponseError.error([GraphQLError]()))
}
+
let elements = [Comment4(content: "content")]
let provider = AppSyncListProvider(elements: elements, nextToken: "nextToken", apiName: "apiName", authMode: .amazonCognitoUserPools)
guard case .loaded = provider.loadedState else {
XCTFail("Should be loaded")
return
}
-
+
do {
_ = try await provider.getNextPage()
XCTFail("Should have failed")
@@ -138,11 +134,11 @@ extension AppSyncListProviderTests {
XCTFail("Unexpected error type \(error)")
}
}
-
+
func testNotLoadedStateGetNextPageFailure() async {
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
- apiName: "apiName",
+ apiName: "apiName",
authMode: nil)
let provider = AppSyncListProvider(metadata: modelMetadata)
guard case .notLoaded = provider.loadedState else {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift
index 1e02c9beaf..e8c7abd661 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Core/AppSyncListProviderTests.swift
@@ -142,8 +142,7 @@ class AppSyncListProviderTests: XCTestCase {
],
"nextToken": "nextToken"
]
- let event: GraphQLOperation.OperationResult = .success(.success(json))
- return event
+ return .success(json)
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
@@ -178,10 +177,8 @@ class AppSyncListProviderTests: XCTestCase {
}
func testNotLoadedStateSynchronousLoadFailure() async {
- mockAPIPlugin.responders[.queryRequestResponse] =
- QueryRequestResponder { _ in
- let event: GraphQLOperation.OperationResult = .failure(APIError.unknown("", "", nil))
- return event
+ mockAPIPlugin.responders[.queryRequestResponse] = QueryRequestResponder { _ in
+ throw APIError.unknown("", "", nil)
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
@@ -228,8 +225,7 @@ class AppSyncListProviderTests: XCTestCase {
],
"nextToken": "nextToken"
]
- let event: GraphQLOperation.OperationResult = .success(.success(json))
- return event
+ return .success(json)
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
@@ -264,10 +260,8 @@ class AppSyncListProviderTests: XCTestCase {
}
func testNotLoadedStateLoadWithCompletionFailure_APIError() async {
- mockAPIPlugin.responders[.queryRequestResponse] =
- QueryRequestResponder { _ in
- let event: GraphQLOperation.OperationResult = .failure(APIError.unknown("", "", nil))
- return event
+ mockAPIPlugin.responders[.queryRequestResponse] = QueryRequestResponder { _ in
+ throw APIError.unknown("", "", nil)
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
@@ -300,11 +294,8 @@ class AppSyncListProviderTests: XCTestCase {
}
func testNotLoadedStateLoadWithCompletionFailure_GraphQLErrorResponse() async {
- mockAPIPlugin.responders[.queryRequestResponse] =
- QueryRequestResponder { _ in
- let event: GraphQLOperation.OperationResult = .success(
- .failure(GraphQLResponseError.error([GraphQLError]())))
- return event
+ mockAPIPlugin.responders[.queryRequestResponse] = QueryRequestResponder { _ in
+ return .failure(GraphQLResponseError.error([GraphQLError]()))
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
@@ -353,8 +344,7 @@ class AppSyncListProviderTests: XCTestCase {
],
"nextToken": "nextToken"
]
- let event: GraphQLOperation.OperationResult = .success(.success(json))
- return event
+ return .success(json)
}
let modelMetadata = AppSyncListDecoder.Metadata(appSyncAssociatedIdentifiers: ["postId"],
appSyncAssociatedFields: ["post"],
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/AuthTokenURLRequestInterceptorTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/AuthTokenURLRequestInterceptorTests.swift
index 5f25a0dc9a..74e1041f82 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/AuthTokenURLRequestInterceptorTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Interceptor/AuthTokenURLRequestInterceptorTests.swift
@@ -7,6 +7,7 @@
import XCTest
import AWSPluginsCore
+import InternalAmplifyCredentials
@testable import Amplify
@testable import AmplifyTestCommon
@testable import AWSAPIPlugin
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
index 2ba9f97779..51c1feea3e 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Mocks/MockSubscription.swift
@@ -11,14 +11,14 @@ import Amplify
import Combine
@testable import AWSAPIPlugin
@_spi(WebSocket) import AWSPluginsCore
+import InternalAmplifyCredentials
struct MockSubscriptionConnectionFactory: AppSyncRealTimeClientFactoryProtocol {
-
typealias OnGetOrCreateConnection = (
AWSAPICategoryPluginConfiguration.EndpointConfig,
URL,
- AWSAuthServiceBehavior,
+ AWSAuthCredentialsProviderBehavior,
AWSAuthorizationType?,
APIAuthProviderFactory
) async throws -> AppSyncRealTimeClientProtocol
@@ -32,7 +32,7 @@ struct MockSubscriptionConnectionFactory: AppSyncRealTimeClientFactoryProtocol {
func getAppSyncRealTimeClient(
for endpointConfig: AWSAPICategoryPluginConfiguration.EndpointConfig,
endpoint: URL,
- authService: AWSAuthServiceBehavior,
+ authService: AWSAuthCredentialsProviderBehavior,
authType: AWSAuthorizationType?,
apiAuthProviderFactory: APIAuthProviderFactory
) async throws -> AppSyncRealTimeClientProtocol {
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLOperationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLOperationTests.swift
index 717a87f5ab..9400006ebd 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLOperationTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSGraphQLOperationTests.swift
@@ -15,7 +15,7 @@ import AWSPluginsCore
class AWSGraphQLOperationTests: AWSAPICategoryPluginTestBase {
/// Tests that upon completion, the operation is removed from the task mapper.
- func testOperationCleanup() {
+ func testOperationCleanup() async {
let request = GraphQLRequest(apiName: apiName,
document: testDocument,
variables: nil,
@@ -34,7 +34,7 @@ class AWSGraphQLOperationTests: AWSAPICategoryPluginTestBase {
} receiveValue: { _ in }
defer { sink.cancel() }
- wait(for: [receivedCompletion], timeout: 1)
+ await fulfillment(of: [receivedCompletion], timeout: 1)
let task = operation.mapper.task(for: operation)
XCTAssertNil(task)
}
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift
index 6f76a6aa72..e6917c412c 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift
@@ -49,7 +49,7 @@ class AWSRESTOperationTests: OperationTestBase {
}
XCTAssertNotNil(operation.request)
- await fulfillment(of: [listenerWasInvoked], timeout: 1)
+ await fulfillment(of: [listenerWasInvoked], timeout: 3)
}
func testGetFailsWithBadAPIName() async throws {
@@ -76,7 +76,7 @@ class AWSRESTOperationTests: OperationTestBase {
/// - Given: A configured plugin
/// - When: I invoke `APICategory.get(apiName:path:listener:)`
/// - Then: The listener is invoked with the successful value
- func testGetReturnsValue() throws {
+ func testGetReturnsValue() async throws {
let sentData = Data([0x00, 0x01, 0x02, 0x03])
try setUpPluginForSingleResponse(sending: sentData, for: .rest)
@@ -92,10 +92,10 @@ class AWSRESTOperationTests: OperationTestBase {
callbackInvoked.fulfill()
}
- wait(for: [callbackInvoked], timeout: 1.0)
+ await fulfillment(of: [callbackInvoked], timeout: 1.0)
}
- func testRESTOperation_withCustomHeader_shouldOverrideDefaultAmplifyHeaders() throws {
+ func testRESTOperation_withCustomHeader_shouldOverrideDefaultAmplifyHeaders() async throws {
let expectedHeaderValue = "text/plain"
let sentData = Data([0x00, 0x01, 0x02, 0x03])
try setUpPluginForSingleResponse(sending: sentData, for: .rest)
@@ -117,7 +117,7 @@ class AWSRESTOperationTests: OperationTestBase {
}
callbackInvoked.fulfill()
}
- wait(for: [callbackInvoked, validated], timeout: 1.0)
+ await fulfillment(of: [callbackInvoked, validated], timeout: 1.0)
}
}
diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Reachability/NetworkReachabilityNotifierTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Reachability/NetworkReachabilityNotifierTests.swift
index cce395ccd3..9777f3b291 100644
--- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Reachability/NetworkReachabilityNotifierTests.swift
+++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Reachability/NetworkReachabilityNotifierTests.swift
@@ -93,7 +93,7 @@ class NetworkReachabilityNotifierTests: XCTestCase {
cancellable.cancel()
}
- func testWifiConnectivity_publisherGoesOutOfScope() {
+ func testWifiConnectivity_publisherGoesOutOfScope() async {
MockReachability.iConnection = .wifi
let defaultValueExpect = expectation(description: ".sink receives default value")
let completeExpect = expectation(description: ".sink receives completion")
@@ -104,12 +104,12 @@ class NetworkReachabilityNotifierTests: XCTestCase {
defaultValueExpect.fulfill()
})
- wait(for: [defaultValueExpect], timeout: 1.0)
+ await fulfillment(of: [defaultValueExpect], timeout: 1.0)
notifier = nil
notification = Notification.init(name: .reachabilityChanged)
NotificationCenter.default.post(notification)
- wait(for: [completeExpect], timeout: 1.0)
+ await fulfillment(of: [completeExpect], timeout: 1.0)
cancellable.cancel()
}
}
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift
index 4581f1d799..721880d462 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift
@@ -13,7 +13,7 @@ import AWSCognitoIdentityProvider
import AWSPluginsCore
import ClientRuntime
import AWSClientRuntime
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
@_spi(InternalHttpEngineProxy) import AWSPluginsCore
extension AWSCognitoAuthPlugin {
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+PluginExtension.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+PluginExtension.swift
index 81c0d9a77e..e617062e5c 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+PluginExtension.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+PluginExtension.swift
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0
//
-@_spi(InternalAmplifyPluginExtension) import AWSPluginsCore
+@_spi(InternalAmplifyPluginExtension) import InternalAmplifyCredentials
import Foundation
import ClientRuntime
diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Utils/HttpClientEngineProxy.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Utils/HttpClientEngineProxy.swift
index 2a8864e5a1..b3a5edf8d2 100644
--- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Utils/HttpClientEngineProxy.swift
+++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/Support/Utils/HttpClientEngineProxy.swift
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0
//
-@_spi(InternalHttpEngineProxy) @_spi(InternalAmplifyPluginExtension) import AWSPluginsCore
+@_spi(InternalHttpEngineProxy) @_spi(InternalAmplifyPluginExtension) import InternalAmplifyCredentials
import ClientRuntime
import Foundation
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/API/APICategoryGraphQLBehaviorExtended.swift b/AmplifyPlugins/Core/AWSPluginsCore/API/APICategoryGraphQLBehaviorExtended.swift
deleted file mode 100644
index 91d56f9763..0000000000
--- a/AmplifyPlugins/Core/AWSPluginsCore/API/APICategoryGraphQLBehaviorExtended.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// Copyright Amazon.com Inc. or its affiliates.
-// All Rights Reserved.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-
-import Foundation
-import Amplify
-
-/// Extending the existing `APICategoryGraphQLBehavior` to include callback based APIs.
-///
-/// This exists to allow DataStore to continue to use the `APICategoryGraphQLCallbackBehavior` APIs without exposing
-/// them publicly from Amplify in `APICategoryGraphQLBehavior`. Eventually, the goal is for DataStore to use the
-/// Async APIs, at which point, this protocol can be completely removed. Introducing this protocol allows Amplify to
-/// to fully deprecate the callback based APIs, while allowing DataStore a gradual migration path forward in moving
-/// away from APIPlugin's callback APIs to the Async APIs.
-/// See https://github.com/aws-amplify/amplify-ios/issues/2252 for more details
-///
-/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly
-/// by host applications. The behavior of this may change without warning.
-public protocol APICategoryGraphQLBehaviorExtended:
- APICategoryGraphQLCallbackBehavior, APICategoryGraphQLBehavior, AnyObject { }
-
-/// Listener callback based APIs
-///
-/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly
-/// by host applications. The behavior of this may change without warning.
-public protocol APICategoryGraphQLCallbackBehavior {
- @discardableResult
- func query(request: GraphQLRequest,
- listener: GraphQLOperation.ResultListener?) -> GraphQLOperation
- @discardableResult
- func mutate(request: GraphQLRequest,
- listener: GraphQLOperation.ResultListener?) -> GraphQLOperation
-
- func subscribe(request: GraphQLRequest,
- valueListener: GraphQLSubscriptionOperation.InProcessListener?,
- completionListener: GraphQLSubscriptionOperation.ResultListener?)
- -> GraphQLSubscriptionOperation
-}
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift
index 020dc68e60..d53920f158 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift
+++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthModeStrategy.swift
@@ -6,6 +6,7 @@
//
import Foundation
+import Combine
import Amplify
/// Represents different auth strategies supported by a client
@@ -64,19 +65,23 @@ public protocol AuthorizationTypeIterator {
}
/// AuthorizationTypeIterator for values of type `AWSAuthorizationType`
-public struct AWSAuthorizationTypeIterator: AuthorizationTypeIterator {
- public typealias AuthorizationType = AWSAuthorizationType
+public struct AWSAuthorizationTypeIterator: AuthorizationTypeIterator, Sequence, IteratorProtocol {
+ public typealias AuthorizationType = AmplifyAuthorizationType
- private var values: IndexingIterator<[AWSAuthorizationType]>
+ private var values: IndexingIterator<[AmplifyAuthorizationType]>
private var _count: Int
private var _position: Int
- public init(withValues values: [AWSAuthorizationType]) {
+ public init(withValues values: [AmplifyAuthorizationType]) {
self.values = values.makeIterator()
self._count = values.count
self._position = 0
}
+ public init(withValues values: [AmplifyAuthorizationType], valuesOnEmpty defaults: [AmplifyAuthorizationType]) {
+ self.init(withValues: values.isEmpty ? defaults : values)
+ }
+
public var count: Int {
_count
}
@@ -85,7 +90,7 @@ public struct AWSAuthorizationTypeIterator: AuthorizationTypeIterator {
_position < _count
}
- public mutating func next() -> AWSAuthorizationType? {
+ public mutating func next() -> AmplifyAuthorizationType? {
if let value = values.next() {
_position += 1
return value
@@ -107,12 +112,12 @@ public class AWSDefaultAuthModeStrategy: AuthModeStrategy {
public func authTypesFor(schema: ModelSchema,
operation: ModelOperation) -> AWSAuthorizationTypeIterator {
- return AWSAuthorizationTypeIterator(withValues: [])
+ return AWSAuthorizationTypeIterator(withValues: [.inferred])
}
public func authTypesFor(schema: ModelSchema,
operations: [ModelOperation]) -> AWSAuthorizationTypeIterator {
- return AWSAuthorizationTypeIterator(withValues: [])
+ return AWSAuthorizationTypeIterator(withValues: [.inferred])
}
}
@@ -127,20 +132,18 @@ public class AWSMultiAuthModeStrategy: AuthModeStrategy {
required public init() {}
private static func defaultAuthTypeFor(authStrategy: AuthStrategy) -> AWSAuthorizationType {
- var defaultAuthType: AWSAuthorizationType
switch authStrategy {
case .owner:
- defaultAuthType = .amazonCognitoUserPools
+ return .amazonCognitoUserPools
case .groups:
- defaultAuthType = .amazonCognitoUserPools
+ return .amazonCognitoUserPools
case .private:
- defaultAuthType = .amazonCognitoUserPools
+ return .amazonCognitoUserPools
case .public:
- defaultAuthType = .apiKey
+ return .apiKey
case .custom:
- defaultAuthType = .function
+ return .function
}
- return defaultAuthType
}
/// Given an auth rule, returns the corresponding AWSAuthorizationType
@@ -234,10 +237,12 @@ public class AWSMultiAuthModeStrategy: AuthModeStrategy {
return rule.allow == .public || rule.allow == .custom
}
}
- let applicableAuthTypes = sortedRules.map {
+
+ let applicableAuthTypes: [AmplifyAuthorizationType] = sortedRules.map {
AWSMultiAuthModeStrategy.authTypeFor(authRule: $0)
- }
- return AWSAuthorizationTypeIterator(withValues: applicableAuthTypes)
+ }.map { .designated($0) }
+
+ return AWSAuthorizationTypeIterator(withValues: applicableAuthTypes, valuesOnEmpty: [.inferred])
}
}
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift
index d0c279314c..b15b4b7d6c 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift
+++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthService.swift
@@ -7,16 +7,11 @@
import Foundation
import Amplify
-import AWSClientRuntime
public class AWSAuthService: AWSAuthServiceBehavior {
public init() {}
- public func getCredentialsProvider() -> CredentialsProviding {
- return AmplifyAWSCredentialsProvider()
- }
-
/// Retrieves the identity identifier for this authentication session from Cognito.
public func getIdentityID() async throws -> String {
let session = try await Amplify.Auth.fetchAuthSession()
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift
index 6c302cc928..1c23190588 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift
+++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AWSAuthServiceBehavior.swift
@@ -7,12 +7,9 @@
import Foundation
import Amplify
-import AWSClientRuntime
public protocol AWSAuthServiceBehavior: AnyObject {
- func getCredentialsProvider() -> CredentialsProviding
-
func getTokenClaims(tokenString: String) -> Result<[String: AnyObject], AuthError>
/// Retrieves the identity identifier of for the Auth service
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/AmplifyAuthorizationType.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AmplifyAuthorizationType.swift
new file mode 100644
index 0000000000..18a90e3106
--- /dev/null
+++ b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AmplifyAuthorizationType.swift
@@ -0,0 +1,29 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+
+import Foundation
+
+/// - Warning: Although this has `public` access, it is intended for internal use and should not be used directly
+/// by host applications. The behavior of this may change without warning.
+public enum AmplifyAuthorizationType {
+
+ /// Determine the authorization method based on the amplifyconfiguration.
+ case inferred
+
+ /// Specify the authentication method.
+ case designated(AWSAuthorizationType)
+
+ public var awsAuthType: AWSAuthorizationType? {
+ switch self {
+ case .inferred: return nil
+ case .designated(let authType): return authType
+ }
+ }
+}
+
+extension AmplifyAuthorizationType: Equatable { }
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthAWSCredentialsProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthAWSCredentialsProvider.swift
rename to AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthAWSCredentialsProvider.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthCognitoIdentityProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthCognitoIdentityProvider.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthCognitoIdentityProvider.swift
rename to AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthCognitoIdentityProvider.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthCognitoTokensProvider.swift b/AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthCognitoTokensProvider.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthCognitoTokensProvider.swift
rename to AmplifyPlugins/Core/AWSPluginsCore/Auth/AuthCognitoTokensProvider.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift
index ea5c29d342..a219bf3ad7 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift
+++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLDocument/SingleDirectiveGraphQLDocument.swift
@@ -8,12 +8,6 @@
import Amplify
import Foundation
-public enum GraphQLOperationType: String {
- case mutation
- case query
- case subscription
-}
-
public typealias GraphQLParameterName = String
/// Represents a single directive GraphQL document. Concrete types that conform to this protocol must
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift
index 304aac6add..8674e962cb 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift
+++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AuthModeStrategyTests.swift
@@ -17,8 +17,9 @@ class AuthModeStrategyTests: XCTestCase {
// Then: an empty iterator is returned
func testDefaultAuthModeShouldReturnAnEmptyIterator() {
let authMode = AWSDefaultAuthModeStrategy()
- let authTypesIterator = authMode.authTypesFor(schema: AnyModelTester.schema, operation: .create)
- XCTAssertEqual(authTypesIterator.count, 0)
+ var authTypesIterator = authMode.authTypesFor(schema: AnyModelTester.schema, operation: .create)
+ XCTAssertEqual(authTypesIterator.count, 1)
+ XCTAssertEqual(authTypesIterator.next(), .inferred)
}
// Given: multi-auth strategy and a model schema
@@ -28,8 +29,8 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelWithOwnerAndPublicAuth.schema, operation: .create)
XCTAssertEqual(authTypesIterator.count, 2)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
// Given: multi-auth strategy and a model schema without auth provider
@@ -39,8 +40,8 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelNoProvider.schema, operation: .read)
XCTAssertEqual(authTypesIterator.count, 2)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
// Given: multi-auth strategy and a model schema with 4 auth rules
@@ -50,10 +51,10 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelAllStrategies.schema, operation: .read)
XCTAssertEqual(authTypesIterator.count, 4)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .awsIAM)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .awsIAM)
}
// Given: multi-auth strategy and a model schema multiple public rules
@@ -63,10 +64,10 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelWithMultiplePublicRules.schema, operation: .read)
XCTAssertEqual(authTypesIterator.count, 4)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .openIDConnect)
- XCTAssertEqual(authTypesIterator.next(), .awsIAM)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .openIDConnect)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .awsIAM)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
// Given: multi-auth strategy and a model schema
@@ -77,8 +78,8 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelAllStrategies.schema, operation: .create)
XCTAssertEqual(authTypesIterator.count, 2)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
}
// Given: multi-auth strategy a model schema
@@ -92,7 +93,7 @@ class AuthModeStrategyTests: XCTestCase {
var authTypesIterator = await authMode.authTypesFor(schema: ModelWithOwnerAndPublicAuth.schema,
operation: .create)
XCTAssertEqual(authTypesIterator.count, 1)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
// Given: multi-auth model schema with a custom strategy
@@ -103,9 +104,9 @@ class AuthModeStrategyTests: XCTestCase {
var authTypesIterator = await authMode.authTypesFor(schema: ModelWithCustomStrategy.schema,
operation: .create)
XCTAssertEqual(authTypesIterator.count, 3)
- XCTAssertEqual(authTypesIterator.next(), .function)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .awsIAM)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .function)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .awsIAM)
}
// Given: multi-auth model schema with a custom strategy
@@ -119,8 +120,8 @@ class AuthModeStrategyTests: XCTestCase {
var authTypesIterator = await authMode.authTypesFor(schema: ModelWithCustomStrategy.schema,
operation: .create)
XCTAssertEqual(authTypesIterator.count, 2)
- XCTAssertEqual(authTypesIterator.next(), .function)
- XCTAssertEqual(authTypesIterator.next(), .awsIAM)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .function)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .awsIAM)
}
// Given: multi-auth strategy and a model schema without auth provider
@@ -130,8 +131,8 @@ class AuthModeStrategyTests: XCTestCase {
let authMode = AWSMultiAuthModeStrategy()
var authTypesIterator = await authMode.authTypesFor(schema: ModelNoProvider.schema, operations: [.read, .create])
XCTAssertEqual(authTypesIterator.count, 2)
- XCTAssertEqual(authTypesIterator.next(), .amazonCognitoUserPools)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .amazonCognitoUserPools)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
// Given: multi-auth strategy and a model schema with auth provider
@@ -143,7 +144,7 @@ class AuthModeStrategyTests: XCTestCase {
authMode.authDelegate = delegate
var authTypesIterator = await authMode.authTypesFor(schema: ModelNoProvider.schema, operations: [.read, .create])
XCTAssertEqual(authTypesIterator.count, 1)
- XCTAssertEqual(authTypesIterator.next(), .apiKey)
+ XCTAssertEqual(authTypesIterator.next()?.awsAuthType, .apiKey)
}
}
diff --git a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift
index d0d87929a9..7f11328ad1 100644
--- a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift
+++ b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSAuthService.swift
@@ -8,9 +8,9 @@
import ClientRuntime
import AWSClientRuntime
import Amplify
-import AWSPluginsCore
+import InternalAmplifyCredentials
-public class MockAWSAuthService: AWSAuthServiceBehavior {
+public class MockAWSAuthService: AWSAuthCredentialsProviderBehavior {
var interactions: [String] = []
var getIdentityIdError: AuthError?
diff --git a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift
index 090010b65a..abeb670efb 100644
--- a/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift
+++ b/AmplifyPlugins/Core/AWSPluginsTestCommon/MockAWSSignatureV4Signer.swift
@@ -8,6 +8,7 @@
import AWSPluginsCore
import ClientRuntime
import AWSClientRuntime
+import InternalAmplifyCredentials
import Foundation
class MockAWSSignatureV4Signer: AWSSignatureV4Signer {
diff --git a/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthCredentialsProviderBehavior.swift b/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthCredentialsProviderBehavior.swift
new file mode 100644
index 0000000000..6d6cfe9277
--- /dev/null
+++ b/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthCredentialsProviderBehavior.swift
@@ -0,0 +1,17 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+import Foundation
+import Amplify
+import AWSClientRuntime
+import AWSPluginsCore
+
+public protocol AWSAuthCredentialsProviderBehavior: AWSAuthServiceBehavior {
+ func getCredentialsProvider() -> CredentialsProviding
+}
+
+
diff --git a/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthService+CredentialsProvider.swift b/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthService+CredentialsProvider.swift
new file mode 100644
index 0000000000..ec8babe3d5
--- /dev/null
+++ b/AmplifyPlugins/Core/AmplifyCredentials/AWSAuthService+CredentialsProvider.swift
@@ -0,0 +1,17 @@
+//
+// Copyright Amazon.com Inc. or its affiliates.
+// All Rights Reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+import Foundation
+import Amplify
+import AWSClientRuntime
+import AWSPluginsCore
+
+extension AWSAuthService: AWSAuthCredentialsProviderBehavior {
+ public func getCredentialsProvider() -> AWSClientRuntime.CredentialsProviding {
+ return AmplifyAWSCredentialsProvider()
+ }
+}
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/AWSPluginExtension.swift b/AmplifyPlugins/Core/AmplifyCredentials/AWSPluginExtension.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/AWSPluginExtension.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/AWSPluginExtension.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift
similarity index 95%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift
index 1959aa58b5..63d1197a6d 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSCredentialsProvider.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSCredentialsProvider.swift
@@ -8,6 +8,7 @@
import Amplify
import AWSClientRuntime
import AwsCommonRuntimeKit
+import AWSPluginsCore
import Foundation
public class AmplifyAWSCredentialsProvider: AWSClientRuntime.CredentialsProviding {
@@ -24,7 +25,7 @@ public class AmplifyAWSCredentialsProvider: AWSClientRuntime.CredentialsProvidin
}
}
-extension AWSCredentials {
+extension AWSPluginsCore.AWSCredentials {
func toAWSSDKCredentials() -> AWSClientRuntime.AWSCredentials {
if let tempCredentials = self as? AWSTemporaryCredentials {
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSServiceConfiguration.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSServiceConfiguration.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift b/AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSSignatureV4Signer.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AmplifyAWSSignatureV4Signer.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/AmplifyAWSSignatureV4Signer.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthTokenProvider.swift b/AmplifyPlugins/Core/AmplifyCredentials/AuthTokenProvider.swift
similarity index 96%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthTokenProvider.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/AuthTokenProvider.swift
index 4c79d1d77b..39f1bd43f2 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/AuthTokenProvider.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentials/AuthTokenProvider.swift
@@ -7,6 +7,7 @@
import Foundation
import Amplify
+import AWSPluginsCore
public protocol AuthTokenProvider {
func getUserPoolAccessToken() async throws -> String
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/ClientRuntimeFoundationBridge.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/FoundationClientEngine.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngine.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/FoundationClientEngine.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngineError.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/FoundationClientEngineError.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/FoundationClientEngineError.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/FoundationClientEngineError.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/PluginClientEngine.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/PluginClientEngine.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/PluginClientEngine.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/PluginClientEngine.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/SdkHttpRequest+updatingUserAgent.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSettingClientEngine.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/UserAgentSettingClientEngine.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSettingClientEngine.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/UserAgentSettingClientEngine.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift b/AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/UserAgentSuffixAppender.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/Utils/CustomHttpClientEngine/UserAgentSuffixAppender.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/CustomHttpClientEngine/UserAgentSuffixAppender.swift
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift b/AmplifyPlugins/Core/AmplifyCredentials/IAMCredentialProvider.swift
similarity index 78%
rename from AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/IAMCredentialProvider.swift
index 3ceee7167e..1265a9130f 100644
--- a/AmplifyPlugins/Core/AWSPluginsCore/Auth/Provider/IAMCredentialProvider.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentials/IAMCredentialProvider.swift
@@ -8,15 +8,16 @@
import Foundation
import Amplify
import AWSClientRuntime
+import AWSPluginsCore
public protocol IAMCredentialsProvider {
func getCredentialsProvider() -> CredentialsProviding
}
public struct BasicIAMCredentialsProvider: IAMCredentialsProvider {
- let authService: AWSAuthServiceBehavior
+ let authService: AWSAuthCredentialsProviderBehavior
- public init(authService: AWSAuthServiceBehavior) {
+ public init(authService: AWSAuthCredentialsProviderBehavior) {
self.authService = authService
}
diff --git a/AmplifyPlugins/Core/AmplifyCredentials/Resources/PrivacyInfo.xcprivacy b/AmplifyPlugins/Core/AmplifyCredentials/Resources/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..74f8af8564
--- /dev/null
+++ b/AmplifyPlugins/Core/AmplifyCredentials/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,8 @@
+
+
+
+
+ NSPrivacyAccessedAPITypes
+
+
+
diff --git a/AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration+Platform.swift b/AmplifyPlugins/Core/AmplifyCredentials/ServiceConfiguration/AmplifyAWSServiceConfiguration+Platform.swift
similarity index 100%
rename from AmplifyPlugins/Core/AWSPluginsCore/ServiceConfiguration/AmplifyAWSServiceConfiguration+Platform.swift
rename to AmplifyPlugins/Core/AmplifyCredentials/ServiceConfiguration/AmplifyAWSServiceConfiguration+Platform.swift
diff --git a/AmplifyPlugins/Core/AmplifyCredentialsTests/AWSPluginsSDKCore.xctestplan b/AmplifyPlugins/Core/AmplifyCredentialsTests/AWSPluginsSDKCore.xctestplan
new file mode 100644
index 0000000000..8a0c4734d0
--- /dev/null
+++ b/AmplifyPlugins/Core/AmplifyCredentialsTests/AWSPluginsSDKCore.xctestplan
@@ -0,0 +1,24 @@
+{
+ "configurations" : [
+ {
+ "id" : "9D7C41C1-F847-4136-AB74-D1E17831BCDD",
+ "name" : "Configuration 1",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+
+ },
+ "testTargets" : [
+ {
+ "target" : {
+ "containerPath" : "container:",
+ "identifier" : "InternalAmplifyCredentialsTests",
+ "name" : "InternalAmplifyCredentialsTests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AWSAuthServiceTests.swift b/AmplifyPlugins/Core/AmplifyCredentialsTests/Auth/AWSAuthServiceTests.swift
similarity index 99%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AWSAuthServiceTests.swift
rename to AmplifyPlugins/Core/AmplifyCredentialsTests/Auth/AWSAuthServiceTests.swift
index 1dc1c19582..abca5ad99c 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Auth/AWSAuthServiceTests.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentialsTests/Auth/AWSAuthServiceTests.swift
@@ -9,6 +9,7 @@ import XCTest
@testable import Amplify
@testable import AWSPluginsCore
+@testable import InternalAmplifyCredentials
import AWSClientRuntime
class AWSAuthServiceTests: XCTestCase {
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift b/AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSettingClientEngineTests.swift
similarity index 99%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift
rename to AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSettingClientEngineTests.swift
index f395f6ef18..a6b2d3a800 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSettingClientEngineTests.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSettingClientEngineTests.swift
@@ -8,7 +8,7 @@
@_spi(InternalAmplifyPluginExtension)
@_spi(PluginHTTPClientEngine)
@_spi(InternalHttpEngineProxy)
-import AWSPluginsCore
+import InternalAmplifyCredentials
import ClientRuntime
import XCTest
diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift b/AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSuffixAppenderTests.swift
similarity index 98%
rename from AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift
rename to AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSuffixAppenderTests.swift
index 3b6b167a92..dece4394d4 100644
--- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Utils/UserAgentSuffixAppenderTests.swift
+++ b/AmplifyPlugins/Core/AmplifyCredentialsTests/Utils/UserAgentSuffixAppenderTests.swift
@@ -5,7 +5,7 @@
// SPDX-License-Identifier: Apache-2.0
//
-@_spi(InternalAmplifyPluginExtension) @_spi(InternalHttpEngineProxy) import AWSPluginsCore
+@_spi(InternalAmplifyPluginExtension) @_spi(InternalHttpEngineProxy) import InternalAmplifyCredentials
import ClientRuntime
import XCTest
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine+SyncRequirement.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine+SyncRequirement.swift
index 0b99894ea6..b6a8aac20c 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine+SyncRequirement.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine+SyncRequirement.swift
@@ -25,7 +25,7 @@ extension StorageEngine {
))
}
- guard let apiGraphQL = api as? APICategoryGraphQLBehaviorExtended else {
+ guard let apiGraphQL = api as? APICategoryGraphQLBehavior else {
log.info("Unable to find GraphQL API plugin for syncEngine. syncEngine will not be started")
return .failure(.configuration(
"Unable to find suitable GraphQL API plugin for syncEngine. syncEngine will not be started",
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift
index e01f235b88..b37a860eae 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOperation.swift
@@ -13,7 +13,7 @@ import Foundation
final class InitialSyncOperation: AsynchronousOperation {
typealias SyncQueryResult = PaginatedList
- private weak var api: APICategoryGraphQLBehaviorExtended?
+ private weak var api: APICategoryGraphQLBehavior?
private weak var reconciliationQueue: IncomingEventReconciliationQueue?
private weak var storageAdapter: StorageEngineAdapter?
private let dataStoreConfiguration: DataStoreConfiguration
@@ -22,6 +22,7 @@ final class InitialSyncOperation: AsynchronousOperation {
private let modelSchema: ModelSchema
private var recordsReceived: UInt
+ private var queryTask: Task?
private var syncMaxRecords: UInt {
return dataStoreConfiguration.syncMaxRecords
@@ -61,7 +62,7 @@ final class InitialSyncOperation: AsynchronousOperation {
}
init(modelSchema: ModelSchema,
- api: APICategoryGraphQLBehaviorExtended?,
+ api: APICategoryGraphQLBehavior?,
reconciliationQueue: IncomingEventReconciliationQueue?,
storageAdapter: StorageEngineAdapter?,
dataStoreConfiguration: DataStoreConfiguration,
@@ -86,7 +87,7 @@ final class InitialSyncOperation: AsynchronousOperation {
log.info("Beginning sync for \(modelSchema.name)")
let lastSyncMetadata = getLastSyncMetadata()
let lastSyncTime = getLastSyncTime(lastSyncMetadata)
- Task {
+ self.queryTask = Task {
await query(lastSyncTime: lastSyncTime)
}
}
@@ -168,42 +169,47 @@ final class InitialSyncOperation: AsynchronousOperation {
}
let minSyncPageSize = Int(min(syncMaxRecords - recordsReceived, syncPageSize))
let limit = minSyncPageSize < 0 ? Int(syncPageSize) : minSyncPageSize
- let completionListener: GraphQLOperation.ResultListener = { result in
- switch result {
- case .failure(let apiError):
- if self.isAuthSignedOutError(apiError: apiError) {
- self.log.error("Sync for \(self.modelSchema.name) failed due to signed out error \(apiError.errorDescription)")
- }
-
- // TODO: Retry query on error
- let error = DataStoreError.api(apiError)
- self.dataStoreConfiguration.errorHandler(error)
- self.finish(result: .failure(error))
- case .success(let graphQLResult):
- self.handleQueryResults(lastSyncTime: lastSyncTime, graphQLResult: graphQLResult)
+ let authTypes = await authModeStrategy.authTypesFor(schema: modelSchema, operation: .read)
+ let queryRequestsStream = AsyncStream { continuation in
+ for authType in authTypes {
+ continuation.yield({ [weak self] in
+ guard let self, let api = self.api else {
+ throw APIError.operationError(
+ "The initial synchronization process can no longer be accessed or referred to",
+ "The initial synchronization process may be cancelled or terminated"
+ )
+ }
+
+ return try await api.query(request: GraphQLRequest.syncQuery(
+ modelSchema: self.modelSchema,
+ where: self.syncPredicate,
+ limit: limit,
+ nextToken: nextToken,
+ lastSync: lastSyncTime,
+ authType: authType.awsAuthType
+ ))
+ })
}
+ continuation.finish()
+ }
+ switch await RetryableGraphQLOperation(requestStream: queryRequestsStream).run() {
+ case .success(let graphQLResult):
+ await handleQueryResults(lastSyncTime: lastSyncTime, graphQLResult: graphQLResult)
+ case .failure(let apiError):
+ if self.isAuthSignedOutError(apiError: apiError) {
+ self.log.error("Sync for \(self.modelSchema.name) failed due to signed out error \(apiError.errorDescription)")
+ }
+ self.dataStoreConfiguration.errorHandler(DataStoreError.api(apiError))
+ self.finish(result: .failure(.api(apiError)))
}
-
- var authTypes = await authModeStrategy.authTypesFor(schema: modelSchema, operation: .read)
-
- RetryableGraphQLOperation(requestFactory: {
- GraphQLRequest.syncQuery(modelSchema: self.modelSchema,
- where: self.syncPredicate,
- limit: limit,
- nextToken: nextToken,
- lastSync: lastSyncTime,
- authType: authTypes.next())
- },
- maxRetries: authTypes.count,
- resultListener: completionListener) { nextRequest, wrappedCompletionListener in
- api.query(request: nextRequest, listener: wrappedCompletionListener)
- }.main()
}
/// Disposes of the query results: Stops if error, reconciles results if success, and kick off a new query if there
/// is a next token
- private func handleQueryResults(lastSyncTime: Int64?,
- graphQLResult: Result>) {
+ private func handleQueryResults(
+ lastSyncTime: Int64?,
+ graphQLResult: Result>
+ ) async {
guard !isCancelled else {
finish(result: .successfulVoid)
return
@@ -238,9 +244,7 @@ final class InitialSyncOperation: AsynchronousOperation {
}
if let nextToken = syncQueryResult.nextToken, recordsReceived < syncMaxRecords {
- Task {
- await self.query(lastSyncTime: lastSyncTime, nextToken: nextToken)
- }
+ await self.query(lastSyncTime: lastSyncTime, nextToken: nextToken)
} else {
updateModelSyncMetadata(lastSyncTime: syncQueryResult.startedAt)
}
@@ -292,6 +296,9 @@ final class InitialSyncOperation: AsynchronousOperation {
super.finish()
}
+ override func cancel() {
+ self.queryTask?.cancel()
+ }
}
extension InitialSyncOperation: DefaultLogger {
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOrchestrator.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOrchestrator.swift
index dbfe953ab1..806b19a240 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOrchestrator.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/InitialSync/InitialSyncOrchestrator.swift
@@ -19,7 +19,7 @@ protocol InitialSyncOrchestrator {
typealias InitialSyncOrchestratorFactory =
(DataStoreConfiguration,
AuthModeStrategy,
- APICategoryGraphQLBehaviorExtended?,
+ APICategoryGraphQLBehavior?,
IncomingEventReconciliationQueue?,
StorageEngineAdapter?) -> InitialSyncOrchestrator
@@ -30,7 +30,7 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
private var initialSyncOperationSinks: [String: AnyCancellable]
private let dataStoreConfiguration: DataStoreConfiguration
- private weak var api: APICategoryGraphQLBehaviorExtended?
+ private weak var api: APICategoryGraphQLBehavior?
private weak var reconciliationQueue: IncomingEventReconciliationQueue?
private weak var storageAdapter: StorageEngineAdapter?
private let authModeStrategy: AuthModeStrategy
@@ -52,7 +52,7 @@ final class AWSInitialSyncOrchestrator: InitialSyncOrchestrator {
init(dataStoreConfiguration: DataStoreConfiguration,
authModeStrategy: AuthModeStrategy,
- api: APICategoryGraphQLBehaviorExtended?,
+ api: APICategoryGraphQLBehavior?,
reconciliationQueue: IncomingEventReconciliationQueue?,
storageAdapter: StorageEngineAdapter?) {
self.initialSyncOperationSinks = [:]
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+Action.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+Action.swift
index a9c8309ad6..f042cfab00 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+Action.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+Action.swift
@@ -15,7 +15,7 @@ extension OutgoingMutationQueue {
enum Action {
// Startup/config actions
case initialized
- case receivedStart(APICategoryGraphQLBehaviorExtended, MutationEventPublisher, IncomingEventReconciliationQueue?)
+ case receivedStart(APICategoryGraphQLBehavior, MutationEventPublisher, IncomingEventReconciliationQueue?)
case receivedSubscription
// Event loop
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+State.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+State.swift
index f7c59eb8ea..b8839a4b3e 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+State.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue+State.swift
@@ -16,7 +16,7 @@ extension OutgoingMutationQueue {
// Startup/config states
case notInitialized
case stopped
- case starting(APICategoryGraphQLBehaviorExtended, MutationEventPublisher, IncomingEventReconciliationQueue?)
+ case starting(APICategoryGraphQLBehavior, MutationEventPublisher, IncomingEventReconciliationQueue?)
// Event loop
case requestingEvent
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift
index d517b170af..e2840f7e47 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/OutgoingMutationQueue.swift
@@ -13,7 +13,7 @@ import AWSPluginsCore
/// Submits outgoing mutation events to the provisioned API
protocol OutgoingMutationQueueBehavior: AnyObject {
func stopSyncingToCloud(_ completion: @escaping BasicClosure)
- func startSyncingToCloud(api: APICategoryGraphQLBehaviorExtended,
+ func startSyncingToCloud(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?)
var publisher: AnyPublisher { get }
@@ -29,7 +29,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior {
/// A DispatchQueue for synchronizing state on the mutation queue
private let mutationDispatchQueue = TaskQueue()
- private weak var api: APICategoryGraphQLBehaviorExtended?
+ private weak var api: APICategoryGraphQLBehavior?
private weak var reconciliationQueue: IncomingEventReconciliationQueue?
private var subscription: Subscription?
@@ -81,7 +81,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior {
// MARK: - Public API
- func startSyncingToCloud(api: APICategoryGraphQLBehaviorExtended,
+ func startSyncingToCloud(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?) {
log.verbose(#function)
@@ -127,7 +127,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior {
/// Responder method for `starting`. Starts the operation queue and subscribes to
/// the publisher. After subscribing to the publisher, return actions:
/// - receivedSubscription
- private func doStart(api: APICategoryGraphQLBehaviorExtended,
+ private func doStart(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?) {
log.verbose(#function)
@@ -223,7 +223,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior {
private func processSyncMutationToCloudResult(_ result: GraphQLOperation>.OperationResult,
mutationEvent: MutationEvent,
- api: APICategoryGraphQLBehaviorExtended) {
+ api: APICategoryGraphQLBehavior) {
if case let .success(graphQLResponse) = result {
if case let .success(graphQLResult) = graphQLResponse {
processSuccessEvent(mutationEvent,
@@ -272,7 +272,7 @@ final class OutgoingMutationQueue: OutgoingMutationQueueBehavior {
}
private func processMutationErrorFromCloud(mutationEvent: MutationEvent,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
apiError: APIError?,
graphQLResponseError: GraphQLResponseError>?) {
if let apiError = apiError, apiError.isOperationCancelledError {
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift
index bbc4ec0895..c334745fb7 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/ProcessMutationErrorFromCloudOperation.swift
@@ -27,12 +27,12 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation {
private let apiError: APIError?
private let completion: (Result) -> Void
private var mutationOperation: AtomicValue>?>
- private weak var api: APICategoryGraphQLBehaviorExtended?
+ private weak var api: APICategoryGraphQLBehavior?
private weak var reconciliationQueue: IncomingEventReconciliationQueue?
init(dataStoreConfiguration: DataStoreConfiguration,
mutationEvent: MutationEvent,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
storageAdapter: StorageEngineAdapter,
graphQLResponseError: GraphQLResponseError>? = nil,
apiError: APIError? = nil,
@@ -296,44 +296,44 @@ class ProcessMutationErrorFromCloudOperation: AsynchronousOperation {
}
log.verbose("\(#function) sending mutation with data: \(apiRequest)")
- let graphQLOperation = api.mutate(request: apiRequest) { [weak self] result in
- guard let self = self, !self.isCancelled else {
- return
- }
+ Task { [weak self] in
+ do {
+ let result = try await api.mutate(request: apiRequest)
+ guard let self = self, !self.isCancelled else {
+ self?.finish(result: .failure(APIError.operationError("Mutation operation cancelled", "")))
+ return
+ }
- self.log.verbose("sendMutationToCloud received asyncEvent: \(result)")
- self.validate(cloudResult: result, request: apiRequest)
+ self.log.verbose("sendMutationToCloud received asyncEvent: \(result)")
+ self.validate(cloudResult: result, request: apiRequest)
+ } catch {
+ self?.finish(result: .failure(APIError.operationError("Failed to do mutation", "", error)))
+ }
}
- mutationOperation.set(graphQLOperation)
}
- private func validate(cloudResult: MutationSyncCloudResult, request: MutationSyncAPIRequest) {
+ private func validate(cloudResult: GraphQLResponse, request: MutationSyncAPIRequest) {
guard !isCancelled else {
return
}
- if case .failure(let error) = cloudResult {
- dataStoreConfiguration.errorHandler(error)
- }
-
- if case let .success(graphQLResponse) = cloudResult {
- if case .failure(let error) = graphQLResponse {
- dataStoreConfiguration.errorHandler(error)
- } else if case let .success(graphQLResult) = graphQLResponse {
- guard let reconciliationQueue = reconciliationQueue else {
- let dataStoreError = DataStoreError.configuration(
- "reconciliationQueue is unexpectedly nil",
- """
- The reference to reconciliationQueue has been released while an ongoing mutation was being processed.
- \(AmplifyErrorMessages.reportBugToAWS())
- """
- )
- finish(result: .failure(dataStoreError))
- return
- }
-
- reconciliationQueue.offer([graphQLResult], modelName: mutationEvent.modelName)
+ switch cloudResult {
+ case .success(let mutationSyncResult):
+ guard let reconciliationQueue = reconciliationQueue else {
+ let dataStoreError = DataStoreError.configuration(
+ "reconciliationQueue is unexpectedly nil",
+ """
+ The reference to reconciliationQueue has been released while an ongoing mutation was being processed.
+ \(AmplifyErrorMessages.reportBugToAWS())
+ """
+ )
+ finish(result: .failure(dataStoreError))
+ return
}
+
+ reconciliationQueue.offer([mutationSyncResult], modelName: mutationEvent.modelName)
+ case .failure(let graphQLResponseError):
+ dataStoreConfiguration.errorHandler(graphQLResponseError)
}
finish(result: .success(nil))
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift
index 3732bafb4a..40f9c04f06 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/MutationSync/OutgoingMutationQueue/SyncMutationToCloudOperation.swift
@@ -17,23 +17,21 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
typealias MutationSyncCloudResult = GraphQLOperation>.OperationResult
- private weak var api: APICategoryGraphQLBehaviorExtended?
+ private weak var api: APICategoryGraphQLBehavior?
private let mutationEvent: MutationEvent
private let getLatestSyncMetadata: () -> MutationSyncMetadata?
private let completion: GraphQLOperation>.ResultListener
private let requestRetryablePolicy: RequestRetryablePolicy
- private let lock: NSRecursiveLock
-
private var networkReachabilityPublisher: AnyPublisher?
- private var mutationOperation: GraphQLOperation>?
+ private var mutationOperation: Task?
private var mutationRetryNotifier: MutationRetryNotifier?
private var currentAttemptNumber: Int
private var authTypesIterator: AWSAuthorizationTypeIterator?
init(mutationEvent: MutationEvent,
getLatestSyncMetadata: @escaping () -> MutationSyncMetadata?,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
authModeStrategy: AuthModeStrategy,
networkReachabilityPublisher: AnyPublisher? = nil,
currentAttemptNumber: Int = 1,
@@ -46,7 +44,6 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
self.completion = completion
self.currentAttemptNumber = currentAttemptNumber
self.requestRetryablePolicy = requestRetryablePolicy ?? RequestRetryablePolicy()
- self.lock = NSRecursiveLock()
if let modelSchema = ModelRegistry.modelSchema(from: mutationEvent.modelName),
let mutationType = GraphQLMutationType(rawValue: mutationEvent.mutationType) {
@@ -61,22 +58,19 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
override func main() {
log.verbose(#function)
- sendMutationToCloud(withAuthType: authTypesIterator?.next())
+ sendMutationToCloud(withAuthType: authTypesIterator?.next()?.awsAuthType)
}
override func cancel() {
log.verbose(#function)
- lock.execute {
- mutationOperation?.cancel()
- mutationRetryNotifier?.cancel()
- mutationRetryNotifier = nil
- }
+ mutationOperation?.cancel()
+ mutationRetryNotifier?.cancel()
+ mutationRetryNotifier = nil
let apiError = APIError(error: OperationCancelledError())
finish(result: .failure(apiError))
}
- /// Does not require a locking context. Member access is read-only.
private func sendMutationToCloud(withAuthType authType: AWSAuthorizationType? = nil) {
guard !isCancelled else {
return
@@ -209,58 +203,66 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
return
}
log.verbose("\(#function) sending mutation with sync data: \(apiRequest)")
- lock.execute {
- mutationOperation = api.mutate(request: apiRequest) { [weak self] result in
- self?.respond(toCloudResult: result, withAPIRequest: apiRequest)
+
+ mutationOperation = Task { [weak self] in
+ let result: GraphQLResponse>
+ do {
+ result = try await api.mutate(request: apiRequest)
+ } catch {
+ result = .failure(.unknown("Failed to send sync mutation request", "", error))
}
+
+ self?.respond(
+ toCloudResult: result,
+ withAPIRequest: apiRequest
+ )
}
+
}
- /// Initiates a locking context
private func respond(
- toCloudResult result: GraphQLOperation>.OperationResult,
+ toCloudResult result: GraphQLResponse>,
withAPIRequest apiRequest: GraphQLRequest>
) {
- lock.execute {
- guard !self.isCancelled else {
- Amplify.log.debug("SyncMutationToCloudOperation cancelled, aborting")
- return
- }
-
- log.verbose("GraphQL mutation operation received result: \(result)")
- validate(cloudResult: result, request: apiRequest)
+ guard !self.isCancelled else {
+ Amplify.log.debug("SyncMutationToCloudOperation cancelled, aborting")
+ return
}
+
+ log.verbose("GraphQL mutation operation received result: \(result)")
+ validate(cloudResult: result, request: apiRequest)
}
- /// - Warning: Must be invoked from a locking context
- private func validate(cloudResult: MutationSyncCloudResult,
- request: GraphQLRequest>) {
- guard !isCancelled else {
+ private func validate(
+ cloudResult: GraphQLResponse>,
+ request: GraphQLRequest>
+ ) {
+ guard !isCancelled, let mutationOperation, !mutationOperation.isCancelled else {
return
}
- if case .failure(let error) = cloudResult {
- let advice = getRetryAdviceIfRetryable(error: error)
+ if case .failure(let error) = cloudResult,
+ let apiError = error.underlyingError as? APIError {
+ let advice = getRetryAdviceIfRetryable(error: apiError)
guard advice.shouldRetry else {
- finish(result: .failure(error))
+ finish(result: .failure(apiError))
return
}
resolveReachabilityPublisher(request: request)
if let pluginOptions = request.options?.pluginOptions as? AWSAPIPluginDataStoreOptions, pluginOptions.authType != nil,
let nextAuthType = authTypesIterator?.next() {
- scheduleRetry(advice: advice, withAuthType: nextAuthType)
+ scheduleRetry(advice: advice, withAuthType: nextAuthType.awsAuthType)
} else {
scheduleRetry(advice: advice)
}
return
}
- finish(result: cloudResult)
+ finish(result: .success(cloudResult))
}
- /// - Warning: Must be invoked from a locking context
private func resolveReachabilityPublisher(request: GraphQLRequest>) {
if networkReachabilityPublisher == nil {
if let reachability = api as? APICategoryReachabilityBehavior {
@@ -277,7 +279,6 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
}
}
- /// - Warning: Must be invoked from a locking context
func getRetryAdviceIfRetryable(error: APIError) -> RequestRetryAdvice {
var advice = RequestRetryAdvice(shouldRetry: false, retryInterval: DispatchTimeInterval.never)
@@ -319,13 +320,11 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
return advice
}
- /// - Warning: Must be invoked from a locking context
private func shouldRetryWithDifferentAuthType() -> RequestRetryAdvice {
let shouldRetry = authTypesIterator?.hasNext == true
return RequestRetryAdvice(shouldRetry: shouldRetry, retryInterval: .milliseconds(0))
}
- /// - Warning: Must be invoked from a locking context
private func scheduleRetry(advice: RequestRetryAdvice,
withAuthType authType: AWSAuthorizationType? = nil) {
log.verbose("\(#function) scheduling retry for mutation \(advice)")
@@ -338,23 +337,19 @@ class SyncMutationToCloudOperation: AsynchronousOperation {
currentAttemptNumber += 1
}
- /// Initiates a locking context
+
private func respondToMutationNotifierTriggered(withAuthType authType: AWSAuthorizationType?) {
log.verbose("\(#function) mutationRetryNotifier triggered")
- lock.execute {
- sendMutationToCloud(withAuthType: authType)
- mutationRetryNotifier = nil
- }
+ sendMutationToCloud(withAuthType: authType)
+ mutationRetryNotifier = nil
}
/// Cleans up operation resources, finalizes AsynchronousOperation states, and invokes `completion` with `result`
/// - Parameter result: The MutationSyncCloudResult to pass to `completion`
private func finish(result: MutationSyncCloudResult) {
log.verbose(#function)
- lock.execute {
- mutationOperation?.removeResultListener()
- mutationOperation = nil
- }
+ mutationOperation?.cancel()
+ mutationOperation = nil
DispatchQueue.global().async {
self.completion(result)
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+Action.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+Action.swift
index 7421637a54..7ace6cd086 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+Action.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+Action.swift
@@ -18,10 +18,10 @@ extension RemoteSyncEngine {
case pausedSubscriptions
case pausedMutationQueue(StorageEngineAdapter)
- case clearedStateOutgoingMutations(APICategoryGraphQLBehaviorExtended, StorageEngineAdapter)
+ case clearedStateOutgoingMutations(APICategoryGraphQLBehavior, StorageEngineAdapter)
case initializedSubscriptions
case performedInitialSync
- case activatedCloudSubscriptions(APICategoryGraphQLBehaviorExtended, MutationEventPublisher, IncomingEventReconciliationQueue?)
+ case activatedCloudSubscriptions(APICategoryGraphQLBehavior, MutationEventPublisher, IncomingEventReconciliationQueue?)
case activatedMutationQueue
case notifiedSyncStarted
case cleanedUp(AmplifyError)
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+State.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+State.swift
index d55c3fe5c3..a1ecebfbbb 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+State.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine+State.swift
@@ -18,10 +18,10 @@ extension RemoteSyncEngine {
case pausingSubscriptions
case pausingMutationQueue
case clearingStateOutgoingMutations(StorageEngineAdapter)
- case initializingSubscriptions(APICategoryGraphQLBehaviorExtended, StorageEngineAdapter)
+ case initializingSubscriptions(APICategoryGraphQLBehavior, StorageEngineAdapter)
case performingInitialSync
case activatingCloudSubscriptions
- case activatingMutationQueue(APICategoryGraphQLBehaviorExtended, MutationEventPublisher, IncomingEventReconciliationQueue?)
+ case activatingMutationQueue(APICategoryGraphQLBehavior, MutationEventPublisher, IncomingEventReconciliationQueue?)
case notifyingSyncStarted
case syncEngineActive
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine.swift
index 26bb453571..fd30c9ecae 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngine.swift
@@ -21,7 +21,7 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
private var authModeStrategy: AuthModeStrategy
// Assigned at `start`
- weak var api: APICategoryGraphQLBehaviorExtended?
+ weak var api: APICategoryGraphQLBehavior?
weak var auth: AuthCategoryBehavior?
// Assigned and released inside `performInitialQueries`, but we maintain a reference so we can `reset`
@@ -197,7 +197,7 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
}
// swiftlint:enable cyclomatic_complexity
- func start(api: APICategoryGraphQLBehaviorExtended, auth: AuthCategoryBehavior?) {
+ func start(api: APICategoryGraphQLBehavior, auth: AuthCategoryBehavior?) {
guard storageAdapter != nil else {
log.error(error: DataStoreError.nilStorageAdapter())
remoteSyncTopicPublisher.send(completion: .failure(DataStoreError.nilStorageAdapter()))
@@ -280,7 +280,7 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
}
}
- private func initializeSubscriptions(api: APICategoryGraphQLBehaviorExtended,
+ private func initializeSubscriptions(api: APICategoryGraphQLBehavior,
storageAdapter: StorageEngineAdapter) async {
log.debug("[InitializeSubscription] \(#function)")
let syncableModelSchemas = ModelRegistry.modelSchemas.filter { $0.isSyncable }
@@ -363,7 +363,7 @@ class RemoteSyncEngine: RemoteSyncEngineBehavior {
reconciliationQueue.start()
}
- private func startMutationQueue(api: APICategoryGraphQLBehaviorExtended,
+ private func startMutationQueue(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?) {
log.debug(#function)
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngineBehavior.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngineBehavior.swift
index ee5710ff21..765d72473f 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngineBehavior.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/RemoteSyncEngineBehavior.swift
@@ -41,7 +41,7 @@ protocol RemoteSyncEngineBehavior: AnyObject {
/// the updates in the Datastore
/// 1. Mutation processor drains messages off the queue in serial and sends to the service, invoking
/// any local callbacks on error if necessary
- func start(api: APICategoryGraphQLBehaviorExtended, auth: AuthCategoryBehavior?)
+ func start(api: APICategoryGraphQLBehavior, auth: AuthCategoryBehavior?)
func stop(completion: @escaping DataStoreCallback)
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift
index 7705120b4b..4a6d765aca 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingEventReconciliationQueue.swift
@@ -15,7 +15,7 @@ typealias DisableSubscriptions = () -> Bool
// Used for testing:
typealias IncomingEventReconciliationQueueFactory =
([ModelSchema],
- APICategoryGraphQLBehaviorExtended,
+ APICategoryGraphQLBehavior,
StorageEngineAdapter,
[DataStoreSyncExpression],
AuthCategoryBehavior?,
@@ -46,7 +46,7 @@ final class AWSIncomingEventReconciliationQueue: IncomingEventReconciliationQueu
private let modelSchemasCount: Int
init(modelSchemas: [ModelSchema],
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
storageAdapter: StorageEngineAdapter,
syncExpressions: [DataStoreSyncExpression],
auth: AuthCategoryBehavior? = nil,
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingSubscriptionEventPublisher.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingSubscriptionEventPublisher.swift
index 32365419fe..76c4b7dc9a 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingSubscriptionEventPublisher.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/AWSIncomingSubscriptionEventPublisher.swift
@@ -23,7 +23,7 @@ final class AWSIncomingSubscriptionEventPublisher: IncomingSubscriptionEventPubl
}
init(modelSchema: ModelSchema,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
modelPredicate: QueryPredicate?,
auth: AuthCategoryBehavior?,
authModeStrategy: AuthModeStrategy) async {
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift
index d5dae69b37..e9a89800dd 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventPublisher.swift
@@ -6,7 +6,7 @@
//
import Amplify
-import AWSPluginsCore
+@_spi(WebSocket) import AWSPluginsCore
import Combine
import Foundation
@@ -39,7 +39,8 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
return onCreateConnected && onUpdateConnected && onDeleteConnected
}
- private let incomingSubscriptionEvents: PassthroughSubject
+ private let incomingSubscriptionEvents = PassthroughSubject()
+ private var cancelables = Set()
private let awsAuthService: AWSAuthServiceBehavior
private let consistencyQueue: DispatchQueue
@@ -47,7 +48,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
private let modelName: ModelName
init(modelSchema: ModelSchema,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
modelPredicate: QueryPredicate?,
auth: AuthCategoryBehavior?,
authModeStrategy: AuthModeStrategy,
@@ -67,72 +68,83 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
connectionStatusQueue.maxConcurrentOperationCount = 1
connectionStatusQueue.isSuspended = false
- let incomingSubscriptionEvents = PassthroughSubject()
- self.incomingSubscriptionEvents = incomingSubscriptionEvents
self.awsAuthService = awsAuthService ?? AWSAuthService()
// onCreate operation
- let onCreateValueListener = onCreateValueListenerHandler(event:)
- let onCreateAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema,
- operations: [.create, .read])
- self.onCreateValueListener = onCreateValueListener
- self.onCreateOperation = RetryableGraphQLSubscriptionOperation(
- requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor(
- for: modelSchema,
- subscriptionType: .onCreate,
- api: api,
- auth: auth,
- awsAuthService: self.awsAuthService,
- authTypeProvider: onCreateAuthTypeProvider),
- maxRetries: onCreateAuthTypeProvider.count,
- resultListener: genericCompletionListenerHandler) { nextRequest, wrappedCompletion in
- api.subscribe(request: nextRequest,
- valueListener: onCreateValueListener,
- completionListener: wrappedCompletion)
- }
- onCreateOperation?.main()
+ self.onCreateValueListener = onCreateValueListenerHandler(event:)
+ self.onCreateOperation = await retryableOperation(
+ subscriptionType: .create,
+ modelSchema: modelSchema,
+ authModeStrategy: authModeStrategy,
+ auth: auth,
+ api: api
+ )
+ onCreateOperation?.subscribe()
+ .sink(receiveCompletion: genericCompletionListenerHandler(result:), receiveValue: onCreateValueListener!)
+ .store(in: &cancelables)
// onUpdate operation
- let onUpdateValueListener = onUpdateValueListenerHandler(event:)
- let onUpdateAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema,
- operations: [.update, .read])
- self.onUpdateValueListener = onUpdateValueListener
- self.onUpdateOperation = RetryableGraphQLSubscriptionOperation(
- requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor(
- for: modelSchema,
- subscriptionType: .onUpdate,
- api: api,
- auth: auth,
- awsAuthService: self.awsAuthService,
- authTypeProvider: onUpdateAuthTypeProvider),
- maxRetries: onUpdateAuthTypeProvider.count,
- resultListener: genericCompletionListenerHandler) { nextRequest, wrappedCompletion in
- api.subscribe(request: nextRequest,
- valueListener: onUpdateValueListener,
- completionListener: wrappedCompletion)
- }
- onUpdateOperation?.main()
+ self.onUpdateValueListener = onUpdateValueListenerHandler(event:)
+ self.onUpdateOperation = await retryableOperation(
+ subscriptionType: .update,
+ modelSchema: modelSchema,
+ authModeStrategy: authModeStrategy,
+ auth: auth,
+ api: api
+ )
+ onUpdateOperation?.subscribe()
+ .sink(receiveCompletion: genericCompletionListenerHandler(result:), receiveValue: onUpdateValueListener!)
+ .store(in: &cancelables)
// onDelete operation
- let onDeleteValueListener = onDeleteValueListenerHandler(event:)
- let onDeleteAuthTypeProvider = await authModeStrategy.authTypesFor(schema: modelSchema,
- operations: [.delete, .read])
- self.onDeleteValueListener = onDeleteValueListener
- self.onDeleteOperation = RetryableGraphQLSubscriptionOperation(
- requestFactory: IncomingAsyncSubscriptionEventPublisher.apiRequestFactoryFor(
- for: modelSchema,
- subscriptionType: .onDelete,
- api: api,
- auth: auth,
- awsAuthService: self.awsAuthService,
- authTypeProvider: onDeleteAuthTypeProvider),
- maxRetries: onUpdateAuthTypeProvider.count,
- resultListener: genericCompletionListenerHandler) { nextRequest, wrappedCompletion in
- api.subscribe(request: nextRequest,
- valueListener: onDeleteValueListener,
- completionListener: wrappedCompletion)
- }
- onDeleteOperation?.main()
+ self.onDeleteValueListener = onDeleteValueListenerHandler(event:)
+ self.onDeleteOperation = await retryableOperation(
+ subscriptionType: .delete,
+ modelSchema: modelSchema,
+ authModeStrategy: authModeStrategy,
+ auth: auth,
+ api: api
+ )
+ onDeleteOperation?.subscribe()
+ .sink(receiveCompletion: genericCompletionListenerHandler(result:), receiveValue: onDeleteValueListener!)
+ .store(in: &cancelables)
+ }
+
+
+ func retryableOperation(
+ subscriptionType: IncomingAsyncSubscriptionType,
+ modelSchema: ModelSchema,
+ authModeStrategy: AuthModeStrategy,
+ auth: AuthCategoryBehavior?,
+ api: APICategoryGraphQLBehavior
+ ) async -> RetryableGraphQLSubscriptionOperation {
+ let authTypeProvider = await authModeStrategy.authTypesFor(
+ schema: modelSchema,
+ operations: subscriptionType.operations
+ )
+
+ return RetryableGraphQLSubscriptionOperation(
+ requestStream: AsyncStream { continuation in
+ for authType in authTypeProvider {
+ continuation.yield({ [weak self] in
+ guard let self else {
+ throw APIError.operationError("GraphQL subscription cancelled", "")
+ }
+
+ return api.subscribe(request: await IncomingAsyncSubscriptionEventPublisher.makeAPIRequest(
+ for: modelSchema,
+ subscriptionType: subscriptionType.subscriptionType,
+ api: api,
+ auth: auth,
+ authType: authType.awsAuthType,
+ awsAuthService: self.awsAuthService
+ ))
+ })
+ }
+ continuation.finish()
+ }
+
+ )
}
func onCreateValueListenerHandler(event: Event) {
@@ -183,9 +195,9 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
}
}
- func genericCompletionListenerHandler(result: Result) {
+ func genericCompletionListenerHandler(result: Subscribers.Completion) {
switch result {
- case .success:
+ case .finished:
send(completion: .finished)
case .failure(let apiError):
log.verbose("[InitializeSubscription.1] API.subscribe failed for `\(modelName)` error: \(apiError.errorDescription)")
@@ -196,7 +208,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
static func makeAPIRequest(for modelSchema: ModelSchema,
subscriptionType: GraphQLSubscriptionType,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
auth: AuthCategoryBehavior?,
authType: AWSAuthorizationType?,
awsAuthService: AWSAuthServiceBehavior) async -> GraphQLRequest {
@@ -226,7 +238,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
return request
}
- static func hasOIDCAuthProviderAvailable(api: APICategoryGraphQLBehaviorExtended) -> AmplifyOIDCAuthProvider? {
+ static func hasOIDCAuthProviderAvailable(api: APICategoryGraphQLBehavior) -> AmplifyOIDCAuthProvider? {
if let apiPlugin = api as? APICategoryAuthProviderFactoryBehavior,
let oidcAuthProvider = apiPlugin.apiAuthProviderFactory().oidcAuthProvider() {
return oidcAuthProvider
@@ -254,7 +266,7 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
func cancel() {
consistencyQueue.sync {
- genericCompletionListenerHandler(result: .successfulVoid)
+ genericCompletionListenerHandler(result: .finished)
onCreateOperation?.cancel()
onCreateOperation = nil
@@ -287,30 +299,33 @@ final class IncomingAsyncSubscriptionEventPublisher: AmplifyCancellable {
onDeleteOperation = nil
onDeleteValueListener?(.connection(.disconnected))
- genericCompletionListenerHandler(result: .successfulVoid)
+ genericCompletionListenerHandler(result: .finished)
}
}
}
-// MARK: - IncomingAsyncSubscriptionEventPublisher + API request factory
-extension IncomingAsyncSubscriptionEventPublisher {
- static func apiRequestFactoryFor(for modelSchema: ModelSchema,
- subscriptionType: GraphQLSubscriptionType,
- api: APICategoryGraphQLBehaviorExtended,
- auth: AuthCategoryBehavior?,
- awsAuthService: AWSAuthServiceBehavior,
- authTypeProvider: AWSAuthorizationTypeIterator) -> RetryableGraphQLOperation.RequestFactory {
- var authTypes = authTypeProvider
- return {
- return await IncomingAsyncSubscriptionEventPublisher.makeAPIRequest(for: modelSchema,
- subscriptionType: subscriptionType,
- api: api,
- auth: auth,
- authType: authTypes.next(),
- awsAuthService: awsAuthService)
+enum IncomingAsyncSubscriptionType {
+ case create
+ case delete
+ case update
+
+ var operations: [ModelOperation] {
+ switch self {
+ case .create: return [.create, .read]
+ case .delete: return [.delete, .read]
+ case .update: return [.update, .read]
}
}
+
+ var subscriptionType: GraphQLSubscriptionType {
+ switch self {
+ case .create: return .onCreate
+ case .delete: return .onDelete
+ case .update: return .onUpdate
+ }
+ }
+
}
extension IncomingAsyncSubscriptionEventPublisher: DefaultLogger {
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventToAnyModelMapper.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventToAnyModelMapper.swift
index 54af20d333..2e1aef7248 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventToAnyModelMapper.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/IncomingAsyncSubscriptionEventToAnyModelMapper.swift
@@ -83,7 +83,7 @@ final class IncomingAsyncSubscriptionEventToAnyModelMapper: Subscriber, AmplifyC
case .success(let mutationSync):
modelsFromSubscription.send(.payload(mutationSync))
case .failure(let failure):
- log.error(error: failure)
+ log.error(failure.errorDescription)
}
}
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift
index 7eacedb029..03074d82e3 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/AWSModelReconciliationQueue.swift
@@ -14,7 +14,7 @@ import Foundation
typealias ModelReconciliationQueueFactory = (
ModelSchema,
StorageEngineAdapter,
- APICategoryGraphQLBehaviorExtended,
+ APICategoryGraphQLBehavior,
ReconcileAndSaveOperationQueue,
QueryPredicate?,
AuthCategoryBehavior?,
@@ -78,7 +78,7 @@ final class AWSModelReconciliationQueue: ModelReconciliationQueue {
init(modelSchema: ModelSchema,
storageAdapter: StorageEngineAdapter?,
- api: APICategoryGraphQLBehaviorExtended,
+ api: APICategoryGraphQLBehavior,
reconcileAndSaveQueue: ReconcileAndSaveOperationQueue,
modelPredicate: QueryPredicate?,
auth: AuthCategoryBehavior?,
diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift
index 474f76666e..7ba4449c50 100644
--- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift
+++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/SubscriptionSync/ReconcileAndLocalSave/ReconcileAndLocalSaveOperation.swift
@@ -337,7 +337,7 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
}
enum ApplyRemoteModelResult {
- case applied(RemoteModel)
+ case applied(RemoteModel, AppliedModel)
case dropped
}
@@ -363,7 +363,7 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
promise(.failure(dataStoreError))
}
case .success:
- promise(.success(.applied(remoteModel)))
+ promise(.success(.applied(remoteModel, remoteModel)))
}
}
}
@@ -387,14 +387,13 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
let anyModel: AnyModel
do {
anyModel = try savedModel.eraseToAnyModel()
+ let appliedModel = MutationSync(model: anyModel, syncMetadata: remoteModel.syncMetadata)
+ promise(.success(.applied(remoteModel, appliedModel)))
} catch {
let dataStoreError = DataStoreError(error: error)
self.notifyDropped(error: dataStoreError)
promise(.failure(dataStoreError))
- return
}
- let inProcessModel = MutationSync(model: anyModel, syncMetadata: remoteModel.syncMetadata)
- promise(.success(.applied(inProcessModel)))
}
}
}
@@ -417,21 +416,15 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
result: ApplyRemoteModelResult,
mutationType: MutationEvent.MutationType
) -> AnyPublisher {
- if case let .applied(inProcessModel) = result {
- return self.saveMetadata(storageAdapter: storageAdapter, remoteModel: inProcessModel, mutationType: mutationType)
- .handleEvents( receiveOutput: { syncMetadata in
- let appliedModel = MutationSync(model: inProcessModel.model, syncMetadata: syncMetadata)
- self.notify(savedModel: appliedModel, mutationType: mutationType)
- }, receiveCompletion: { completion in
- if case .failure(let error) = completion {
- self.notifyDropped(error: error)
- }
- })
- .map { _ in () }
+ switch result {
+ case .applied(let remoteModel, let appliedModel):
+ return self.saveMetadata(storageAdapter: storageAdapter, remoteModel: remoteModel, mutationType: mutationType)
+ .map { MutationSync(model: appliedModel.model, syncMetadata: $0) }
+ .map { [weak self] in self?.notify(appliedModel: $0, mutationType: mutationType) }
.eraseToAnyPublisher()
-
+ case .dropped:
+ return Just(()).setFailureType(to: DataStoreError.self).eraseToAnyPublisher()
}
- return Just(()).setFailureType(to: DataStoreError.self).eraseToAnyPublisher()
}
private func saveMetadata(
@@ -440,9 +433,17 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
mutationType: MutationEvent.MutationType
) -> Future {
Future { promise in
- storageAdapter.save(remoteModel.syncMetadata,
- condition: nil,
- eagerLoad: self.isEagerLoad) { result in
+ storageAdapter.save(
+ remoteModel.syncMetadata,
+ condition: nil,
+ eagerLoad: self.isEagerLoad
+ ) { result in
+ switch result {
+ case .failure(let error):
+ self.notifyDropped(error: error)
+ case .success:
+ self.notifyHub(remoteModel: remoteModel, mutationType: mutationType)
+ }
promise(result)
}
}
@@ -454,28 +455,46 @@ class ReconcileAndLocalSaveOperation: AsynchronousOperation {
}
}
- private func notify(savedModel: AppliedModel,
- mutationType: MutationEvent.MutationType) {
- let version = savedModel.syncMetadata.version
+ /// Inform the mutationEvents subscribers about the updated model,
+ /// which incorporates lazy loading information if applicable.
+ private func notify(appliedModel: AppliedModel, mutationType: MutationEvent.MutationType) {
+ guard let json = try? appliedModel.model.instance.toJSON() else {
+ log.error("Could not notify mutation event")
+ return
+ }
+
+ let modelIdentifier = appliedModel.model.instance.identifier(schema: modelSchema).stringValue
+ let mutationEvent = MutationEvent(modelId: modelIdentifier,
+ modelName: modelSchema.name,
+ json: json,
+ mutationType: mutationType,
+ version: appliedModel.syncMetadata.version)
+ mutationEventPublisher.send(.mutationEvent(mutationEvent))
+ }
+ /// Inform the remote mutationEvents to Hub event subscribers,
+ /// which only contains information received from AppSync server.
+ private func notifyHub(
+ remoteModel: RemoteModel,
+ mutationType: MutationEvent.MutationType
+ ) {
// TODO: Dispatch/notify error if we can't erase to any model? Would imply an error in JSON decoding,
// which shouldn't be possible this late in the process. Possibly notify global conflict/error handler?
- guard let json = try? savedModel.model.instance.toJSON() else {
- log.error("Could not notify mutation event")
+ guard let json = try? remoteModel.model.instance.toJSON() else {
+ log.error("Could not notify Hub mutation event")
return
}
- let modelIdentifier = savedModel.model.instance.identifier(schema: modelSchema).stringValue
+
+ let modelIdentifier = remoteModel.model.instance.identifier(schema: modelSchema).stringValue
let mutationEvent = MutationEvent(modelId: modelIdentifier,
modelName: modelSchema.name,
json: json,
mutationType: mutationType,
- version: version)
+ version: remoteModel.syncMetadata.version)
let payload = HubPayload(eventName: HubPayload.EventName.DataStore.syncReceived,
data: mutationEvent)
Amplify.Hub.dispatch(to: .dataStore, payload: payload)
-
- mutationEventPublisher.send(.mutationEvent(mutationEvent))
}
private func notifyFinished() {
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift
index 869b62d24d..c4bbc384dc 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/AWSDataStorePluginTests.swift
@@ -77,7 +77,8 @@ class AWSDataStorePluginTests: XCTestCase {
} catch {
XCTFail("DataStore configuration should not fail with nil configuration. \(error)")
}
- await fulfillment(of: [startExpectation], timeout: 1.0) }
+ await fulfillment(of: [startExpectation], timeout: 1)
+ }
func testStorageEngineStartsOnQuery() async throws {
let startExpectation = expectation(description: "Start Sync should be called with Query")
@@ -111,22 +112,22 @@ class AWSDataStorePluginTests: XCTestCase {
timeout: 1
)
}
-
+
func testStorageEngineStartsOnPluginStopStart() async throws {
let stopExpectation = expectation(description: "Stop plugin should be called")
stopExpectation.isInverted = true
let startExpectation = expectation(description: "Start Sync should be called")
var currCount = 0
let storageEngine = MockStorageEngineBehavior()
-
+
storageEngine.responders[.stopSync] = StopSyncResponder { _ in
stopExpectation.fulfill()
}
-
+
storageEngine.responders[.startSync] = StartSyncResponder { _ in
currCount = self.expect(startExpectation, currCount, 1)
}
-
+
let storageEngineBehaviorFactory: StorageEngineBehaviorFactory = {_, _, _, _, _, _ throws in
return storageEngine
}
@@ -136,11 +137,11 @@ class AWSDataStorePluginTests: XCTestCase {
dataStorePublisher: dataStorePublisher,
validAPIPluginKey: "MockAPICategoryPlugin",
validAuthPluginKey: "MockAuthCategoryPlugin")
-
+
do {
try plugin.configure(using: nil)
XCTAssertNil(plugin.storageEngine)
-
+
plugin.stop(completion: { _ in
plugin.start(completion: { _ in })
})
@@ -149,21 +150,21 @@ class AWSDataStorePluginTests: XCTestCase {
}
await fulfillment(of: [startExpectation, stopExpectation], timeout: 1.0)
}
-
+
func testStorageEngineStartsOnPluginClearStart() async throws {
let clearExpectation = expectation(description: "Clear should be called")
let startExpectation = expectation(description: "Start Sync should be called")
var currCount = 0
-
+
let storageEngine = MockStorageEngineBehavior()
storageEngine.responders[.clear] = ClearResponder { _ in
currCount = self.expect(clearExpectation, currCount, 1)
}
-
+
storageEngine.responders[.startSync] = StartSyncResponder { _ in
currCount = self.expect(startExpectation, currCount, 2)
}
-
+
let storageEngineBehaviorFactory: StorageEngineBehaviorFactory = {_, _, _, _, _, _ throws in
return storageEngine
}
@@ -173,11 +174,11 @@ class AWSDataStorePluginTests: XCTestCase {
dataStorePublisher: dataStorePublisher,
validAPIPluginKey: "MockAPICategoryPlugin",
validAuthPluginKey: "MockAuthCategoryPlugin")
-
+
do {
try plugin.configure(using: nil)
XCTAssertNil(plugin.storageEngine)
-
+
plugin.clear(completion: { _ in
plugin.start(completion: { _ in })
})
@@ -251,7 +252,8 @@ class AWSDataStorePluginTests: XCTestCase {
XCTAssertNotNil(plugin.storageEngine)
XCTAssertNotNil(plugin.dataStorePublisher)
})
- await fulfillment(of: [startExpectation, stopExpectation, startExpectationOnSecondStart],
+ await fulfillment(
+ of: [startExpectation, stopExpectation, startExpectationOnSecondStart],
timeout: 1,
enforceOrder: true
)
@@ -326,7 +328,8 @@ class AWSDataStorePluginTests: XCTestCase {
XCTAssertNotNil(plugin.dataStorePublisher)
})
- await fulfillment(of: [startExpectation, clearExpectation, startExpectationOnSecondStart],
+ await fulfillment(
+ of: [startExpectation, clearExpectation, startExpectationOnSecondStart],
timeout: 1,
enforceOrder: true
)
@@ -402,7 +405,12 @@ class AWSDataStorePluginTests: XCTestCase {
XCTAssertNotNil(plugin.storageEngine)
XCTAssertNotNil(plugin.dataStorePublisher)
})
- await fulfillment(of: [startExpectation, clearExpectation, startExpectationOnQuery, finishNotReceived], timeout: 1.0)
+ await fulfillment(of: [
+ startExpectation,
+ clearExpectation,
+ startExpectationOnQuery,
+ finishNotReceived
+ ], timeout: 1)
sink.cancel()
} catch {
XCTFail("DataStore configuration should not fail with nil configuration. \(error)")
@@ -491,7 +499,12 @@ class AWSDataStorePluginTests: XCTestCase {
modelSchema: mockModel.schema,
mutationType: .create))
- await fulfillment(of: [startExpectation, clearExpectation, publisherReceivedValue, finishNotReceived], timeout: 1)
+ await fulfillment(of: [
+ startExpectation,
+ clearExpectation,
+ finishNotReceived,
+ publisherReceivedValue
+ ], timeout: 1)
sink.cancel()
} catch {
XCTFail("DataStore configuration should not fail with nil configuration. \(error)")
@@ -563,9 +576,10 @@ class AWSDataStorePluginTests: XCTestCase {
XCTAssertNotNil(plugin.dataStorePublisher)
stopCompleted.fulfill()
})
- await fulfillment(of: [stopCompleted], timeout: 1.0)
+ await fulfillment(of: [stopCompleted], timeout: 1.0)
- await fulfillment(of: [startExpectation, stopExpectation],
+ await fulfillment(
+ of: [startExpectation, stopExpectation],
timeout: 1,
enforceOrder: true
)
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift
index d71082ff45..7db19c714d 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift
@@ -76,7 +76,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
/// - the `save(post)` is called
/// - Then:
/// - call `query(Post)` to check if the model was correctly inserted
- func testInsertPost() {
+ func testInsertPost() async {
let expectation = self.expectation(
description: "it should save and select a Post from the database")
@@ -115,7 +115,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
expectation.fulfill()
}
}
- wait(for: [expectation], timeout: 5)
+ await fulfillment(of: [expectation], timeout: 5)
}
/// - Given: a list a `Post` instance
@@ -124,7 +124,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
/// - Then:
/// - call `query(Post, where: title == post.title)` to check
/// if the model was correctly inserted using a predicate
- func testInsertPostAndSelectByTitle() {
+ func testInsertPostAndSelectByTitle() async {
let expectation = self.expectation(
description: "it should save and select a Post from the database")
@@ -163,7 +163,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
}
}
- wait(for: [expectation], timeout: 5)
+ await fulfillment(of: [expectation], timeout: 5)
}
/// - Given: a list a `Post` instance
@@ -173,7 +173,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
/// - call `save(post)` again with an updated title
/// - check if the `query(Post)` returns only 1 post
/// - the post has the updated title
- func testInsertPostAndThenUpdateIt() {
+ func testInsertPostAndThenUpdateIt() async {
let expectation = self.expectation(
description: "it should insert and update a Post")
@@ -224,7 +224,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
}
}
- wait(for: [expectation], timeout: 5)
+ await fulfillment(of: [expectation], timeout: 5)
}
/// - Given: a list a `Post` instance
@@ -233,7 +233,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
/// - Then:
/// - call `delete(Post, id)` and check if `query(Post)` is empty
/// - check if `storageAdapter.exists(Post, id)` returns `false`
- func testInsertPostAndThenDeleteIt() {
+ func testInsertPostAndThenDeleteIt() async {
let saveExpectation = expectation(description: "Saved")
let deleteExpectation = expectation(description: "Deleted")
let queryExpectation = expectation(description: "Queried")
@@ -270,7 +270,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase {
}
}
- wait(for: [saveExpectation, deleteExpectation, queryExpectation], timeout: 2)
+ await fulfillment(of: [saveExpectation, deleteExpectation, queryExpectation], timeout: 2)
}
/// - Given: A Post instance
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationSyncExpressionTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationSyncExpressionTests.swift
index 298fcdb8b9..e118693987 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationSyncExpressionTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationSyncExpressionTests.swift
@@ -15,7 +15,7 @@ import Combine
@testable import AWSPluginsCore
class InitialSyncOperationSyncExpressionTests: XCTestCase {
- typealias APIPluginQueryResponder = QueryRequestListenerResponder>
+ typealias APIPluginQueryResponder = QueryRequestResponder>
var storageAdapter: StorageEngineAdapter!
var apiPlugin = MockAPICategoryPlugin()
@@ -36,13 +36,13 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
func initialSyncOperation(withSyncExpression syncExpression: DataStoreSyncExpression,
responder: APIPluginQueryResponder) -> InitialSyncOperation {
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
#if os(watchOS)
- let configuration = DataStoreConfiguration.custom(syncPageSize: 10,
+ let configuration = DataStoreConfiguration.custom(syncPageSize: 10,
syncExpressions: [syncExpression],
disableSubscriptions: { false })
#else
- let configuration = DataStoreConfiguration.custom(syncPageSize: 10,
+ let configuration = DataStoreConfiguration.custom(syncPageSize: 10,
syncExpressions: [syncExpression])
#endif
return InitialSyncOperation(
@@ -55,7 +55,7 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
}
func testBaseQueryWithBasicSyncExpression() async throws {
- let responder = APIPluginQueryResponder { request, listener in
+ let responder = APIPluginQueryResponder { request in
XCTAssertEqual(request.document, """
query SyncMockSynceds($filter: ModelMockSyncedFilterInput, $limit: Int) {
syncMockSynceds(filter: $filter, limit: $limit) {
@@ -73,28 +73,26 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
""")
guard let filter = request.variables?["filter"] as? [String: Any?] else {
XCTFail("Unable to get filter")
- return nil
+ return .failure(.unknown("Unable to get filter", "", nil))
}
guard let group = filter["and"] as? [[String: Any?]] else {
XCTFail("Unable to find 'and' group")
- return nil
+ return .failure(.unknown("Unable to find 'and' group", "", nil))
}
guard let key = group[0]["id"] as? [String: Any?] else {
XCTFail("Unable to get id from filter")
- return nil
+ return .failure(.unknown("Unable to get id from filter", "", nil))
}
guard let value = key["eq"] as? String else {
XCTFail("Unable to get eq from key")
- return nil
+ return .failure(.unknown("Unable to get eq from key", "", nil))
}
XCTAssertEqual(value, "id-123")
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
self.apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let syncExpression = DataStoreSyncExpression.syncExpression(MockSynced.schema, where: {
@@ -127,7 +125,7 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
}
func testBaseQueryWithFilterSyncExpression() async throws {
- let responder = APIPluginQueryResponder { request, listener in
+ let responder = APIPluginQueryResponder { request in
XCTAssertEqual(request.document, """
query SyncMockSynceds($filter: ModelMockSyncedFilterInput, $limit: Int) {
syncMockSynceds(filter: $filter, limit: $limit) {
@@ -145,28 +143,26 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
""")
guard let filter = request.variables?["filter"] as? [String: Any?] else {
XCTFail("Unable to get filter")
- return nil
+ return .failure(.unknown("Unable to get filter", "", nil))
}
guard let group = filter["or"] as? [[String: Any?]] else {
XCTFail("Unable to find 'or' group")
- return nil
+ return .failure(.unknown("Unable to find 'or' group", "", nil))
}
guard let key = group[0]["id"] as? [String: Any?] else {
XCTFail("Unable to get id from filter")
- return nil
+ return .failure(.unknown("Unable to get id from filter", "", nil))
}
guard let value = key["eq"] as? String else {
XCTFail("Unable to get eq from key")
- return nil
+ return .failure(.unknown("Unable to get eq from key", "", nil))
}
XCTAssertEqual(value, "id-123")
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
self.apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let syncExpression = DataStoreSyncExpression.syncExpression(MockSynced.schema, where: {
@@ -199,7 +195,7 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
}
func testBaseQueryWithSyncExpressionConstantAll() async throws {
- let responder = APIPluginQueryResponder { request, listener in
+ let responder = APIPluginQueryResponder { request in
XCTAssertEqual(request.document, """
query SyncMockSynceds($limit: Int) {
syncMockSynceds(limit: $limit) {
@@ -218,10 +214,8 @@ class InitialSyncOperationSyncExpressionTests: XCTestCase {
XCTAssertNil(request.variables?["filter"])
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
self.apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let syncExpression = DataStoreSyncExpression.syncExpression(MockSynced.schema, where: {
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift
index bea2d03aeb..07cdd95ecb 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift
@@ -247,16 +247,14 @@ class InitialSyncOperationTests: XCTestCase {
/// - Then:
/// - It reads sync metadata from storage
func testReadsMetadata() async {
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000
let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
let metadataQueryReceived = expectation(description: "Metadata query received by storage adapter")
@@ -308,17 +306,15 @@ class InitialSyncOperationTests: XCTestCase {
/// - It performs a sync query against the API category
func testQueriesAPI() async {
let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel")
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000
let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -366,16 +362,14 @@ class InitialSyncOperationTests: XCTestCase {
/// - Then:
/// - The method invokes a completion callback when complete
func testInvokesPublisherCompletion() async {
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000
let list = PaginatedList(items: [], nextToken: nil, startedAt: startDateMilliseconds)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -421,18 +415,16 @@ class InitialSyncOperationTests: XCTestCase {
var nextTokens = ["token1", "token2"]
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startedAt = Int64(Date().timeIntervalSince1970)
let nextToken = nextTokens.isEmpty ? nil : nextTokens.removeFirst()
let list = PaginatedList(items: [], nextToken: nextToken, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -482,15 +474,13 @@ class InitialSyncOperationTests: XCTestCase {
lastChangedAt: Int64(Date().timeIntervalSince1970),
version: 1)
let mutationSync = MutationSync(model: anyModel, syncMetadata: metadata)
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let list = PaginatedList(items: [mutationSync], nextToken: nil, startedAt: startedAtMilliseconds)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -551,16 +541,14 @@ class InitialSyncOperationTests: XCTestCase {
/// - The method submits the returned data to the reconciliation queue
func testUpdatesSyncMetadata() async throws {
let startDateMilliseconds = Int64(Date().timeIntervalSince1970) * 1_000
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startedAt = startDateMilliseconds
let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = try SQLiteStorageEngineAdapter(connection: Connection(.inMemory))
try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas + [MockSynced.schema])
@@ -615,22 +603,20 @@ class InitialSyncOperationTests: XCTestCase {
/// - Then:
/// - The method completes with a failure result, error handler is called.
func testQueriesAPIReturnSignedOutError() async throws {
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let authError = AuthError.signedOut("", "", nil)
let apiError = APIError.operationError("", "", authError)
- let event: GraphQLOperation>.OperationResult = .failure(apiError)
- listener?(event)
- return nil
+ throw apiError
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = try SQLiteStorageEngineAdapter(connection: Connection(.inMemory))
let reconciliationQueue = MockReconciliationQueue()
let expectErrorHandlerCalled = expectation(description: "Expect error handler called")
-
+
#if os(watchOS)
let configuration = DataStoreConfiguration.custom(errorHandler: { error in
guard let dataStoreError = error as? DataStoreError,
@@ -704,7 +690,12 @@ class InitialSyncOperationTests: XCTestCase {
operation.main()
- await fulfillment(of: [syncStartedReceived, syncCompletionReceived, finishedReceived, expectErrorHandlerCalled], timeout: 1)
+ await fulfillment(of: [
+ expectErrorHandlerCalled,
+ syncStartedReceived,
+ syncCompletionReceived,
+ finishedReceived
+ ], timeout: 1)
sink.cancel()
}
@@ -734,19 +725,17 @@ class InitialSyncOperationTests: XCTestCase {
await fulfillment(of: [syncMetadataSaved], timeout: 1)
let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel")
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
let lastSync = request.variables?["lastSync"] as? Int64
XCTAssertEqual(lastSync, startDateMilliseconds)
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let reconciliationQueue = MockReconciliationQueue()
let operation = InitialSyncOperation(
@@ -805,19 +794,17 @@ class InitialSyncOperationTests: XCTestCase {
wait(for: [syncMetadataSaved], timeout: 1.0)
let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel")
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
let lastSync = request.variables?["lastSync"] as? Int
XCTAssertNil(lastSync)
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let reconciliationQueue = MockReconciliationQueue()
#if os(watchOS)
@@ -866,7 +853,7 @@ class InitialSyncOperationTests: XCTestCase {
try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas + [MockSynced.schema])
let apiWasQueried = expectation(description: "API was queried for a PaginatedList of AnyModel")
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
let lastSync = request.variables?["lastSync"] as? Int
XCTAssertNil(lastSync)
XCTAssert(request.document.contains("limit: Int"))
@@ -874,14 +861,12 @@ class InitialSyncOperationTests: XCTestCase {
XCTAssertEqual(10, limitValue)
let list = PaginatedList(items: [], nextToken: nil, startedAt: nil)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
apiWasQueried.fulfill()
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let reconciliationQueue = MockReconciliationQueue()
#if os(watchOS)
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift
index 7e62bb0743..1e4a7ca208 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOrchestratorTests.swift
@@ -27,16 +27,14 @@ class InitialSyncOrchestratorTests: XCTestCase {
func testInvokesCompletionCallback() async throws {
ModelRegistry.reset()
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startedAt = Int64(Date().timeIntervalSince1970)
let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -120,23 +118,19 @@ class InitialSyncOrchestratorTests: XCTestCase {
func testFinishWithAPIError() async throws {
ModelRegistry.reset()
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
if request.document.contains("SyncPosts") {
- let event: GraphQLOperation>.OperationResult =
- .failure(APIError.operationError("", "", nil))
- listener?(event)
+ throw APIError.operationError("", "", nil)
} else if request.document.contains("SyncComments") {
let startedAt = Int64(Date().timeIntervalSince1970)
let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
+ return .success(list)
}
-
- return nil
+ return .failure(.unknown("", "", nil))
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -238,16 +232,14 @@ class InitialSyncOrchestratorTests: XCTestCase {
}
TestModelsWithNoAssociations().registerModels(registry: ModelRegistry.self)
- let responder = QueryRequestListenerResponder> { _, listener in
+ let responder = QueryRequestResponder> { _ in
let startedAt = Int64(Date().timeIntervalSince1970)
let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -297,7 +289,7 @@ class InitialSyncOrchestratorTests: XCTestCase {
PostCommentModelRegistration().registerModels(registry: ModelRegistry.self)
let postWasQueried = expectation(description: "Post was queried")
let commentWasQueried = expectation(description: "Comment was queried")
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
if request.document.hasPrefix("query SyncPosts") {
postWasQueried.fulfill()
}
@@ -308,13 +300,11 @@ class InitialSyncOrchestratorTests: XCTestCase {
let startedAt = Int64(Date().timeIntervalSince1970)
let list = PaginatedList(items: [], nextToken: nil, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
@@ -371,7 +361,7 @@ class InitialSyncOrchestratorTests: XCTestCase {
var nextTokens = Array(repeating: "token", count: pageCount - 1)
- let responder = QueryRequestListenerResponder> { request, listener in
+ let responder = QueryRequestResponder> { request in
if request.document.hasPrefix("query SyncPosts") {
postWasQueried.fulfill()
}
@@ -383,13 +373,11 @@ class InitialSyncOrchestratorTests: XCTestCase {
let startedAt = Int64(Date().timeIntervalSince1970)
let nextToken = nextTokens.isEmpty ? nil : nextTokens.removeFirst()
let list = PaginatedList(items: [], nextToken: nextToken, startedAt: startedAt)
- let event: GraphQLOperation>.OperationResult = .success(.success(list))
- listener?(event)
- return nil
+ return .success(list)
}
let apiPlugin = MockAPICategoryPlugin()
- apiPlugin.responders[.queryRequestListener] = responder
+ apiPlugin.responders[.queryRequestResponse] = responder
let storageAdapter = MockSQLiteStorageEngineAdapter()
storageAdapter.returnOnQueryModelSyncMetadata(nil)
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift
index 035918e8bf..63f1acd748 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueNetworkTests.swift
@@ -113,7 +113,7 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
)
// Start by accepting the initial "create" mutation
- apiPlugin.responders = [.mutateRequestListener: acceptInitialMutation]
+ apiPlugin.responders = [.mutateRequestResponse: acceptInitialMutation]
try await startAmplifyAndWaitForSync()
@@ -129,7 +129,7 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
// Set the responder to reject the mutation. Make sure to push a retry advice before sending
// a new mutation.
- apiPlugin.responders = [.mutateRequestListener: rejectMutationsWithRetriableError]
+ apiPlugin.responders = [.mutateRequestResponse: rejectMutationsWithRetriableError]
// NOTE: This policy is not used by the SyncMutationToCloudOperation, only by the
// RemoteSyncEngine.
@@ -248,7 +248,7 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
fulfillingWhenNetworkAvailableAgain: networkAvailableAgain
)
- apiPlugin.responders = [.mutateRequestListener: acceptSubsequentMutations]
+ apiPlugin.responders = [.mutateRequestResponse: acceptSubsequentMutations]
reachabilitySubject.send(ReachabilityUpdate(isOnline: true))
await fulfillment(of: [networkAvailableAgain, syncStarted, expectedFinalContentReceived, outboxEmpty], timeout: 5.0)
@@ -260,8 +260,8 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
for model: AnyModel,
fulfilling expectation: XCTestExpectation,
incrementing version: AtomicValue
- ) -> MutateRequestListenerResponder> {
- MutateRequestListenerResponder> { _, eventListener in
+ ) -> MutateRequestResponder> {
+ MutateRequestResponder> { _ in
let mockResponse = MutationSync(
model: model,
syncMetadata: MutationSyncMetadata(
@@ -273,24 +273,19 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
)
)
- DispatchQueue.global().async {
- eventListener?(.success(.success(mockResponse)))
- expectation.fulfill()
- }
-
- return nil
+ try! await Task.sleep(seconds: 0.01)
+ expectation.fulfill()
+ return .success(mockResponse)
}
}
/// Returns a responder that executes the eventListener after a delay, to simulate network lag
private func setUpRetriableErrorRequestResponder(
listenerDelay: TimeInterval
- ) -> MutateRequestListenerResponder> {
- MutateRequestListenerResponder> { _, eventListener in
- DispatchQueue.global().asyncAfter(deadline: .now() + listenerDelay) {
- eventListener?(.failure(self.connectionError))
- }
- return nil
+ ) -> MutateRequestResponder> {
+ MutateRequestResponder> { _ in
+ try? await Task.sleep(seconds: listenerDelay)
+ return .failure(.unknown("", "", self.connectionError))
}
}
@@ -299,12 +294,12 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
fulfilling expectation: XCTestExpectation,
whenContentContains expectedFinalContent: String,
incrementing version: AtomicValue
- ) -> MutateRequestListenerResponder> {
- MutateRequestListenerResponder> { request, eventListener in
+ ) -> MutateRequestResponder> {
+ MutateRequestResponder> { request in
guard let input = request.variables?["input"] as? [String: Any],
let content = input["content"] as? String else {
XCTFail("Unexpected request structure: no `content` in variables.")
- return nil
+ return .failure(.unknown("Unexpected request structure: no `content` in variables.", "", nil))
}
let mockResponse = MutationSync(
@@ -317,14 +312,12 @@ class OutgoingMutationQueueNetworkTests: SyncEngineTestBase {
version: version.increment()
)
)
-
- eventListener?(.success(.success(mockResponse)))
-
+
if content == expectedFinalContent {
expectation.fulfill()
}
- return nil
+ return .success(mockResponse)
}
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift
index 41f3244351..8802e832ff 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTests.swift
@@ -24,50 +24,57 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
await tryOrFail {
try setUpStorageAdapter()
- try setUpDataStore(mutationQueue: OutgoingMutationQueue(storageAdapter: storageAdapter,
- dataStoreConfiguration: .testDefault(),
- authModeStrategy: AWSDefaultAuthModeStrategy()))
+ try setUpDataStore(
+ mutationQueue: OutgoingMutationQueue(
+ storageAdapter: storageAdapter,
+ dataStoreConfiguration: .testDefault(),
+ authModeStrategy: AWSDefaultAuthModeStrategy()
+ )
+ )
}
- let post = Post(title: "Post title",
- content: "Post content",
- createdAt: .now())
+ let post = Post(title: "Post title", content: "Post content", createdAt: .now())
let outboxStatusReceivedCurrentCount = AtomicValue(initialValue: 0)
let outboxStatusOnStart = expectation(description: "On DataStore start, outboxStatus received")
let outboxStatusOnMutationEnqueued = expectation(description: "Mutation enqueued, outboxStatus received")
let outboxMutationEnqueued = expectation(description: "Mutation enqueued, outboxMutationEnqueued received")
- let outboxStatusFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.outboxStatus)
- let outboxMutationEnqueuedFilter = HubFilters.forEventName(HubPayload.EventName.DataStore.outboxMutationEnqueued)
- let filters = HubFilters.any(filters: outboxStatusFilter, outboxMutationEnqueuedFilter)
- let hubListener = Amplify.Hub.listen(to: .dataStore, isIncluded: filters) { payload in
- if payload.eventName == HubPayload.EventName.DataStore.outboxStatus {
- _ = outboxStatusReceivedCurrentCount.increment(by: 1)
- guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else {
- XCTFail("Failed to cast payload data as OutboxStatusEvent")
- return
- }
+ let hubListener0 = Amplify.Hub.listen(to: .dataStore, eventName: HubPayload.EventName.DataStore.outboxStatus) { payload in
+ defer { _ = outboxStatusReceivedCurrentCount.increment(by: 1) }
+ guard let outboxStatusEvent = payload.data as? OutboxStatusEvent else {
+ XCTFail("Failed to cast payload data as OutboxStatusEvent")
+ return
+ }
- if outboxStatusReceivedCurrentCount.get() == 1 {
- XCTAssertTrue(outboxStatusEvent.isEmpty)
- outboxStatusOnStart.fulfill()
- } else {
- XCTAssertFalse(outboxStatusEvent.isEmpty)
- outboxStatusOnMutationEnqueued.fulfill()
- }
+ switch outboxStatusReceivedCurrentCount.get() {
+ case 0:
+ XCTAssertTrue(outboxStatusEvent.isEmpty)
+ outboxStatusOnStart.fulfill()
+ case 1:
+ XCTAssertFalse(outboxStatusEvent.isEmpty)
+ outboxStatusOnMutationEnqueued.fulfill()
+ case 2:
+ XCTAssertTrue(outboxStatusEvent.isEmpty)
+ default:
+ XCTFail("Should not trigger outbox status event")
}
+ }
- if payload.eventName == HubPayload.EventName.DataStore.outboxMutationEnqueued {
- guard let outboxStatusEvent = payload.data as? OutboxMutationEvent else {
- XCTFail("Failed to cast payload data as OutboxMutationEvent")
- return
- }
- XCTAssertEqual(outboxStatusEvent.modelName, "Post")
- outboxMutationEnqueued.fulfill()
+ let hubListener1 = Amplify.Hub.listen(to: .dataStore, eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued) { payload in
+ guard let outboxStatusEvent = payload.data as? OutboxMutationEvent else {
+ XCTFail("Failed to cast payload data as OutboxMutationEvent")
+ return
}
+ XCTAssertEqual(outboxStatusEvent.modelName, "Post")
+ outboxMutationEnqueued.fulfill()
}
- guard try await HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else {
+ guard try await HubListenerTestUtilities.waitForListener(with: hubListener0, timeout: 5.0) else {
+ XCTFail("Listener not registered for hub")
+ return
+ }
+
+ guard try await HubListenerTestUtilities.waitForListener(with: hubListener1, timeout: 5.0) else {
XCTFail("Listener not registered for hub")
return
}
@@ -79,6 +86,19 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
}
}
+ apiPlugin.responders[.mutateRequestResponse] = MutateRequestResponder { request in
+ let anyModel = try! post.eraseToAnyModel()
+ let remoteSyncMetadata = MutationSyncMetadata(
+ modelId: post.id,
+ modelName: Post.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2
+ )
+ let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
+ return .success(remoteMutationSync)
+ }
+
try await startAmplifyAndWaitForSync()
let saveSuccess = expectation(description: "save success")
@@ -86,10 +106,10 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
_ = try await Amplify.DataStore.save(post)
saveSuccess.fulfill()
}
- await fulfillment(of: [saveSuccess], timeout: 1.0)
await fulfillment(
of: [
+ saveSuccess,
outboxStatusOnStart,
outboxStatusOnMutationEnqueued,
outboxMutationEnqueued,
@@ -97,7 +117,8 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
],
timeout: 5.0
)
- Amplify.Hub.removeListener(hubListener)
+ Amplify.Hub.removeListener(hubListener0)
+ Amplify.Hub.removeListener(hubListener1)
}
/// - Given: A sync-configured DataStore
@@ -112,10 +133,11 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
/// - Given: A sync-configured DataStore
/// - When:
/// - I start syncing with mutation events already in the database
+ /// - keep the mutaiton sync request in process
/// - Then:
/// - The mutation queue delivers the first previously loaded event
func testMutationQueueLoadsPendingMutations() async throws {
-
+ let timeout: TimeInterval = 5
await tryOrFail {
try setUpStorageAdapter()
}
@@ -123,21 +145,46 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
// pre-load the MutationEvent table with mutation data
let mutationEventSaved = expectation(description: "Preloaded mutation event saved")
mutationEventSaved.expectedFulfillmentCount = 2
- for id in 1 ... 2 {
- let postId = "pendingPost-\(id)"
- let pendingPost = Post(id: postId,
- title: "pendingPost-\(id) title",
- content: "pendingPost-\(id) content",
- createdAt: .now())
-
- let pendingPostJSON = try pendingPost.toJSON()
- let event = MutationEvent(id: "mutation-\(id)",
- modelId: "pendingPost-\(id)",
+
+ let posts = (1...2).map { Post(
+ id: "pendingPost-\($0)",
+ title: "pendingPost-\($0) title",
+ content: "pendingPost-\($0) content",
+ createdAt: .now()
+ )}
+
+ let postMutationEvents = try posts.map {
+ let pendingPostJSON = try $0.toJSON()
+ return MutationEvent(
+ id: "mutation-\($0.id)",
+ modelId: $0.id,
modelName: Post.modelName,
json: pendingPostJSON,
mutationType: .create,
- createdAt: .now())
+ createdAt: .now()
+ )
+ }
+ apiPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ if let variables = request.variables?["input"] as? [String: Any],
+ let postId = variables["id"] as? String,
+ let post = posts.first(where: { $0.id == postId })
+ {
+ try? await Task.sleep(seconds: timeout + 1)
+ let anyModel = try! post.eraseToAnyModel()
+ let remoteSyncMetadata = MutationSyncMetadata(modelId: post.id,
+ modelName: Post.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2)
+ let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
+ return .success(remoteMutationSync)
+ }
+ return .failure(.unknown("No matching post found", "", nil))
+ }
+
+
+ postMutationEvents.forEach { event in
storageAdapter.save(event) { result in
switch result {
case .failure(let dataStoreError):
@@ -146,7 +193,6 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
mutationEventSaved.fulfill()
}
}
-
}
await fulfillment(of: [mutationEventSaved], timeout: 1.0)
@@ -163,13 +209,17 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
return
}
- if outboxStatusReceivedCurrentCount == 1 {
+ switch outboxStatusReceivedCurrentCount {
+ case 1:
XCTAssertFalse(outboxStatusEvent.isEmpty)
outboxStatusOnStart.fulfill()
- } else {
+ case 2:
XCTAssertFalse(outboxStatusEvent.isEmpty)
outboxStatusOnMutationEnqueued.fulfill()
+ default:
+ XCTFail("Should not trigger outbox status event")
}
+
}
guard try await HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else {
@@ -195,17 +245,12 @@ class OutgoingMutationQueueTests: SyncEngineTestBase {
try await startAmplify()
}
-
-
- await fulfillment(
- of: [
- outboxStatusOnStart,
- outboxStatusOnMutationEnqueued,
- mutation1Sent,
- mutation2Sent
- ],
- timeout: 5.0
- )
+ await fulfillment(of: [
+ outboxStatusOnStart,
+ outboxStatusOnMutationEnqueued,
+ mutation1Sent,
+ mutation2Sent
+ ], timeout: timeout)
Amplify.Hub.removeListener(hubListener)
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift
index 0699e41032..722a3821c2 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/OutgoingMutationQueueTestsWithMockStateMachine.swift
@@ -69,14 +69,14 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
await fulfillment(of: [expect], timeout: 1)
}
- func testRequestingEvent_subscriptionSetup() throws {
+ func testRequestingEvent_subscriptionSetup() async throws {
let receivedSubscription = expectation(description: "state machine received receivedSubscription")
stateMachine.pushExpectActionCriteria { action in
XCTAssertEqual(action, OutgoingMutationQueue.Action.receivedSubscription)
receivedSubscription.fulfill()
}
stateMachine.state = .starting(apiBehavior, publisher, reconciliationQueue)
- wait(for: [receivedSubscription], timeout: 1.0)
+ await fulfillment(of: [receivedSubscription], timeout: 1.0)
let json = "{\"id\":\"1234\",\"title\":\"t\",\"content\":\"c\",\"createdAt\":\"2020-09-03T22:55:13.424Z\"}"
let futureResult = MutationEvent(modelId: "1",
@@ -92,17 +92,24 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
}
let apiMutationReceived = expectation(description: "API call for mutate received")
- var listenerFromRequest: GraphQLOperation>.ResultListener!
- let responder = MutateRequestListenerResponder> { _, eventListener in
- listenerFromRequest = eventListener
+ let responder = MutateRequestResponder> { _ in
apiMutationReceived.fulfill()
- return nil
+ try! await Task.sleep(seconds: 0.5)
+ let model = MockSynced(id: "id-1")
+ let anyModel = try! model.eraseToAnyModel()
+ let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
+ modelName: MockSynced.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2)
+ let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
+ return .success(remoteMutationSync)
}
- apiBehavior.responders[.mutateRequestListener] = responder
+ apiBehavior.responders[.mutateRequestResponse] = responder
stateMachine.state = .requestingEvent
- wait(for: [enqueueEvent, apiMutationReceived], timeout: 1)
+ await fulfillment(of: [enqueueEvent, apiMutationReceived], timeout: 1)
let processEvent = expectation(description: "state requestingEvent, processedEvent")
stateMachine.pushExpectActionCriteria { action in
@@ -110,17 +117,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
processEvent.fulfill()
}
- let model = MockSynced(id: "id-1")
- let anyModel = try model.eraseToAnyModel()
- let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
- modelName: MockSynced.modelName,
- deleted: false,
- lastChangedAt: Date().unixSeconds,
- version: 2)
- let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- listenerFromRequest(.success(.success(remoteMutationSync)))
-
- wait(for: [processEvent], timeout: 1)
+ await fulfillment(of: [processEvent], timeout: 1)
}
func testRequestingEvent_nosubscription() async {
@@ -135,7 +132,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
await fulfillment(of: [expect], timeout: 1)
}
- func testReceivedStartActionWhileExpectingEventProcessedAction() throws {
+ func testReceivedStartActionWhileExpectingEventProcessedAction() async throws {
// Ensure subscription is setup
let receivedSubscription = expectation(description: "receivedSubscription")
stateMachine.pushExpectActionCriteria { action in
@@ -143,7 +140,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
receivedSubscription.fulfill()
}
stateMachine.state = .starting(apiBehavior, publisher, reconciliationQueue)
- wait(for: [receivedSubscription], timeout: 0.1)
+ await fulfillment(of: [receivedSubscription], timeout: 0.1)
// Mock incoming mutation event
let post = Post(title: "title",
@@ -160,16 +157,24 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
enqueueEvent.fulfill()
}
let mutateAPICallExpecation = expectation(description: "Call to api category for mutate")
- var listenerFromRequest: GraphQLOperation>.ResultListener!
- let responder = MutateRequestListenerResponder> { _, eventListener in
- listenerFromRequest = eventListener
+
+ let responder = MutateRequestResponder> { _ in
mutateAPICallExpecation.fulfill()
- return nil
+ try! await Task.sleep(seconds: 0.3)
+ let model = MockSynced(id: "id-1")
+ let anyModel = try! model.eraseToAnyModel()
+ let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
+ modelName: MockSynced.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2)
+ let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
+ return .success(remoteMutationSync)
}
- apiBehavior.responders[.mutateRequestListener] = responder
+ apiBehavior.responders[.mutateRequestResponse] = responder
stateMachine.state = .requestingEvent
- wait(for: [enqueueEvent, mutateAPICallExpecation], timeout: 0.1)
+ await fulfillment(of: [enqueueEvent, mutateAPICallExpecation], timeout: 0.1)
// While we are expecting the mutationEvent to be processed by making an API call,
// stop the mutation queue. Note that we are not testing that the operation
@@ -181,7 +186,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
mutationQueueStopped.fulfill()
}
mutationQueue.stopSyncingToCloud { }
- wait(for: [mutationQueueStopped], timeout: 0.1)
+ await fulfillment(of: [mutationQueueStopped], timeout: 0.1)
// Re-enable syncing
let startReceivedAgain = expectation(description: "Start received again")
@@ -196,7 +201,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
mutationEventPublisher: publisher,
reconciliationQueue: reconciliationQueue)
- wait(for: [startReceivedAgain], timeout: 1)
+ await fulfillment(of: [startReceivedAgain], timeout: 1)
// After - enabling, mock the callback from API to be completed
let processEvent = expectation(description: "state requestingEvent, processedEvent")
@@ -205,17 +210,7 @@ class OutgoingMutationQueueMockStateTest: XCTestCase {
processEvent.fulfill()
}
- let model = MockSynced(id: "id-1")
- let anyModel = try model.eraseToAnyModel()
- let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
- modelName: MockSynced.modelName,
- deleted: false,
- lastChangedAt: Date().unixSeconds,
- version: 2)
- let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- listenerFromRequest(.success(.success(remoteMutationSync)))
-
- wait(for: [processEvent], timeout: 1)
+ await fulfillment(of: [processEvent], timeout: 1)
}
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift
index 2163442dca..fc2133fa69 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/ProcessMutationErrorFromCloudOperationTests.swift
@@ -530,7 +530,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `delete`, remote model is an update, conflict handler returns `.retryLocal`
/// - Then:
/// - API is called to delete with local model
- func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsRetryLocal() throws {
+ func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsRetryLocal() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .delete)
@@ -546,19 +546,32 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectCompletion.fulfill()
}
- var eventListenerOptional: GraphQLOperation>.ResultListener?
let apiMutateCalled = expectation(description: "API was called")
- mockAPIPlugin.responders[.mutateRequestListener] =
- MutateRequestListenerResponder> { request, eventListener in
- guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
- XCTFail("The document variables property doesn't contain a valid input")
- return nil
- }
- XCTAssert(input["id"] as? String == localPost.id)
- XCTAssert(request.document.contains("DeletePost"))
- eventListenerOptional = eventListener
- apiMutateCalled.fulfill()
- return nil
+ mockAPIPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
+ modelName: remotePost.modelName,
+ deleted: true,
+ lastChangedAt: 0,
+ version: 3)
+
+ guard let variables = request.variables,
+ let input = variables["input"] as? [String: Any]
+ else {
+ XCTFail("The document variables property doesn't contain a valid input")
+ return .failure(.unknown("", "", nil))
+ }
+ XCTAssert(input["id"] as? String == localPost.id)
+ XCTAssert(request.document.contains("DeletePost"))
+ apiMutateCalled.fulfill()
+
+ guard let mockResponse = (
+ try? localPost.eraseToAnyModel()
+ ).map({ MutationSync(model:$0 , syncMetadata: updatedMetadata) })
+ else {
+ XCTFail("Failed to wrap to AnyModel")
+ return .failure(.unknown("", "", nil))
+ }
+ return .success(mockResponse)
}
let expectConflicthandlerCalled = expectation(description: "Expect conflict handler called")
@@ -585,21 +598,9 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
- guard let eventListener = eventListenerOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
- let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
- modelName: remotePost.modelName,
- deleted: true,
- lastChangedAt: 0,
- version: 3)
- let mockResponse = MutationSync(model: try localPost.eraseToAnyModel(), syncMetadata: updatedMetadata)
- eventListener(.success(.success(mockResponse)))
-
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectCompletion], timeout: defaultAsyncWaitTimeout)
}
/// - Given: Conflict Unhandled error
@@ -607,7 +608,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `delete`, remote model is an update, conflict handler returns `.retry(model)`
/// - Then:
/// - API is called with the model from the conflict handler result
- func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsRetryModel() throws {
+ func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsRetryModel() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .delete)
@@ -624,19 +625,28 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
}
let retryModel = Post(title: "retryModel", content: "retryContent", createdAt: .now())
- var eventListenerOptional: GraphQLOperation>.ResultListener?
+
let apiMutateCalled = expectation(description: "API was called")
- mockAPIPlugin.responders[.mutateRequestListener] =
- MutateRequestListenerResponder> { request, eventListener in
- guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
- XCTFail("The document variables property doesn't contain a valid input")
- return nil
- }
- XCTAssert(input["title"] as? String == retryModel.title)
- XCTAssertTrue(request.document.contains("UpdatePost"))
- eventListenerOptional = eventListener
- apiMutateCalled.fulfill()
- return nil
+ mockAPIPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
+ XCTFail("The document variables property doesn't contain a valid input")
+ return .failure(.unknown("The document variables property doesn't contain a valid input", "", nil))
+ }
+ XCTAssert(input["title"] as? String == retryModel.title)
+ XCTAssertTrue(request.document.contains("UpdatePost"))
+ apiMutateCalled.fulfill()
+
+ let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
+ modelName: remotePost.modelName,
+ deleted: false,
+ lastChangedAt: 0,
+ version: 3)
+ guard let mockResponse = (try? localPost.eraseToAnyModel()).map({ MutationSync(model: $0, syncMetadata: updatedMetadata) }) else {
+ XCTFail("Failed to wrap to AnyModel")
+ return .failure(.unknown("Failed to wrap to AnyModel", "", nil))
+ }
+
+ return .success(mockResponse)
}
let expectConflicthandlerCalled = expectation(description: "Expect conflict handler called")
@@ -663,21 +673,9 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
- guard let eventListener = eventListenerOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
- let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
- modelName: remotePost.modelName,
- deleted: false,
- lastChangedAt: 0,
- version: 3)
- let mockResponse = MutationSync(model: try localPost.eraseToAnyModel(), syncMetadata: updatedMetadata)
- eventListener(.success(.success(mockResponse)))
-
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectCompletion], timeout: defaultAsyncWaitTimeout)
}
/// - Given: Conflict Unhandled error
@@ -685,7 +683,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `delete`, remote model is an update, conflict handler returns `.applyRemote`
/// - Then:
/// - Local Store is reconciled(recreated) to remote model, result mutationEvent is `update`
- func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsApplyRemote() throws {
+ func testConflictUnhandledForDeleteMutationAndUpdatedRemoteModelReturnsApplyRemote() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .delete)
@@ -742,9 +740,9 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
completion: completion)
queue.addOperation(operation)
- wait(for: [modelSavedEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectHubEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [modelSavedEvent], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectHubEvent], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectCompletion], timeout: defaultAsyncWaitTimeout)
Amplify.Hub.removeListener(hubListener)
}
@@ -753,13 +751,15 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `update`, remote model is deleted
/// - Then:
/// - Local model is deleted, result mutationEvent is `delete`
- func testConflictUnhandledForUpdateMutationAndDeletedRemoteModel() throws {
+ func testConflictUnhandledForUpdateMutationAndDeletedRemoteModel() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .update)
- guard let graphQLResponseError = try getGraphQLResponseError(withRemote: remotePost,
- deleted: true,
- version: 2) else {
+ guard let graphQLResponseError = try getGraphQLResponseError(
+ withRemote: remotePost,
+ deleted: true,
+ version: 2
+ ) else {
XCTFail("Couldn't get GraphQL response with remote post")
return
}
@@ -797,19 +797,23 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectHubEvent.fulfill()
}
}
- let operation = ProcessMutationErrorFromCloudOperation(dataStoreConfiguration: .testDefault(),
- mutationEvent: mutationEvent,
- api: mockAPIPlugin,
- storageAdapter: storageAdapter,
- graphQLResponseError: graphQLResponseError,
- completion: completion)
+ let operation = ProcessMutationErrorFromCloudOperation(
+ dataStoreConfiguration: .testDefault(),
+ mutationEvent: mutationEvent,
+ api: mockAPIPlugin,
+ storageAdapter: storageAdapter,
+ graphQLResponseError: graphQLResponseError,
+ completion: completion
+ )
queue.addOperation(operation)
- wait(for: [modelDeletedEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [metadataSavedEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectHubEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ modelDeletedEvent,
+ metadataSavedEvent,
+ expectHubEvent,
+ expectCompletion
+ ], timeout: defaultAsyncWaitTimeout)
Amplify.Hub.removeListener(hubListener)
}
@@ -818,13 +822,15 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `update`, remote model is an update, conflict handler returns `.applyRemote`
/// - Then:
/// - Local model is updated with remote model data, result mutationEvent is `update`
- func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsApplyRemote() throws {
+ func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsApplyRemote() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .update)
- guard let graphQLResponseError = try getGraphQLResponseError(withRemote: remotePost,
- deleted: false,
- version: 2) else {
+ guard let graphQLResponseError = try getGraphQLResponseError(
+ withRemote: remotePost,
+ deleted: false,
+ version: 2
+ ) else {
XCTFail("Couldn't get GraphQL response with remote post")
return
}
@@ -879,19 +885,24 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectConflicthandlerCalled.fulfill()
resolve(.applyRemote)
})
- let operation = ProcessMutationErrorFromCloudOperation(dataStoreConfiguration: configuration,
- mutationEvent: mutationEvent,
- api: mockAPIPlugin,
- storageAdapter: storageAdapter,
- graphQLResponseError: graphQLResponseError,
- completion: completion)
+
+ let operation = ProcessMutationErrorFromCloudOperation(
+ dataStoreConfiguration: configuration,
+ mutationEvent: mutationEvent,
+ api: mockAPIPlugin,
+ storageAdapter: storageAdapter,
+ graphQLResponseError: graphQLResponseError,
+ completion: completion
+ )
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [modelSavedEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [metadataSavedEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectHubEvent], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ expectConflicthandlerCalled,
+ modelSavedEvent,
+ metadataSavedEvent,
+ expectHubEvent,
+ expectCompletion
+ ], timeout: defaultAsyncWaitTimeout)
Amplify.Hub.removeListener(hubListener)
}
@@ -900,13 +911,15 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `update`, remote model is an update, conflict handler returns `.retryLocal`
/// - Then:
/// - API is called to update with the local model
- func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsRetryLocal() throws {
+ func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsRetryLocal() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .update)
- guard let graphQLResponseError = try getGraphQLResponseError(withRemote: remotePost,
- deleted: false,
- version: 2) else {
+ guard let graphQLResponseError = try getGraphQLResponseError(
+ withRemote: remotePost,
+ deleted: false,
+ version: 2
+ ) else {
XCTFail("Couldn't get GraphQL response with remote post")
return
}
@@ -920,19 +933,32 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectCompletion.fulfill()
}
- var eventListenerOptional: GraphQLOperation>.ResultListener?
let apiMutateCalled = expectation(description: "API was called")
- mockAPIPlugin.responders[.mutateRequestListener] =
- MutateRequestListenerResponder> { request, eventListener in
- guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
- XCTFail("The document variables property doesn't contain a valid input")
- return nil
- }
- XCTAssert(input["title"] as? String == localPost.title)
- XCTAssertTrue(request.document.contains("UpdatePost"))
- eventListenerOptional = eventListener
- apiMutateCalled.fulfill()
- return nil
+ mockAPIPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
+ XCTFail("The document variables property doesn't contain a valid input")
+ return .failure(.unknown("The document variables property doesn't contain a valid input", "", nil))
+ }
+ XCTAssert(input["title"] as? String == localPost.title)
+ XCTAssertTrue(request.document.contains("UpdatePost"))
+ apiMutateCalled.fulfill()
+
+ let updatedMetadata = MutationSyncMetadata(
+ modelId: remotePost.id,
+ modelName: remotePost.modelName,
+ deleted: false,
+ lastChangedAt: 0,
+ version: 3
+ )
+
+ guard let mockResponse = (try? localPost.eraseToAnyModel())
+ .map({ MutationSync(model: $0, syncMetadata: updatedMetadata) })
+ else {
+ XCTFail("Failed to wrap to AnyModel")
+ return .failure(.unknown("Failed to wrap AnyModel", "", nil))
+ }
+
+ return .success(mockResponse)
}
let expectConflicthandlerCalled = expectation(description: "Expect conflict handler called")
@@ -948,30 +974,23 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectConflicthandlerCalled.fulfill()
resolve(.retryLocal)
})
- let operation = ProcessMutationErrorFromCloudOperation(dataStoreConfiguration: configuration,
- mutationEvent: mutationEvent,
- api: mockAPIPlugin,
- storageAdapter: storageAdapter,
- graphQLResponseError: graphQLResponseError,
- reconciliationQueue: reconciliationQueue,
- completion: completion)
+ let operation = ProcessMutationErrorFromCloudOperation(
+ dataStoreConfiguration: configuration,
+ mutationEvent: mutationEvent,
+ api: mockAPIPlugin,
+ storageAdapter: storageAdapter,
+ graphQLResponseError: graphQLResponseError,
+ reconciliationQueue: reconciliationQueue,
+ completion: completion
+ )
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
- guard let eventListener = eventListenerOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
- let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
- modelName: remotePost.modelName,
- deleted: false,
- lastChangedAt: 0,
- version: 3)
- let mockResponse = MutationSync(model: try localPost.eraseToAnyModel(), syncMetadata: updatedMetadata)
- eventListener(.success(.success(mockResponse)))
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ expectConflicthandlerCalled,
+ apiMutateCalled,
+ expectCompletion
+ ], timeout: defaultAsyncWaitTimeout)
}
/// - Given: Conflict Unhandled error
@@ -979,7 +998,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - MutationType is `update`, remote model is an update, conflict handler returns `.retry(Model)`
/// - Then:
/// - API is called to update the model from the conflict handler result
- func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsRetryModel() throws {
+ func testConflictUnhandledUpdateMutationAndUpdatedRemoteReturnsRetryModel() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .update)
@@ -1000,19 +1019,30 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
}
let retryModel = Post(title: "retryModel", content: "retryContent", createdAt: .now())
- var eventListenerOptional: GraphQLOperation>.ResultListener?
+
let apiMutateCalled = expectation(description: "API was called")
- mockAPIPlugin.responders[.mutateRequestListener] =
- MutateRequestListenerResponder> { request, eventListener in
- guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
- XCTFail("The document variables property doesn't contain a valid input")
- return nil
- }
- XCTAssert(input["title"] as? String == retryModel.title)
- XCTAssertTrue(request.document.contains("UpdatePost"))
- eventListenerOptional = eventListener
- apiMutateCalled.fulfill()
- return nil
+ mockAPIPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
+ XCTFail("The document variables property doesn't contain a valid input")
+ return .failure(.unknown("The document variables property doesn't contain a valid input", "", nil))
+ }
+ XCTAssert(input["title"] as? String == retryModel.title)
+ XCTAssertTrue(request.document.contains("UpdatePost"))
+ apiMutateCalled.fulfill()
+ let updatedMetadata = MutationSyncMetadata(
+ modelId: remotePost.id,
+ modelName: remotePost.modelName,
+ deleted: false,
+ lastChangedAt: 0,
+ version: 3
+ )
+ guard let mockResponse = (try? localPost.eraseToAnyModel())
+ .map({ MutationSync(model: $0, syncMetadata: updatedMetadata) })
+ else {
+ XCTFail("Failed to wrap to AnyModel")
+ return .failure(.unknown("Failed to wrap to AnyModel", "", nil))
+ }
+ return .success(mockResponse)
}
let expectConflicthandlerCalled = expectation(description: "Expect conflict handler called")
@@ -1028,29 +1058,22 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectConflicthandlerCalled.fulfill()
resolve(.retry(retryModel))
})
- let operation = ProcessMutationErrorFromCloudOperation(dataStoreConfiguration: configuration,
- mutationEvent: mutationEvent,
- api: mockAPIPlugin,
- storageAdapter: storageAdapter,
- graphQLResponseError: graphQLResponseError,
- reconciliationQueue: reconciliationQueue,
- completion: completion)
+ let operation = ProcessMutationErrorFromCloudOperation(
+ dataStoreConfiguration: configuration,
+ mutationEvent: mutationEvent,
+ api: mockAPIPlugin,
+ storageAdapter: storageAdapter,
+ graphQLResponseError: graphQLResponseError,
+ reconciliationQueue: reconciliationQueue,
+ completion: completion
+ )
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
- guard let eventListener = eventListenerOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
- let updatedMetadata = MutationSyncMetadata(modelId: remotePost.id,
- modelName: remotePost.modelName,
- deleted: false,
- lastChangedAt: 0,
- version: 3)
- let mockResponse = MutationSync(model: try localPost.eraseToAnyModel(), syncMetadata: updatedMetadata)
- eventListener(.success(.success(mockResponse)))
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ expectConflicthandlerCalled,
+ apiMutateCalled,
+ expectCompletion
+ ], timeout: defaultAsyncWaitTimeout)
}
/// - Given: Conflict Unhandled error
@@ -1059,7 +1082,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - API is called to update with local model and response contains error
/// - Then:
/// - `DataStoreErrorHandler` is called
- func testConflictUnhandledSyncToCloudReturnsError() throws {
+ func testConflictUnhandledSyncToCloudReturnsError() async throws {
let localPost = Post(title: "localTitle", content: "localContent", createdAt: .now())
let remotePost = Post(id: localPost.id, title: "remoteTitle", content: "remoteContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: localPost, modelSchema: localPost.schema, mutationType: .update)
@@ -1079,19 +1102,18 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
expectCompletion.fulfill()
}
- var eventListenerOptional: GraphQLOperation>.ResultListener?
+
let apiMutateCalled = expectation(description: "API was called")
- mockAPIPlugin.responders[.mutateRequestListener] =
- MutateRequestListenerResponder> { request, eventListener in
- guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
- XCTFail("The document variables property doesn't contain a valid input")
- return nil
- }
- XCTAssert(input["title"] as? String == localPost.title)
- XCTAssertTrue(request.document.contains("UpdatePost"))
- eventListenerOptional = eventListener
- apiMutateCalled.fulfill()
- return nil
+ mockAPIPlugin.responders[.mutateRequestResponse] = MutateRequestResponder> { request in
+ guard let variables = request.variables, let input = variables["input"] as? [String: Any] else {
+ XCTFail("The document variables property doesn't contain a valid input")
+ return .failure(.unknown("The document variables property doesn't contain a valid input", "", nil))
+ }
+
+ XCTAssert(input["title"] as? String == localPost.title)
+ XCTAssertTrue(request.document.contains("UpdatePost"))
+ apiMutateCalled.fulfill()
+ return .failure(.error([GraphQLError(message: "some other error")]))
}
let expectConflicthandlerCalled = expectation(description: "Expect conflict handler called")
@@ -1118,18 +1140,12 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
completion: completion)
queue.addOperation(operation)
- wait(for: [expectConflicthandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [apiMutateCalled], timeout: defaultAsyncWaitTimeout)
- guard let eventListener = eventListenerOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
-
- let error = GraphQLError(message: "some other error")
- eventListener(.success(.failure(.error([error]))))
-
- wait(for: [expectErrorHandlerCalled], timeout: defaultAsyncWaitTimeout)
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ expectConflicthandlerCalled,
+ apiMutateCalled,
+ expectErrorHandlerCalled,
+ expectCompletion
+ ], timeout: defaultAsyncWaitTimeout)
}
/// Given: GraphQL "OperationDisabled" error
@@ -1137,7 +1153,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
/// - API is called and response contains an "OperationDisabled" error
/// - Then:
/// - Completion handler is successfully called
- func testProcessOperationDisabledError() throws {
+ func testProcessOperationDisabledError() async throws {
let post = Post(title: "localTitle", content: "localContent", createdAt: .now())
let mutationEvent = try MutationEvent(model: post, modelSchema: Post.schema, mutationType: .create)
let expectCompletion = expectation(description: "Expect to complete error processing")
@@ -1164,7 +1180,7 @@ class ProcessMutationErrorFromCloudOperationTests: XCTestCase {
completion: completion)
queue.addOperation(operation)
- wait(for: [expectCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [expectCompletion], timeout: defaultAsyncWaitTimeout)
}
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift
index 71baed1a34..dbe8220e59 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/MutationQueue/SyncMutationToCloudOperationTests.swift
@@ -38,38 +38,38 @@ class SyncMutationToCloudOperationTests: XCTestCase {
let expectFirstCallToAPIMutate = expectation(description: "First call to API.mutate")
let expectSecondCallToAPIMutate = expectation(description: "Second call to API.mutate")
+ let model = MockSynced(id: "id-1")
let post1 = Post(title: "post1", content: "content1", createdAt: .now())
let mutationEvent = try MutationEvent(model: post1, modelSchema: post1.schema, mutationType: .create)
- var listenerFromFirstRequestOptional: GraphQLOperation>.ResultListener?
- var listenerFromSecondRequestOptional: GraphQLOperation>.ResultListener?
-
var numberOfTimesEntered = 0
- let responder = MutateRequestListenerResponder> { request, eventListener in
+ let responder = MutateRequestResponder> { request in
+ defer { numberOfTimesEntered += 1 }
if numberOfTimesEntered == 0 {
let requestInputVersion = request.variables.flatMap { $0["input"] as? [String: Any] }.flatMap { $0["_version"] as? Int }
XCTAssertEqual(requestInputVersion, 10)
- listenerFromFirstRequestOptional = eventListener
expectFirstCallToAPIMutate.fulfill()
- } else if numberOfTimesEntered == 1 {
- listenerFromSecondRequestOptional = eventListener
+ let urlError = URLError(URLError.notConnectedToInternet)
+ return .failure(.unknown("", "", APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
+ } else if numberOfTimesEntered == 1, let anyModel = try? model.eraseToAnyModel() {
expectSecondCallToAPIMutate.fulfill()
+ let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
+ modelName: model.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2)
+ return .success(MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata))
} else {
XCTFail("This should not be called more than once")
+ return .failure(.unknown("Unexpected operation", "", nil))
}
- numberOfTimesEntered += 1
- // We could return an operation here, but we don't need to.
- // The main reason for having this responder is to get the eventListener.
- // the eventListener block will execute the the call to validateResponseFromCloud
- return nil
}
- mockAPIPlugin.responders[.mutateRequestListener] = responder
+ mockAPIPlugin.responders[.mutateRequestResponse] = responder
let completion: GraphQLOperation>.ResultListener = { _ in
expectMutationRequestCompletion.fulfill()
}
- let model = MockSynced(id: "id-1")
let operation = await SyncMutationToCloudOperation(
mutationEvent: mutationEvent,
getLatestSyncMetadata: {
@@ -89,32 +89,11 @@ class SyncMutationToCloudOperationTests: XCTestCase {
)
let queue = OperationQueue()
queue.addOperation(operation)
- await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout)
- guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
-
- let urlError = URLError(URLError.notConnectedToInternet)
- listenerFromFirstRequest(.failure(APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
- await fulfillment(of: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout)
-
- guard let listenerFromSecondRequest = listenerFromSecondRequestOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
-
-
- let anyModel = try model.eraseToAnyModel()
- let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
- modelName: model.modelName,
- deleted: false,
- lastChangedAt: Date().unixSeconds,
- version: 2)
- let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- listenerFromSecondRequest(.success(.success(remoteMutationSync)))
- // waitForExpectations(timeout: 1)
- await fulfillment(of: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout)
+ await fulfillment(of: [
+ expectFirstCallToAPIMutate,
+ expectSecondCallToAPIMutate,
+ expectMutationRequestCompletion
+ ], timeout: defaultAsyncWaitTimeout)
}
func testRetryOnChangeReachability() async throws {
@@ -127,28 +106,30 @@ class SyncMutationToCloudOperationTests: XCTestCase {
let expectSecondCallToAPIMutate = expectation(description: "Second call to API.mutate")
let post1 = Post(title: "post1", content: "content1", createdAt: .now())
let mutationEvent = try MutationEvent(model: post1, modelSchema: post1.schema, mutationType: .create)
-
- var listenerFromFirstRequestOptional: GraphQLOperation>.ResultListener?
- var listenerFromSecondRequestOptional: GraphQLOperation>.ResultListener?
+ let model = MockSynced(id: "id-1")
var numberOfTimesEntered = 0
- let responder = MutateRequestListenerResponder> { _, eventListener in
+ let responder = MutateRequestResponder> { request in
+ defer { numberOfTimesEntered += 1 }
if numberOfTimesEntered == 0 {
- listenerFromFirstRequestOptional = eventListener
expectFirstCallToAPIMutate.fulfill()
- } else if numberOfTimesEntered == 1 {
- listenerFromSecondRequestOptional = eventListener
+ let urlError = URLError(URLError.notConnectedToInternet)
+ return .failure(.unknown("", "", APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
+ } else if numberOfTimesEntered == 1, let anyModel = try? model.eraseToAnyModel() {
expectSecondCallToAPIMutate.fulfill()
+ let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
+ modelName: model.modelName,
+ deleted: false,
+ lastChangedAt: Date().unixSeconds,
+ version: 2)
+ let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
+ return .success(remoteMutationSync)
} else {
XCTFail("This should not be called more than once")
+ return .failure(.unknown("This should not be called more than once", "", nil))
}
- numberOfTimesEntered += 1
- // We could return an operation here, but we don't need to.
- // The main reason for having this responder is to get the eventListener.
- // the eventListener block will execute the the call to validateResponseFromCloud
- return nil
}
- mockAPIPlugin.responders[.mutateRequestListener] = responder
+ mockAPIPlugin.responders[.mutateRequestResponse] = responder
let completion: GraphQLOperation>.ResultListener = { _ in
expectMutationRequestCompletion.fulfill()
@@ -166,30 +147,10 @@ class SyncMutationToCloudOperationTests: XCTestCase {
let queue = OperationQueue()
queue.addOperation(operation)
await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout)
- guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
- let urlError = URLError(URLError.notConnectedToInternet)
- listenerFromFirstRequest(.failure(APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
reachabilityPublisher.send(ReachabilityUpdate(isOnline: true))
await fulfillment(of: [expectSecondCallToAPIMutate], timeout: defaultAsyncWaitTimeout)
- guard let listenerFromSecondRequest = listenerFromSecondRequestOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
-
- let model = MockSynced(id: "id-1")
- let anyModel = try model.eraseToAnyModel()
- let remoteSyncMetadata = MutationSyncMetadata(modelId: model.id,
- modelName: model.modelName,
- deleted: false,
- lastChangedAt: Date().unixSeconds,
- version: 2)
- let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- listenerFromSecondRequest(.success(.success(remoteMutationSync)))
await fulfillment(of: [expectMutationRequestCompletion], timeout: defaultAsyncWaitTimeout)
}
@@ -203,23 +164,20 @@ class SyncMutationToCloudOperationTests: XCTestCase {
let post1 = Post(title: "post1", content: "content1", createdAt: .now())
let mutationEvent = try MutationEvent(model: post1, modelSchema: post1.schema, mutationType: .create)
- var listenerFromFirstRequestOptional: GraphQLOperation>.ResultListener?
-
var numberOfTimesEntered = 0
- let responder = MutateRequestListenerResponder> { _, eventListener in
+ let responder = MutateRequestResponder> { _ in
+ defer { numberOfTimesEntered += 1 }
if numberOfTimesEntered == 0 {
- listenerFromFirstRequestOptional = eventListener
expectFirstCallToAPIMutate.fulfill()
+ let urlError = URLError(URLError.notConnectedToInternet)
+ return .failure(.unknown("", "", APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
} else {
XCTFail("This should not be called more than once")
+ return .failure(.unknown("This should not be called more than once", "", nil))
}
- numberOfTimesEntered += 1
- // We could return an operation here, but we don't need to.
- // The main reason for having this responder is to get the eventListener.
- // the eventListener block will execute the the call to validateResponseFromCloud
- return nil
+
}
- mockAPIPlugin.responders[.mutateRequestListener] = responder
+ mockAPIPlugin.responders[.mutateRequestResponse] = responder
let completion: GraphQLOperation>.ResultListener = { asyncEvent in
switch asyncEvent {
@@ -242,13 +200,6 @@ class SyncMutationToCloudOperationTests: XCTestCase {
let queue = OperationQueue()
queue.addOperation(operation)
await fulfillment(of: [expectFirstCallToAPIMutate], timeout: defaultAsyncWaitTimeout)
- guard let listenerFromFirstRequest = listenerFromFirstRequestOptional else {
- XCTFail("Listener was not called through MockAPICategoryPlugin")
- return
- }
-
- let urlError = URLError(URLError.notConnectedToInternet)
- listenerFromFirstRequest(.failure(APIError.networkError("mock NotConnectedToInternetError", nil, urlError)))
// At this point, we will be "waiting forever" to retry our request or until the operation is canceled
operation.cancel()
@@ -326,28 +277,13 @@ class SyncMutationToCloudOperationTests: XCTestCase {
}
)
- let responder = MutateRequestListenerResponder> { request, eventListener in
- let requestOptions = GraphQLOperationRequest>.Options(pluginOptions: nil)
- let request = GraphQLOperationRequest>(apiName: request.apiName,
- operationType: .mutation,
- document: request.document,
- variables: request.variables,
- responseType: request.responseType,
- options: requestOptions)
- let operation = MockGraphQLOperation(request: request, responseType: request.responseType)
-
- numberOfTimesEntered += 1
-
- DispatchQueue.global().sync {
- // Fail with 401 status code
- eventListener!(.failure(error))
- }
-
- return operation
+ let responder = MutateRequestResponder> { request in
+ defer { numberOfTimesEntered += 1 }
+ return .failure(.unknown("", "", error))
}
- mockAPIPlugin.responders[.mutateRequestListener] = responder
-
+ mockAPIPlugin.responders[.mutateRequestResponse] = responder
+
let queue = OperationQueue()
queue.addOperation(operation)
@@ -372,6 +308,9 @@ class SyncMutationToCloudOperationTests: XCTestCase {
}
func testGetRetryAdvice_OperationErrorAuthErrorWithSingleAuth_RetryFalse() async throws {
+ let expectation = expectation(description: "operation completed")
+ var numberOfTimesEntered = 0
+ var error: APIError?
let operation = await SyncMutationToCloudOperation(
mutationEvent: try createMutationEvent(),
getLatestSyncMetadata: { nil },
@@ -379,13 +318,30 @@ class SyncMutationToCloudOperationTests: XCTestCase {
authModeStrategy: AWSDefaultAuthModeStrategy(),
networkReachabilityPublisher: publisher,
currentAttemptNumber: 1,
- completion: { _ in }
+ completion: { result in
+ XCTAssertEqual(numberOfTimesEntered, 1)
+ switch result {
+ case .failure(let apiError):
+ error = apiError
+ default:
+ XCTFail("Wrong result")
+ }
+ expectation.fulfill()
+ }
)
-
- let authError = AuthError.notAuthorized("", "", nil)
- let error = APIError.operationError("", "", authError)
- let advice = operation.getRetryAdviceIfRetryable(error: error)
- XCTAssertFalse(advice.shouldRetry)
+
+ let responder = MutateRequestResponder> { request in
+ defer { numberOfTimesEntered += 1 }
+ let authError = AuthError.notAuthorized("", "", nil)
+ return .failure(.unknown("", "", APIError.operationError("", "", authError)))
+ }
+
+ mockAPIPlugin.responders[.mutateRequestResponse] = responder
+
+ let queue = OperationQueue()
+ queue.addOperation(operation)
+ await fulfillment(of: [expectation])
+ XCTAssertEqual(false, operation.getRetryAdviceIfRetryable(error: error!).shouldRetry)
}
func testGetRetryAdvice_OperationErrorAuthErrorSessionExpired_RetryTrue() async throws {
@@ -435,12 +391,18 @@ public class MockMultiAuthModeStrategy: AuthModeStrategy {
public func authTypesFor(schema: ModelSchema,
operation: ModelOperation) -> AWSAuthorizationTypeIterator {
- return AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools, .apiKey])
+ return AWSAuthorizationTypeIterator(withValues: [
+ .designated(.amazonCognitoUserPools),
+ .designated(.apiKey)
+ ])
}
public func authTypesFor(schema: ModelSchema,
operations: [ModelOperation]) -> AWSAuthorizationTypeIterator {
- return AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools, .apiKey])
+ return AWSAuthorizationTypeIterator(withValues: [
+ .designated(.amazonCognitoUserPools),
+ .designated(.apiKey)
+ ])
}
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift
index b1b3d978bd..d153f98310 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ModelReconciliationDeleteTests.swift
@@ -40,14 +40,16 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
storageAdapter.save(localSyncMetadata) { _ in localMetadataSaved.fulfill() }
await fulfillment(of: [localMetadataSaved], timeout: 1)
- var valueListenerFromRequest: MutationSyncInProcessListener?
+ var asyncSequence: AmplifyAsyncThrowingSequence>>?
let expectationListener = expectation(description: "listener")
- let responder = SubscribeRequestListenerResponder> { request, valueListener, _ in
+ let responder = SubscribeRequestListenerResponder> { request in
if request.document.contains("onUpdateMockSynced") {
- valueListenerFromRequest = valueListener
expectationListener.fulfill()
}
- return nil
+
+ let sequence = AmplifyAsyncThrowingSequence>>()
+ asyncSequence = sequence
+ return sequence
}
apiPlugin.responders[.subscribeRequestListener] = responder
@@ -59,7 +61,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
}
await fulfillment(of: [expectationListener], timeout: 2)
- guard let valueListener = valueListenerFromRequest else {
+ guard let asyncSequence else {
XCTFail("Incoming responder didn't set up listener")
return
}
@@ -71,7 +73,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
lastChangedAt: Date().unixSeconds,
version: 1)
let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- valueListener(.data(.success(remoteMutationSync)))
+ asyncSequence.send(.data(.success(remoteMutationSync)))
// Because we expect this event to be dropped, there won't be a Hub notification or callback to listen to, so
// we have to brute-force this wait
@@ -106,9 +108,13 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
let onUpdateListener: MutationSyncInProcessListener = { _ in
print("emptyListener")
}
- _ = self.apiPlugin.subscribe(request: request,
- valueListener: onUpdateListener,
- completionListener: nil)
+
+ Task {
+ let sequence = self.apiPlugin.subscribe(request: request)
+ for try await event in sequence {
+ onUpdateListener(event)
+ }
+ }
MockAWSIncomingEventReconciliationQueue.mockSend(event: .initialized)
}
default:
@@ -131,15 +137,15 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
try setUpStorageAdapter()
}
- var valueListenerFromRequest: MutationSyncInProcessListener?
+ var asyncSequence: AmplifyAsyncThrowingSequence>>?
- let responder = SubscribeRequestListenerResponder> {request, valueListener, _ in
+ let responder = SubscribeRequestListenerResponder> {request in
if request.document.contains("onUpdateMockSynced") {
- valueListenerFromRequest = valueListener
expectationListener.fulfill()
}
-
- return nil
+ let sequence = AmplifyAsyncThrowingSequence>>()
+ asyncSequence = sequence
+ return sequence
}
apiPlugin.responders[.subscribeRequestListener] = responder
@@ -151,7 +157,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
}
await fulfillment(of: [expectationListener], timeout: 1)
- guard let valueListener = valueListenerFromRequest else {
+ guard let asyncSequence else {
XCTFail("Incoming responder didn't set up listener")
return
}
@@ -174,7 +180,7 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
lastChangedAt: Date().unixSeconds,
version: 2)
let remoteMutationSync = MutationSync(model: anyModel, syncMetadata: remoteSyncMetadata)
- valueListener(.data(.success(remoteMutationSync)))
+ asyncSequence.send(.data(.success(remoteMutationSync)))
await fulfillment(of: [syncReceivedNotification], timeout: 1)
let finalLocalMetadata = try storageAdapter.queryMutationSyncMetadata(for: model.id,
@@ -225,9 +231,12 @@ class ModelReconciliationDeleteTests: SyncEngineTestBase {
break
}
}
- _ = self.apiPlugin.subscribe(request: request,
- valueListener: onUpdateListener,
- completionListener: nil)
+ Task {
+ let sequence = self.apiPlugin.subscribe(request: request)
+ for try await event in sequence {
+ onUpdateListener(event)
+ }
+ }
MockAWSIncomingEventReconciliationQueue.mockSend(event: .initialized)
}
default:
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift
index 52447a2ff4..0b83465e7e 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/ReconcileAndLocalSaveOperationTests.swift
@@ -758,20 +758,29 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase {
}, receiveValue: { _ in
expect.fulfill()
}).store(in: &cancellables)
- await fulfillment(of: [expect, storageExpect, storageMetadataExpect, notifyExpect, hubExpect], timeout: 1)
+ await fulfillment(of: [
+ expect,
+ storageExpect,
+ storageMetadataExpect,
+ notifyExpect,
+ hubExpect
+ ], timeout: 1)
}
func testApplyRemoteModels_multipleDispositions() async {
- let dispositions: [RemoteSyncReconciler.Disposition] = [.create(anyPostMutationSync),
- .create(anyPostMutationSync),
- .update(anyPostMutationSync),
- .update(anyPostMutationSync),
- .delete(anyPostMutationSync),
- .delete(anyPostMutationSync),
- .create(anyPostMutationSync),
- .update(anyPostMutationSync),
- .delete(anyPostMutationSync)]
+ let dispositions: [RemoteSyncReconciler.Disposition] = [
+ .create(anyPostMutationSync),
+ .create(anyPostMutationSync),
+ .update(anyPostMutationSync),
+ .update(anyPostMutationSync),
+ .delete(anyPostMutationSync),
+ .delete(anyPostMutationSync),
+ .create(anyPostMutationSync),
+ .update(anyPostMutationSync),
+ .delete(anyPostMutationSync)
+ ]
+
let expect = expectation(description: "should complete successfully")
expect.expectedFulfillmentCount = 2
let storageExpect = expectation(description: "storage save/delete should be called")
@@ -835,7 +844,13 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase {
}, receiveValue: { _ in
expect.fulfill()
}).store(in: &cancellables)
- await fulfillment(of: [expect, storageExpect, notifyExpect, storageMetadataExpect, hubExpect], timeout: 1)
+ await fulfillment(of: [
+ expect,
+ storageExpect,
+ storageMetadataExpect,
+ notifyExpect,
+ hubExpect
+ ], timeout: 1)
}
func testApplyRemoteModels_skipFailedOperations() async throws {
@@ -890,7 +905,12 @@ class ReconcileAndLocalSaveOperationTests: XCTestCase {
}, receiveValue: { _ in
}).store(in: &cancellables)
- await fulfillment(of: [expect, expectedDropped, expectedDeleteSuccess], timeout: 1)
+
+ await fulfillment(of: [
+ expect,
+ expectedDropped,
+ expectedDeleteSuccess
+ ], timeout: 1)
}
func testApplyRemoteModels_failWithConstraintViolationShouldBeSuccessful() async {
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/AWSAuthorizationTypeIteratorTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/AWSAuthorizationTypeIteratorTests.swift
index 15472e2ac6..f07db70e2b 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/AWSAuthorizationTypeIteratorTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/AWSAuthorizationTypeIteratorTests.swift
@@ -18,8 +18,8 @@ class AWSAuthorizationTypeIteratorTests: XCTestCase {
}
func testOneElementIterator_hasNextValue_once() throws {
- var iterator = AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools])
-
+ var iterator = AWSAuthorizationTypeIterator(withValues: [.designated(.amazonCognitoUserPools)])
+
XCTAssertTrue(iterator.hasNext)
XCTAssertNotNil(iterator.next())
@@ -27,8 +27,11 @@ class AWSAuthorizationTypeIteratorTests: XCTestCase {
}
func testTwoElementsIterator_hasNextValue_twice() throws {
- var iterator = AWSAuthorizationTypeIterator(withValues: [.amazonCognitoUserPools, .apiKey])
-
+ var iterator = AWSAuthorizationTypeIterator(withValues: [
+ .designated(.amazonCognitoUserPools),
+ .designated(.apiKey)
+ ])
+
XCTAssertTrue(iterator.hasNext)
XCTAssertNotNil(iterator.next())
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift
index ab8f7640c1..0add6aebd1 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/Support/MutationEventExtensionsTests.swift
@@ -21,7 +21,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
/// 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 {
+ func testSentModelWithNilVersion_Reconciled() async throws {
let modelId = UUID().uuidString
let post = Post(id: modelId, title: "title", content: "content", createdAt: .now())
let requestMutationEvent = try createMutationEvent(model: post,
@@ -57,7 +57,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
updatingVersionExpectation.fulfill()
}
}
- wait(for: [updatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [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,
@@ -75,7 +75,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
queryAfterUpdatingVersionExpectation.fulfill()
}
}
- wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [queryAfterUpdatingVersionExpectation], timeout: 1)
}
/// - Given: A pending mutation events queue with two events(update and delete) containing `nil` version,
@@ -85,7 +85,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
/// 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 {
+ func testSentModelWithNilVersion_SecondPendingEventNotReconciled() async throws {
let modelId = UUID().uuidString
let post = Post(id: modelId, title: "title", content: "content", createdAt: .now())
let requestMutationEvent = try createMutationEvent(model: post,
@@ -127,7 +127,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
updatingVersionExpectation.fulfill()
}
}
- wait(for: [updatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [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,
@@ -146,7 +146,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
queryAfterUpdatingVersionExpectation.fulfill()
}
}
- wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [queryAfterUpdatingVersionExpectation], timeout: 1)
}
/// - Given: A pending mutation events queue with event containing version 2, a sent mutation event model
@@ -154,7 +154,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
/// 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 {
+ func testSentModelVersionNewerThanResponseVersion_PendingEventNotReconciled() async throws {
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())
@@ -190,7 +190,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
updatingVersionExpectation.fulfill()
}
}
- wait(for: [updatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [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,
@@ -208,7 +208,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
queryAfterUpdatingVersionExpectation.fulfill()
}
}
- wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [queryAfterUpdatingVersionExpectation], timeout: 1)
}
/// - Given: A pending mutation events queue with event containing version 1, a sent mutation event model
@@ -216,7 +216,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
/// 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 {
+ func testSentModelNotEqualToResponseModel_PendingEventNotReconciled() async throws {
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())
@@ -253,7 +253,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
updatingVersionExpectation.fulfill()
}
}
- wait(for: [updatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [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,
@@ -271,7 +271,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
queryAfterUpdatingVersionExpectation.fulfill()
}
}
- wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [queryAfterUpdatingVersionExpectation], timeout: 1)
}
/// - Given: A pending mutation events queue with event containing version 1, a sent mutation event model
@@ -279,7 +279,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
/// 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 {
+ func testPendingVersionReconciledSuccess() async throws {
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())
@@ -315,7 +315,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
updatingVersionExpectation.fulfill()
}
}
- wait(for: [updatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [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,
@@ -333,7 +333,7 @@ class MutationEventExtensionsTest: BaseDataStoreTests {
queryAfterUpdatingVersionExpectation.fulfill()
}
}
- wait(for: [queryAfterUpdatingVersionExpectation], timeout: 1)
+ await fulfillment(of: [queryAfterUpdatingVersionExpectation], timeout: 1)
}
private func createMutationEvent(model: Model,
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockOutgoingMutationQueue.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockOutgoingMutationQueue.swift
index 3eec2bce57..e0223bac2b 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockOutgoingMutationQueue.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockOutgoingMutationQueue.swift
@@ -17,7 +17,7 @@ class MockOutgoingMutationQueue: OutgoingMutationQueueBehavior {
completion()
}
- func startSyncingToCloud(api: APICategoryGraphQLBehaviorExtended,
+ func startSyncingToCloud(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?) {
// no-op
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockRemoteSyncEngine.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockRemoteSyncEngine.swift
index 105ab41ebf..1f2036784e 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockRemoteSyncEngine.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/MockRemoteSyncEngine.swift
@@ -37,7 +37,7 @@ class MockRemoteSyncEngine: RemoteSyncEngineBehavior {
init() {
self.remoteSyncTopicPublisher = PassthroughSubject()
}
- func start(api: APICategoryGraphQLBehaviorExtended, auth: AuthCategoryBehavior?) {
+ func start(api: APICategoryGraphQLBehavior, auth: AuthCategoryBehavior?) {
syncing = true
}
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/NoOpMutationQueue.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/NoOpMutationQueue.swift
index 56a65bc96b..82c9b031af 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/NoOpMutationQueue.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/Mocks/NoOpMutationQueue.swift
@@ -17,7 +17,7 @@ class NoOpMutationQueue: OutgoingMutationQueueBehavior {
completion()
}
- func startSyncingToCloud(api: APICategoryGraphQLBehaviorExtended,
+ func startSyncingToCloud(api: APICategoryGraphQLBehavior,
mutationEventPublisher: MutationEventPublisher,
reconciliationQueue: IncomingEventReconciliationQueue?) {
// do nothing
diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/SyncEngineTestBase.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/SyncEngineTestBase.swift
index 5d69207902..d86b8ba8a4 100644
--- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/SyncEngineTestBase.swift
+++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/TestSupport/SyncEngineTestBase.swift
@@ -75,7 +75,6 @@ class SyncEngineTestBase: XCTestCase {
authPlugin = MockAuthCategoryPlugin()
try Amplify.add(plugin: apiPlugin)
try Amplify.add(plugin: authPlugin)
- Amplify.Logging.logLevel = .verbose
}
override func tearDown() async throws {
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift
index c0172df6ff..1aac1aa85e 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/DataStoreFlutterConsecutiveUpdatesTests.swift
@@ -17,7 +17,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB
/// - Given: API has been setup with `Post` model registered
/// - When: A Post is saved and then immediately updated
/// - Then: The post should be updated with new fields immediately and in the eventual consistent state
- func testSaveAndImmediatelyUpdate() throws {
+ func testSaveAndImmediatelyUpdate() async throws {
try startAmplifyAndWaitForSync()
let plugin: AWSDataStorePlugin = try Amplify.DataStore.getPlugin(for: "awsDataStorePlugin") as! AWSDataStorePlugin
let newPost = try PostWrapper(title: "MyPost",
@@ -132,7 +132,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB
/// - Given: API has been setup with `Post` model registered
/// - When: A Post is saved and deleted immediately
/// - Then: The Post should not be returned when queried for immediately and in the eventual consistent state
- func testSaveAndImmediatelyDelete() throws {
+ func testSaveAndImmediatelyDelete() async throws {
try startAmplifyAndWaitForSync()
let plugin: AWSDataStorePlugin = try Amplify.DataStore.getPlugin(for: "awsDataStorePlugin") as! AWSDataStorePlugin
let newPost = try PostWrapper(title: "MyPost",
@@ -237,7 +237,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB
/// - Given: API has been setup with `Post` model registered
/// - When: A Post is saved with sync complete, updated and deleted immediately
/// - Then: The Post should not be returned when queried for
- func testSaveThenUpdateAndImmediatelyDelete() throws {
+ func testSaveThenUpdateAndImmediatelyDelete() async throws {
try startAmplifyAndWaitForSync()
let plugin: AWSDataStorePlugin = try Amplify.DataStore.getPlugin(for: "awsDataStorePlugin") as! AWSDataStorePlugin
@@ -367,7 +367,7 @@ class DataStoreFlutterConsecutiveUpdatesTests: SyncEngineFlutterIntegrationTestB
await fulfillment(of: [apiQuerySuccess], timeout: networkTimeout)
}
- private func queryPost(id: String, plugin: AWSDataStorePlugin) -> PostWrapper? {
+ private func queryPost(id: String, plugin: AWSDataStorePlugin) -> async PostWrapper? {
let queryExpectation = expectation(description: "Query is successful")
var queryResult: PostWrapper?
plugin.query(FlutterSerializedModel.self, modelSchema: Post.schema, where: Post.keys.id.eq(id)) { result in
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift
index 5505303ae7..b60c5bdef7 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginFlutterTests/TestSupport/SyncEngineFlutterIntegrationTestBase.swift
@@ -62,7 +62,7 @@ class SyncEngineFlutterIntegrationTestBase: XCTestCase {
}
}
- func startAmplifyAndWaitForSync() throws {
+ func startAmplifyAndWaitForSync() async throws {
let syncStarted = expectation(description: "Sync started")
let plugin: AWSDataStorePlugin = try Amplify.DataStore.getPlugin(for: "awsDataStorePlugin") as! AWSDataStorePlugin
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario1Tests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario1Tests.swift
index ae26e441e7..49c5fb097e 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario1Tests.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/Connection/DataStoreConnectionScenario1Tests.swift
@@ -228,7 +228,7 @@ class DataStoreConnectionScenario1Tests: SyncEngineIntegrationTestBase {
}
func testDeleteWithInvalidCondition() async throws {
- await setUp(withModels: TestModelRegistration())
+ await setUp(withModels: TestModelRegistration(), logLevel: .verbose)
try await startAmplifyAndWaitForSync()
let team = Team1(name: "name")
let project = Project1(team: team)
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift
index 916a4d26ea..a5fe3115f4 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreHubEventsTests.swift
@@ -37,7 +37,6 @@ class DataStoreHubEventTests: HubEventsIntegrationTestBase {
/// {modelName: "Some Model name", isFullSync: true/false, isDeltaSync: false/true, createCount: #, updateCount: #, deleteCount: #}
/// - syncQueriesReady received, payload should be nil
func testDataStoreConfiguredDispatchesHubEvents() async throws {
- Amplify.Logging.logLevel = .verbose
try configureAmplify(withModels: TestModelRegistration())
try await Amplify.DataStore.clear()
await Amplify.reset()
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreLargeNumberModelsSubscriptionTests.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreLargeNumberModelsSubscriptionTests.swift
index cc09f0f40d..4121f562bd 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreLargeNumberModelsSubscriptionTests.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/DataStoreLargeNumberModelsSubscriptionTests.swift
@@ -46,7 +46,7 @@ class DataStoreLargeNumberModelsSubscriptionTests: SyncEngineIntegrationTestBase
}
func testDataStoreStop_subscriptionsShouldAllUnsubscribed() async throws {
- await setUp(withModels: TestModelRegistration())
+ await setUp(withModels: TestModelRegistration(), logLevel: .verbose)
try await startAmplifyAndWaitForSync()
try await stopDataStoreAndVerifyAppSyncClientDisconnected()
diff --git a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift
index 9bb4167312..e480689b9e 100644
--- a/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift
+++ b/AmplifyPlugins/DataStore/Tests/DataStoreHostApp/AWSDataStorePluginIntegrationTests/TestSupport/HubEventsIntegrationTestBase.swift
@@ -52,7 +52,9 @@ class HubEventsIntegrationTestBase: XCTestCase {
#if os(watchOS)
try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: models, configuration: .subscriptionsDisabled))
#else
- try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: models))
+ try Amplify.add(plugin: AWSDataStorePlugin(
+ modelRegistration: models
+ ))
#endif
try Amplify.add(plugin: AWSAPIPlugin(
modelRegistration: models,
diff --git a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift
index 7a59c3c809..6b194b48af 100644
--- a/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift
+++ b/AmplifyPlugins/Geo/Sources/AWSLocationGeoPlugin/AWSLocationGeoPlugin+Configure.swift
@@ -8,7 +8,7 @@
import Foundation
@_spi(InternalAmplifyConfiguration) import Amplify
import AWSPluginsCore
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
import AWSLocation
import AWSClientRuntime
diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/PinpointEvent+PinpointClientTypes.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/PinpointEvent+PinpointClientTypes.swift
index b198b5e45d..e2c78b496e 100644
--- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/PinpointEvent+PinpointClientTypes.swift
+++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Analytics/PinpointEvent+PinpointClientTypes.swift
@@ -7,6 +7,7 @@
import AWSPinpoint
import AWSPluginsCore
+import InternalAmplifyCredentials
import Foundation
extension PinpointEvent {
diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift
index e93baf21c2..1d7a9d7cd9 100644
--- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift
+++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Extensions/PinpointClient+CredentialsProvider.swift
@@ -8,7 +8,7 @@
import AWSClientRuntime
import AWSPluginsCore
import AWSPinpoint
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
extension PinpointClient {
convenience init(region: String, credentialsProvider: CredentialsProviding) throws {
diff --git a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift
index fd10c9a9fa..9a2b02fa87 100644
--- a/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift
+++ b/AmplifyPlugins/Internal/Sources/InternalAWSPinpoint/Support/Utils/PinpointRequestsRegistry.swift
@@ -8,8 +8,7 @@
import Foundation
import AWSPinpoint
import ClientRuntime
-@_spi(PluginHTTPClientEngine)
-import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
@globalActor actor PinpointRequestsRegistry {
static let shared = PinpointRequestsRegistry()
diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift
index 76b32228ce..733dc11828 100644
--- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift
+++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/AWSCloudWatchLoggingSessionController.swift
@@ -6,7 +6,7 @@
//
import AWSPluginsCore
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
import Amplify
import Combine
import Foundation
diff --git a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift
index 90bebdf058..6a11ded903 100644
--- a/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift
+++ b/AmplifyPlugins/Logging/Sources/AWSCloudWatchLoggingPlugin/Configuration/DefaultRemoteLoggingConstraintsProvider.swift
@@ -8,6 +8,7 @@
import Foundation
import Amplify
import AWSPluginsCore
+import InternalAmplifyCredentials
import AWSClientRuntime
import ClientRuntime
diff --git a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift
index b90c7e0a03..9470da9c2b 100644
--- a/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift
+++ b/AmplifyPlugins/Predictions/AWSPredictionsPlugin/Service/Predictions/AWSPredictionsService.swift
@@ -12,7 +12,7 @@ import AWSTextract
import AWSComprehend
import AWSPolly
import AWSPluginsCore
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
import Foundation
import ClientRuntime
import AWSClientRuntime
diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift
index 14dd8aca32..a930bb2184 100644
--- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift
+++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Service/Storage/AWSS3StorageService.swift
@@ -9,14 +9,14 @@ import Foundation
import AWSS3
import Amplify
import AWSPluginsCore
-@_spi(PluginHTTPClientEngine) import AWSPluginsCore
import ClientRuntime
+@_spi(PluginHTTPClientEngine) import InternalAmplifyCredentials
/// - Tag: AWSS3StorageService
class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy {
// resettable values
- private var authService: AWSAuthServiceBehavior?
+ private var authService: AWSAuthCredentialsProviderBehavior?
var logger: Logger!
var preSignedURLBuilder: AWSS3PreSignedURLBuilderBehavior!
var awsS3: AWSS3Behavior!
@@ -48,7 +48,7 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy {
storageConfiguration.sessionIdentifier
}
- convenience init(authService: AWSAuthServiceBehavior,
+ convenience init(authService: AWSAuthCredentialsProviderBehavior,
region: String,
bucket: String,
httpClientEngineProxy: HttpClientEngineProxy? = nil,
@@ -107,7 +107,7 @@ class AWSS3StorageService: AWSS3StorageServiceBehavior, StorageServiceProxy {
bucket: bucket)
}
- init(authService: AWSAuthServiceBehavior,
+ init(authService: AWSAuthCredentialsProviderBehavior,
storageConfiguration: StorageConfiguration = .default,
storageTransferDatabase: StorageTransferDatabase = .default,
fileSystem: FileSystem = .default,
diff --git a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift
index cce7df5f2a..d317df8ea8 100644
--- a/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift
+++ b/AmplifyPlugins/Storage/Tests/AWSS3StoragePluginTests/Support/Internal/DefaultStorageTransferDatabaseTests.swift
@@ -73,7 +73,7 @@ class DefaultStorageTransferDatabaseTests: XCTestCase {
XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key1" }))
XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key2" }))
}
-
+
/// Given: A DefaultStorageTransferDatabase
/// When: linkTasksWithSessions is invoked with tasks containing multipart uploads but without a sessionTask, and a session
/// Then: A StorageTransferTaskPairs linking the tasks with the session is returned
@@ -127,7 +127,7 @@ class DefaultStorageTransferDatabaseTests: XCTestCase {
uploadId: "uploadId",
uploadFile: uploadFile
)
-
+
let transferTask1 = StorageTransferTask(
transferType: .multiPartUploadPart(
uploadId: "uploadId",
@@ -159,7 +159,7 @@ class DefaultStorageTransferDatabaseTests: XCTestCase {
bytes: Bytes.megabytes(6).bytes,
eTag: "eTag"
)
-
+
let transferTask2 = StorageTransferTask(
transferType: .multiPartUploadPart(
uploadId: "uploadId",
@@ -184,7 +184,7 @@ class DefaultStorageTransferDatabaseTests: XCTestCase {
bytesTransferred: Bytes.megabytes(3).bytes,
taskIdentifier: 1
)
-
+
let pairs = database.linkTasksWithSessions(
persistableTransferTasks: [
"taskId0": .init(task: transferTask0),
@@ -195,7 +195,7 @@ class DefaultStorageTransferDatabaseTests: XCTestCase {
session
]
)
-
+
XCTAssertEqual(pairs.count, 3)
XCTAssertTrue(pairs.contains(where: { $0.transferTask.key == "key1" }))
XCTAssertFalse(pairs.contains(where: { $0.transferTask.key == "key2" }))
diff --git a/AmplifyTestCommon/Mocks/MockAPICategoryPlugin.swift b/AmplifyTestCommon/Mocks/MockAPICategoryPlugin.swift
index 2b65491429..d1336f66a1 100644
--- a/AmplifyTestCommon/Mocks/MockAPICategoryPlugin.swift
+++ b/AmplifyTestCommon/Mocks/MockAPICategoryPlugin.swift
@@ -13,7 +13,7 @@ import Foundation
class MockAPICategoryPlugin: MessageReporter,
APICategoryPlugin,
APICategoryReachabilityBehavior,
- APICategoryGraphQLBehaviorExtended {
+ APICategoryGraphQLBehavior {
var authProviderFactory: APIAuthProviderFactory?
@@ -51,101 +51,26 @@ class MockAPICategoryPlugin: MessageReporter,
// MARK: - Request-based GraphQL methods
- func mutate(request: GraphQLRequest,
- listener: GraphQLOperation.ResultListener?) -> GraphQLOperation {
- // This is a really weighty notification message, but needed for tests to be able to assert that a particular
- // model is being mutated
- notify("mutate(request) document: \(request.document); variables: \(String(describing: request.variables))")
-
- if let responder = responders[.mutateRequestListener] as? MutateRequestListenerResponder {
- if let operation = responder.callback((request, listener)) {
- return operation
- }
- }
- let requestOptions = GraphQLOperationRequest