amplify-swift/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/GraphQLSubscribeTests.swift

299 lines
12 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import Amplify
@testable import AmplifyTestCommon
@testable import AWSAPIPlugin
import AppSyncRealTimeClient
class GraphQLSubscribeTests: OperationTestBase {
// Setup expectations
var onSubscribeInvoked: XCTestExpectation!
// Callback expectations
var receivedCompletionFinish: XCTestExpectation!
var receivedCompletionFailure: XCTestExpectation!
// Subscription lifefycle expectations
var receivedConnected: XCTestExpectation!
var receivedDisconnected: XCTestExpectation!
// Subscription item expectations
var receivedSubscriptionEventData: XCTestExpectation!
var receivedSubscriptionEventError: XCTestExpectation!
// Handles to the subscription item and event handler used to make mock calls into the
// subscription system
var subscriptionItem: SubscriptionItem!
var subscriptionEventHandler: SubscriptionEventHandler!
override func setUp() async throws {
try await super.setUp()
onSubscribeInvoked = expectation(description: "onSubscribeInvoked")
receivedCompletionFinish = expectation(description: "receivedCompletionFinish")
receivedCompletionFailure = expectation(description: "receivedCompletionFailure")
receivedConnected = expectation(description: "receivedConnected")
receivedDisconnected = expectation(description: "receivedDisconnected")
receivedSubscriptionEventData = expectation(description: "receivedSubscriptionEventData")
receivedSubscriptionEventError = expectation(description: "receivedSubscriptionEventError")
try setUpMocksAndSubscriptionItems()
}
/// Lifecycle test
///
/// When:
/// - Successfully connect
/// - Successfully send valid data
/// - Disconnect normally
///
/// Then:
/// - The value handler is invoked with a successful connection message
/// - The value handler is invoked with a successfully decoded value
/// - The value handler is invoked with a disconnection message
/// - The completion handler is invoked with a normal termination
func testHappyPath() throws {
let testJSON: JSONValue = ["foo": true]
let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)!
receivedCompletionFinish.shouldTrigger = true
receivedCompletionFailure.shouldTrigger = false
receivedConnected.shouldTrigger = true
receivedDisconnected.shouldTrigger = true
receivedSubscriptionEventData.shouldTrigger = true
receivedSubscriptionEventError.shouldTrigger = false
subscribe(expecting: testJSON)
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.connection(.connected), subscriptionItem)
subscriptionEventHandler(.data(testData), subscriptionItem)
subscriptionEventHandler(.connection(.disconnected), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
/// Lifecycle test
///
/// When:
/// - Successfully connect
/// - Successfully disconnect
///
/// Then:
/// - The value handler is invoked with a successful connection message
/// - The value handler is not invoked with with a data value
/// - The value handler is invoked with a disconnection message
/// - The completion handler is invoked with a normal termination
func testConnectionWithNoData() throws {
receivedCompletionFinish.shouldTrigger = true
receivedCompletionFailure.shouldTrigger = false
receivedConnected.shouldTrigger = true
receivedDisconnected.shouldTrigger = true
receivedSubscriptionEventData.shouldTrigger = false
receivedSubscriptionEventError.shouldTrigger = false
subscribe()
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.connection(.connected), subscriptionItem)
subscriptionEventHandler(.connection(.disconnected), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
/// Lifecycle test
///
/// When:
/// - Connect with error
///
/// Then:
/// - The value handler is not invoked with a successful connection message
/// - The value handler is not invoked with with a data value
/// - The value handler is invoked with a disconnection message
/// - The completion handler is invoked with an error termination
func testConnectionError() throws {
receivedCompletionFinish.shouldTrigger = false
receivedCompletionFailure.shouldTrigger = true
receivedConnected.shouldTrigger = false
receivedDisconnected.shouldTrigger = false
receivedSubscriptionEventData.shouldTrigger = false
receivedSubscriptionEventError.shouldTrigger = false
subscribe()
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.failed("Error"), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
/// Lifecycle test
///
/// When:
/// - Successfully connect
/// - Send invalid data
/// - Disconnect normally
///
/// Then:
/// - The value handler is invoked with a successful connection message
/// - The value handler is invoked with an error
/// - The value handler is invoked with a disconnection message
/// - The completion handler is invoked with a normal termination
func testDecodingError() throws {
let testData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)!
receivedCompletionFinish.shouldTrigger = true
receivedCompletionFailure.shouldTrigger = false
receivedConnected.shouldTrigger = true
receivedDisconnected.shouldTrigger = true
receivedSubscriptionEventData.shouldTrigger = false
receivedSubscriptionEventError.shouldTrigger = true
subscribe()
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.connection(.connected), subscriptionItem)
subscriptionEventHandler(.data(testData), subscriptionItem)
subscriptionEventHandler(.connection(.disconnected), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
func testMultipleSuccessValues() throws {
let testJSON: JSONValue = ["foo": true]
let testData = #"{"data": {"foo": true}}"# .data(using: .utf8)!
receivedCompletionFinish.shouldTrigger = true
receivedCompletionFailure.shouldTrigger = false
receivedConnected.shouldTrigger = true
receivedDisconnected.shouldTrigger = true
receivedSubscriptionEventData.shouldTrigger = true
receivedSubscriptionEventData.expectedFulfillmentCount = 2
receivedSubscriptionEventError.shouldTrigger = false
subscribe(expecting: testJSON)
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.connection(.connected), subscriptionItem)
subscriptionEventHandler(.data(testData), subscriptionItem)
subscriptionEventHandler(.data(testData), subscriptionItem)
subscriptionEventHandler(.connection(.disconnected), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
func testMixedSuccessAndErrorValues() throws {
let successfulTestData = #"{"data": {"foo": true}}"# .data(using: .utf8)!
let invalidTestData = #"{"data": {"foo": true}, "errors": []}"# .data(using: .utf8)!
receivedCompletionFinish.shouldTrigger = true
receivedCompletionFailure.shouldTrigger = false
receivedConnected.shouldTrigger = true
receivedDisconnected.shouldTrigger = true
receivedSubscriptionEventData.shouldTrigger = true
receivedSubscriptionEventData.expectedFulfillmentCount = 2
receivedSubscriptionEventError.shouldTrigger = true
subscribe()
wait(for: [onSubscribeInvoked], timeout: 0.05)
subscriptionEventHandler(.connection(.connecting), subscriptionItem)
subscriptionEventHandler(.connection(.connected), subscriptionItem)
subscriptionEventHandler(.data(successfulTestData), subscriptionItem)
subscriptionEventHandler(.data(invalidTestData), subscriptionItem)
subscriptionEventHandler(.data(successfulTestData), subscriptionItem)
subscriptionEventHandler(.connection(.disconnected), subscriptionItem)
waitForExpectations(timeout: 0.05)
}
// MARK: - Utilities
/// Sets up test with a mock subscription connection handler that populates
/// self.subscriptionItem and self.subscriptionEventHandler, then fulfills
/// self.onSubscribeInvoked
func setUpMocksAndSubscriptionItems() throws {
let onSubscribe: MockSubscriptionConnection.OnSubscribe = {
requestString, variables, eventHandler in
let item = SubscriptionItem(
requestString: requestString,
variables: variables,
eventHandler: eventHandler
)
self.subscriptionItem = item
self.subscriptionEventHandler = eventHandler
self.onSubscribeInvoked.fulfill()
return item
}
let onGetOrCreateConnection: MockSubscriptionConnectionFactory.OnGetOrCreateConnection = { _, _, _, _ in
MockSubscriptionConnection(onSubscribe: onSubscribe, onUnsubscribe: { _ in })
}
try setUpPluginForSubscriptionResponse(onGetOrCreateConnection: onGetOrCreateConnection)
}
/// Calls `Amplify.API.subscribe` with a request made from a generic document, and returns
/// the operation created from that subscription. If `expectedValue` is not nil, also asserts
/// that the received value is equal to the expected value
@discardableResult
func subscribe(
expecting expectedValue: JSONValue? = nil
) -> GraphQLSubscriptionOperation<JSONValue> {
let testDocument = "subscribe { subscribeTodos { id name description }}"
let request = GraphQLRequest(
document: testDocument,
variables: nil,
responseType: JSONValue.self
)
let operation = apiPlugin.subscribe(
request: request,
valueListener: { value in
switch value {
case .connection(let connectionState):
switch connectionState {
case .connecting:
break
case .connected:
self.receivedConnected.fulfill()
case .disconnected:
self.receivedDisconnected.fulfill()
}
case .data(let result):
switch result {
case .success(let actualValue):
if let expectedValue = expectedValue {
XCTAssertEqual(actualValue, expectedValue)
}
self.receivedSubscriptionEventData.fulfill()
case .failure:
self.receivedSubscriptionEventError.fulfill()
}
}
}, completionListener: { result in
switch result {
case .failure:
self.receivedCompletionFailure.fulfill()
case .success:
self.receivedCompletionFinish.fulfill()
}
})
return operation
}
}