Skip to content

Commit a0ae77d

Browse files
lawmichadiegocstn
andauthored
chore: Add API subscription concurrency tests, DataStore stop/clear start tests (#1197)
* chore: Add API subscription concurrency tests, DataStore stop/clear start tests * Update AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLModelBased/GraphQLModelBasedTests+Concurrency.swift Co-authored-by: Diego Costantino <[email protected]> Co-authored-by: Diego Costantino <[email protected]>
1 parent e6a5b0c commit a0ae77d

File tree

6 files changed

+201
-55
lines changed

6 files changed

+201
-55
lines changed

AmplifyPlugins/API/APICategoryPlugin.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
13066F229854831AA628ECEC /* Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithUserPoolIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DD6386039136045F18D44AC /* Pods_HostApp_AWSAPICategoryPluginTestCommon_RESTWithUserPoolIntegrationTests.framework */; };
11+
210E217E265E9BB000D90ED8 /* GraphQLModelBasedTests+Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210E217D265E9BB000D90ED8 /* GraphQLModelBasedTests+Concurrency.swift */; };
1112
211BEDF025E5904E004F367A /* AWSHTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 211BEDEF25E5904E004F367A /* AWSHTTPURLResponse.swift */; };
1213
211BEDFB25E59DC8004F367A /* AWSHTTPURLResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 211BEDFA25E59DC8004F367A /* AWSHTTPURLResponseTests.swift */; };
1314
21233D032469B06B00039337 /* SocialNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21233D022469B06B00039337 /* SocialNote.swift */; };
@@ -176,7 +177,6 @@
176177
FA8EE785238632620097E4F1 /* AWSAPIPlugin+Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8EE784238632620097E4F1 /* AWSAPIPlugin+Log.swift */; };
177178
FA97F5532386CCF500EE9EFE /* AnyModelIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA97F5522386CCF500EE9EFE /* AnyModelIntegrationTests.swift */; };
178179
FAA7A5AA24C0E01500CA863F /* AWSGraphQLSubscriptionOperationCancelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA7A5A924C0E01500CA863F /* AWSGraphQLSubscriptionOperationCancelTests.swift */; };
179-
FAC5F9B1238B70EE00F70F02 /* APICategoryPluginConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA9554DF238B6C0B00D42A43 /* APICategoryPluginConcurrencyTests.swift */; };
180180
FADC469524C0E8AF00EF447B /* GraphQLQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADC469424C0E8AF00EF447B /* GraphQLQueryCombineTests.swift */; };
181181
FAF2199A24C0F25E00171A3D /* OperationTestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF2199924C0F25E00171A3D /* OperationTestBase.swift */; };
182182
FAF7067024C8EFD300F19DCF /* GraphQLSubscribeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAF7066F24C8EFD300F19DCF /* GraphQLSubscribeTests.swift */; };
@@ -309,6 +309,7 @@
309309
1B13CFC866A30622EDD91AF4 /* Pods_AWSAPICategoryPlugin_AWSAPICategoryPluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AWSAPICategoryPlugin_AWSAPICategoryPluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
310310
1B30959CE873C097E54BDFD6 /* Pods-GraphQLWithAPIKeyIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GraphQLWithAPIKeyIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-GraphQLWithAPIKeyIntegrationTests/Pods-GraphQLWithAPIKeyIntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
311311
1FDC07084023DEF205FF6598 /* Pods-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig"; path = "Target Support Files/Pods-AWSAPICategoryPlugin-AWSAPICategoryPluginTests/Pods-AWSAPICategoryPlugin-AWSAPICategoryPluginTests.debug.xcconfig"; sourceTree = "<group>"; };
312+
210E217D265E9BB000D90ED8 /* GraphQLModelBasedTests+Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GraphQLModelBasedTests+Concurrency.swift"; sourceTree = "<group>"; };
312313
211BEDEF25E5904E004F367A /* AWSHTTPURLResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSHTTPURLResponse.swift; sourceTree = "<group>"; };
313314
211BEDFA25E59DC8004F367A /* AWSHTTPURLResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSHTTPURLResponseTests.swift; sourceTree = "<group>"; };
314315
21233D022469B06B00039337 /* SocialNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialNote.swift; sourceTree = "<group>"; };
@@ -534,7 +535,6 @@
534535
FA249EE424C5F8CC009B3CE8 /* GraphQLSubscribeCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLSubscribeCombineTests.swift; sourceTree = "<group>"; };
535536
FA249EEC24C64303009B3CE8 /* MockSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSubscription.swift; sourceTree = "<group>"; };
536537
FA8EE784238632620097E4F1 /* AWSAPIPlugin+Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSAPIPlugin+Log.swift"; sourceTree = "<group>"; };
537-
FA9554DF238B6C0B00D42A43 /* APICategoryPluginConcurrencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICategoryPluginConcurrencyTests.swift; sourceTree = "<group>"; };
538538
FA97F5522386CCF500EE9EFE /* AnyModelIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyModelIntegrationTests.swift; sourceTree = "<group>"; };
539539
FAA7A5A724C0DE1900CA863F /* RESTCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTCombineTests.swift; sourceTree = "<group>"; };
540540
FAA7A5A924C0E01500CA863F /* AWSGraphQLSubscriptionOperationCancelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSGraphQLSubscriptionOperationCancelTests.swift; sourceTree = "<group>"; };
@@ -728,6 +728,7 @@
728728
21FDBB752587D9E40086FCDC /* GraphQLConnectionScenario6Tests.swift */,
729729
21F40A2D23A0707E0074678E /* GraphQLModelBasedTests-amplifyconfiguration.json */,
730730
217856C12383339D00A30D19 /* GraphQLModelBasedTests.swift */,
731+
210E217D265E9BB000D90ED8 /* GraphQLModelBasedTests+Concurrency.swift */,
731732
212B299E259251F100593ED5 /* GraphQLModelBasedTests+List.swift */,
732733
21A90540261644F700EC141D /* GraphQLScalarTests.swift */,
733734
21A9054B2616453200EC141D /* GraphQLTestBase.swift */,
@@ -807,7 +808,6 @@
807808
isa = PBXGroup;
808809
children = (
809810
FA97F5522386CCF500EE9EFE /* AnyModelIntegrationTests.swift */,
810-
FA9554DF238B6C0B00D42A43 /* APICategoryPluginConcurrencyTests.swift */,
811811
21598CEC23A008DB00529F29 /* GraphQLModelBased */,
812812
21598CEB23A0041400529F29 /* GraphQLSyncBased */,
813813
217856732380C73600A30D19 /* Info.plist */,
@@ -2284,7 +2284,6 @@
22842284
files = (
22852285
217D60002578369F009F0639 /* GraphQLConnectionScenario4Tests.swift in Sources */,
22862286
217D60032578369F009F0639 /* GraphQLConnectionScenario3Tests.swift in Sources */,
2287-
FAC5F9B1238B70EE00F70F02 /* APICategoryPluginConcurrencyTests.swift in Sources */,
22882287
21FE04BD25894F8800B81D72 /* GraphQLConnectionScenario3Tests+Subscribe.swift in Sources */,
22892288
216201E323920A6200AB2E10 /* GraphQLSyncBasedTests.swift in Sources */,
22902289
21FE04BB25894F8800B81D72 /* GraphQLConnectionScenario3Tests+Helpers.swift in Sources */,
@@ -2294,6 +2293,7 @@
22942293
217D5FE925783559009F0639 /* GraphQLConnectionScenario2Tests.swift in Sources */,
22952294
21A9054C2616453200EC141D /* GraphQLTestBase.swift in Sources */,
22962295
217856C22383339D00A30D19 /* GraphQLModelBasedTests.swift in Sources */,
2296+
210E217E265E9BB000D90ED8 /* GraphQLModelBasedTests+Concurrency.swift in Sources */,
22972297
212B298B2592519800593ED5 /* GraphQLConnectionScenario3Tests+List.swift in Sources */,
22982298
217D60022578369F009F0639 /* GraphQLConnectionScenario1Tests.swift in Sources */,
22992299
21A90541261644F700EC141D /* GraphQLScalarTests.swift in Sources */,

AmplifyPlugins/API/APICategoryPlugin.xcodeproj/xcshareddata/xcschemes/AWSAPICategoryPluginFunctionalTests.xcscheme

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
buildConfiguration = "Debug"
1111
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
1212
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13-
shouldUseLaunchSchemeArgsEnv = "YES">
13+
shouldUseLaunchSchemeArgsEnv = "YES"
14+
enableThreadSanitizer = "YES">
1415
<Testables>
1516
<TestableReference
1617
skipped = "NO">

AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/APICategoryPluginConcurrencyTests.swift

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import XCTest
9+
import AWSMobileClient
10+
import AWSPluginsCore
11+
@testable import AWSAPICategoryPlugin
12+
@testable import Amplify
13+
@testable import AmplifyTestCommon
14+
@testable import AWSAPICategoryPluginTestCommon
15+
16+
extension GraphQLModelBasedTests {
17+
18+
func testConcurrentSubscriptions() throws {
19+
let count = 50
20+
let connectedInvoked = expectation(description: "Connection established")
21+
connectedInvoked.expectedFulfillmentCount = count
22+
let disconnectedInvoked = expectation(description: "Connection disconnected")
23+
disconnectedInvoked.expectedFulfillmentCount = count
24+
let completedInvoked = expectation(description: "Completed invoked")
25+
completedInvoked.expectedFulfillmentCount = count
26+
let progressInvoked = expectation(description: "progress invoked")
27+
progressInvoked.expectedFulfillmentCount = count
28+
let uuid = UUID().uuidString
29+
let testMethodName = String("\(#function)".dropLast(2))
30+
let title = testMethodName + "Title"
31+
32+
let operations = AtomicValue<[GraphQLSubscriptionOperation<Post>]>(initialValue: [])
33+
DispatchQueue.concurrentPerform(iterations: count) { _ in
34+
let operation = Amplify.API.subscribe(
35+
request: .subscription(of: Post.self, type: .onCreate),
36+
valueListener: { event in
37+
switch event {
38+
case .connection(let state):
39+
switch state {
40+
case .connecting:
41+
break
42+
case .connected:
43+
connectedInvoked.fulfill()
44+
case .disconnected:
45+
disconnectedInvoked.fulfill()
46+
}
47+
case .data(let result):
48+
switch result {
49+
case .success(let post):
50+
if post.id == uuid {
51+
progressInvoked.fulfill()
52+
}
53+
case .failure(let error):
54+
XCTFail("\(error)")
55+
}
56+
}
57+
58+
},
59+
completionListener: { event in
60+
switch event {
61+
case .failure(let error):
62+
XCTFail("Unexpected .failed event: \(error)")
63+
case .success:
64+
completedInvoked.fulfill()
65+
}
66+
})
67+
operations.append(operation)
68+
}
69+
70+
wait(for: [connectedInvoked], timeout: TestCommonConstants.networkTimeout)
71+
XCTAssertEqual(operations.get().count, count)
72+
guard createPost(id: uuid, title: title) != nil else {
73+
XCTFail("Failed to create post")
74+
return
75+
}
76+
77+
wait(for: [progressInvoked], timeout: TestCommonConstants.networkTimeout)
78+
DispatchQueue.concurrentPerform(iterations: count) { index in
79+
operations.get()[index].cancel()
80+
}
81+
wait(for: [disconnectedInvoked, completedInvoked], timeout: TestCommonConstants.networkTimeout)
82+
83+
let completedOperations = operations.get()
84+
for operation in completedOperations {
85+
XCTAssertTrue(operation.isFinished)
86+
}
87+
}
88+
}

AmplifyPlugins/API/AWSAPICategoryPluginFunctionalTests/GraphQLModelBased/GraphQLScalarTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,14 @@ class GraphQLScalarTests: GraphQLTestBase {
185185
XCTAssertNil(emptyModel)
186186
}
187187

188+
// TODO: Update with https://github.com/aws-amplify/amplify-ios/pull/1145
188189
func testListContainerWithNil() {
189190
let container = ListStringContainer(
190191
test: "test",
191192
nullableString: nil,
192193
stringList: ["value1"],
193-
stringNullableList: nil,
194-
nullableStringList: [nil],
194+
stringNullableList: [], // TODO: test with `nil` with new codegen feature
195+
nullableStringList: ["value1"], // TODO: test with `[nil]`
195196
nullableStringNullableList: nil)
196197

197198
let updatedContainer = ListStringContainer(id: container.id,

AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreEndToEndTests.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,108 @@ class DataStoreEndToEndTests: SyncEngineIntegrationTestBase {
237237

238238
wait(for: [conditionalReceived], timeout: networkTimeout)
239239
}
240+
241+
/// Ensure DataStore.stop followed by DataStore.start is successful
242+
///
243+
/// - Given: DataStore has completely started
244+
/// - When:
245+
/// - DataStore.stop
246+
/// - Followed by DataStore.start in the completion of the stop
247+
/// - Then:
248+
/// - Saving a post should be successful
249+
///
250+
func testStopStart() throws {
251+
try startAmplifyAndWaitForSync()
252+
let stopStartSuccess = expectation(description: "stop then start successful")
253+
Amplify.DataStore.stop { result in
254+
switch result {
255+
case .success:
256+
Amplify.DataStore.start { result in
257+
switch result {
258+
case .success:
259+
stopStartSuccess.fulfill()
260+
case .failure(let error):
261+
XCTFail("\(error)")
262+
}
263+
}
264+
case .failure(let error):
265+
XCTFail("\(error)")
266+
}
267+
}
268+
wait(for: [stopStartSuccess], timeout: networkTimeout)
269+
try validateSavePost()
270+
271+
}
272+
273+
/// Ensure DataStore.clear followed by DataStore.start is successful
274+
///
275+
/// - Given: DataStore has completely started
276+
/// - When:
277+
/// - DataStore.clear
278+
/// - Followed by DataStore.start in the completion of the clear
279+
/// - Then:
280+
/// - Saving a post should be successful
281+
///
282+
func testClearStart() throws {
283+
try startAmplifyAndWaitForSync()
284+
let clearStartSuccess = expectation(description: "clear then start successful")
285+
Amplify.DataStore.clear { result in
286+
switch result {
287+
case .success:
288+
Amplify.DataStore.start { result in
289+
switch result {
290+
case .success:
291+
clearStartSuccess.fulfill()
292+
case .failure(let error):
293+
XCTFail("\(error)")
294+
}
295+
}
296+
case .failure(let error):
297+
XCTFail("\(error)")
298+
}
299+
}
300+
wait(for: [clearStartSuccess], timeout: networkTimeout)
301+
try validateSavePost()
302+
}
303+
304+
// MARK: - Helpers
305+
306+
func validateSavePost() throws {
307+
let date = Temporal.DateTime.now()
308+
let newPost = Post(
309+
title: "This is a new post I created",
310+
content: "Original content from DataStoreEndToEndTests at \(date)",
311+
createdAt: date)
312+
let createReceived = expectation(description: "Create notification received")
313+
let hubListener = Amplify.Hub.listen(
314+
to: .dataStore,
315+
eventName: HubPayload.EventName.DataStore.syncReceived) { payload in
316+
guard let mutationEvent = payload.data as? MutationEvent
317+
else {
318+
XCTFail("Can't cast payload as mutation event")
319+
return
320+
}
321+
322+
// This check is to protect against stray events being processed after the test has completed,
323+
// and it shouldn't be construed as a pattern necessary for production applications.
324+
guard let post = try? mutationEvent.decodeModel() as? Post, post.id == newPost.id else {
325+
return
326+
}
327+
328+
if mutationEvent.mutationType == GraphQLMutationType.create.rawValue {
329+
XCTAssertEqual(post.content, newPost.content)
330+
XCTAssertEqual(mutationEvent.version, 1)
331+
createReceived.fulfill()
332+
return
333+
}
334+
}
335+
336+
guard try HubListenerTestUtilities.waitForListener(with: hubListener, timeout: 5.0) else {
337+
XCTFail("Listener not registered for hub")
338+
return
339+
}
340+
341+
Amplify.DataStore.save(newPost) { _ in }
342+
wait(for: [createReceived], timeout: networkTimeout)
343+
}
240344
}

0 commit comments

Comments
 (0)