Skip to content

Commit e8a15b9

Browse files
authored
Merge pull request #1430 from OneSignal/tests/add_user_switching_tests
[Tests] Add more complex user switching tests
2 parents 445b5d2 + 7d60db6 commit e8a15b9

File tree

8 files changed

+592
-112
lines changed

8 files changed

+592
-112
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
3C47A975292642B100312125 /* OneSignalConfigManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C47A973292642B100312125 /* OneSignalConfigManager.m */; };
8383
3C4F9E4428A4466C009F453A /* OSOperationRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F9E4328A4466C009F453A /* OSOperationRepo.swift */; };
8484
3C5117172B15C31E00563465 /* OSUserState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5117162B15C31E00563465 /* OSUserState.swift */; };
85+
3C67F77A2BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */; };
8586
3C789DBD293C2206004CF83D /* OSFocusInfluenceParam.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A600B432453790700514A53 /* OSFocusInfluenceParam.m */; };
8687
3C789DBE293D8EAD004CF83D /* OSFocusInfluenceParam.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A600B41245378ED00514A53 /* OSFocusInfluenceParam.h */; settings = {ATTRIBUTES = (Public, ); }; };
8788
3C7A39C12B7BED900082665E /* OneSignalCoreMocks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC0639A2B6D7A8C002BB07F /* OneSignalCoreMocks.framework */; };
@@ -1124,6 +1125,7 @@
11241125
3C47A973292642B100312125 /* OneSignalConfigManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalConfigManager.m; sourceTree = "<group>"; };
11251126
3C4F9E4328A4466C009F453A /* OSOperationRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSOperationRepo.swift; sourceTree = "<group>"; };
11261127
3C5117162B15C31E00563465 /* OSUserState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSUserState.swift; sourceTree = "<group>"; };
1128+
3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchUserIntegrationTests.swift; sourceTree = "<group>"; };
11271129
3C7A39D42B7C18EE0082665E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
11281130
3C87066F2BDE0957000D8CD2 /* MockUserRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserRequests.swift; sourceTree = "<group>"; };
11291131
3C8706712BDEE076000D8CD2 /* MockUserDefines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefines.swift; sourceTree = "<group>"; };
@@ -1963,6 +1965,7 @@
19631965
isa = PBXGroup;
19641966
children = (
19651967
3CC063ED2B6D7FE8002BB07F /* OneSignalUserTests.swift */,
1968+
3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */,
19661969
);
19671970
path = OneSignalUserTests;
19681971
sourceTree = "<group>";
@@ -3743,6 +3746,7 @@
37433746
isa = PBXSourcesBuildPhase;
37443747
buildActionMask = 2147483647;
37453748
files = (
3749+
3C67F77A2BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift in Sources */,
37463750
3CC063EE2B6D7FE8002BB07F /* OneSignalUserTests.swift in Sources */,
37473751
);
37483752
runOnlyForDeploymentPostprocessing = 0;

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/Extensions/NSDictionary+UnitTests.swift

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,62 @@
11
extension NSDictionary {
2-
func contains(key: String, value: Any) -> Bool {
2+
/*
3+
This method goes one level deep into dictionaries
4+
*/
5+
private func contains(key: String, value: Any) -> Bool {
36
guard let dictVal = self[key] else {
47
return false
58
}
6-
7-
return equals(dictVal, value)
9+
if let value = value as? [String: Any],
10+
let dictVal = dictVal as? NSDictionary {
11+
return dictVal.contains(value)
12+
} else {
13+
return equals(dictVal, value)
14+
}
815
}
916

10-
func contains(_ dict: [String: Any]) -> Bool {
17+
/*
18+
let parent = [
19+
"apple": [
20+
"type": "fruit",
21+
"count": 5,
22+
"fresh": true
23+
],
24+
"orange": [
25+
"type": "color"
26+
],
27+
"cactus": "error"
28+
]
29+
30+
// 1. Example 1 -
31+
let child1 = [
32+
"apple": [
33+
"type": "fruit",
34+
"fresh": true
35+
]
36+
]
37+
parent.contains(child1) = true
38+
39+
// 2. Example 2 -
40+
let child2 = [
41+
"apple": [
42+
"type": "fruit"
43+
],
44+
"orange": [
45+
"type": "fruit"
46+
]
47+
]
48+
parent.contains(child2) = false
49+
50+
// 3. Example 3 -
51+
let child3 = [
52+
"orange": [
53+
"type": "color"
54+
],
55+
"cactus": "error"
56+
]
57+
parent.contains(child3) = true
58+
*/
59+
public func contains(_ dict: [String: Any]) -> Bool {
1160
for (key, value) in dict {
1261
if !contains(key: key, value: value) {
1362
return false

iOS_SDK/OneSignalSDK/OneSignalCoreMocks/MockOneSignalClient.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class MockOneSignalClient: NSObject, IOneSignalClient {
9595
if executeInstantaneously {
9696
finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
9797
} else {
98-
executionQueue.async {
98+
executionQueue.asyncAfter(deadline: .now() + .milliseconds(50)) {
9999
self.finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
100100
}
101101
}
@@ -177,4 +177,10 @@ extension MockOneSignalClient {
177177

178178
return found
179179
}
180+
181+
public func hasExecutedRequestOfType(_ type: AnyClass) -> Bool {
182+
executedRequests.contains { request in
183+
request.isKind(of: type)
184+
}
185+
}
180186
}

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestAddAliases.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class OSRequestAddAliases: OneSignalRequest, OSUserRequest {
5454
init(aliases: [String: String], identityModel: OSIdentityModel) {
5555
self.identityModel = identityModel
5656
self.aliases = aliases
57-
self.stringDescription = "OSRequestAddAliases with aliases: \(aliases)"
57+
self.stringDescription = "<OSRequestAddAliases with aliases: \(aliases)>"
5858
super.init()
5959
self.parameters = ["identity": aliases]
6060
self.method = PATCH
@@ -82,7 +82,7 @@ class OSRequestAddAliases: OneSignalRequest, OSUserRequest {
8282
}
8383
self.identityModel = identityModel
8484
self.aliases = aliases
85-
self.stringDescription = "OSRequestAddAliases with parameters: \(parameters)"
85+
self.stringDescription = "<OSRequestAddAliases with aliases: \(aliases)>"
8686
super.init()
8787
self.parameters = parameters
8888
self.method = HTTPMethod(rawValue: rawMethod)

iOS_SDK/OneSignalSDK/OneSignalUser/Source/Requests/OSRequestCreateSubscription.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class OSRequestCreateSubscription: OneSignalRequest, OSUserRequest {
5858
init(subscriptionModel: OSSubscriptionModel, identityModel: OSIdentityModel) {
5959
self.subscriptionModel = subscriptionModel
6060
self.identityModel = identityModel
61-
self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")"
61+
self.stringDescription = "<OSRequestCreateSubscription with token: \(subscriptionModel.address ?? "nil")>"
6262
super.init()
6363
self.parameters = ["subscription": subscriptionModel.jsonRepresentation()]
6464
self.method = POST
@@ -86,7 +86,7 @@ class OSRequestCreateSubscription: OneSignalRequest, OSUserRequest {
8686
}
8787
self.subscriptionModel = subscriptionModel
8888
self.identityModel = identityModel
89-
self.stringDescription = "OSRequestCreateSubscription with subscriptionModel: \(subscriptionModel.address ?? "nil")"
89+
self.stringDescription = "<OSRequestCreateSubscription with token: \(subscriptionModel.address ?? "nil")>"
9090
super.init()
9191
self.parameters = parameters
9292
self.method = HTTPMethod(rawValue: rawMethod)

iOS_SDK/OneSignalSDK/OneSignalUserMocks/MockUserRequests.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,31 @@ extension MockUserRequests {
125125
)
126126
}
127127

128+
/**
129+
Returns many user data to mimic pulling remote data. Used to test for hydration.
130+
*/
131+
public static func setDefaultFetchUserResponseForHydration(with client: MockOneSignalClient, externalId: String) {
132+
let osid = getOneSignalId(for: externalId)
133+
134+
var fetchResponse: [String: Any] = [
135+
"identity": ["onesignal_id": osid, "external_id": externalId, "remote_alias": "remote_id"],
136+
"properties": [
137+
"tags": ["remote_tag": "remote_value"],
138+
"language": "remote_language"
139+
],
140+
"subscriptions": [
141+
["type": "Email",
142+
"id": "remote_email_id",
143+
"token": "[email protected]"
144+
]
145+
]
146+
]
147+
client.setMockResponseForRequest(
148+
request: "<OSRequestFetchUser with external_id: \(externalId)>",
149+
response: fetchResponse
150+
)
151+
}
152+
128153
public static func setAddTagsResponse(with client: MockOneSignalClient, tags: [String: String]) {
129154
let tagsResponse = MockUserRequests.testPropertiesPayload(properties: ["tags": tags])
130155

@@ -133,4 +158,40 @@ extension MockUserRequests {
133158
response: tagsResponse
134159
)
135160
}
161+
162+
public static func setSetLanguageResponse(with client: MockOneSignalClient, language: String) {
163+
client.setMockResponseForRequest(
164+
request: "<OSRequestUpdateProperties with properties: [\"language\": Optional(\"\(language)\")] deltas: nil refreshDeviceMetadata: false>",
165+
response: [:] // The SDK does not use the response in any way
166+
)
167+
}
168+
169+
public static func setAddAliasesResponse(with client: MockOneSignalClient, aliases: [String: String]) {
170+
client.setMockResponseForRequest(
171+
request: "<OSRequestAddAliases with aliases: \(aliases)>",
172+
response: [:] // The SDK does not use the response in any way
173+
)
174+
}
175+
176+
/** The real response will either contain a subscription payload or be empty (if already exists on user) */
177+
public static func setAddEmailResponse(with client: MockOneSignalClient, email: String) {
178+
let response = [
179+
"subscription": [
180+
"id": "\(email)_id",
181+
"type": "Email",
182+
"token": email
183+
]
184+
]
185+
client.setMockResponseForRequest(
186+
request: "<OSRequestCreateSubscription with token: \(email)>",
187+
response: response
188+
)
189+
}
190+
191+
public static func setTransferSubscriptionResponse(with client: MockOneSignalClient, externalId: String) {
192+
client.setMockResponseForRequest(
193+
request: "<OSRequestTransferSubscription to external_id: \(externalId)>",
194+
response: [:] // The SDK does not use the response
195+
)
196+
}
136197
}

iOS_SDK/OneSignalSDK/OneSignalUserTests/OneSignalUserTests.swift

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -137,107 +137,4 @@ final class OneSignalUserTests: XCTestCase {
137137
identityModel.clearData()
138138
}
139139
}
140-
141-
func testSwitchUser_sendsCorrectTags() throws {
142-
/* Setup */
143-
144-
let client = MockOneSignalClient()
145-
146-
// 1. Set up mock responses for the anonymous user
147-
MockUserRequests.setDefaultCreateAnonUserResponses(with: client)
148-
149-
// 2. Set up mock responses for User A
150-
let tagsUserA = ["tag_a": "value_a"]
151-
MockUserRequests.setDefaultIdentifyUserResponses(with: client, externalId: userA_EUID)
152-
MockUserRequests.setAddTagsResponse(with: client, tags: tagsUserA)
153-
154-
// 3. Set up mock responses for User B
155-
let tagsUserB = ["tag_b": "value_b"]
156-
MockUserRequests.setDefaultCreateUserResponses(with: client, externalId: userB_EUID)
157-
MockUserRequests.setAddTagsResponse(with: client, tags: tagsUserB)
158-
159-
OneSignalCoreImpl.setSharedClient(client)
160-
161-
/* When */
162-
163-
// 1. Login to user A and add tag
164-
OneSignalUserManagerImpl.sharedInstance.login(externalId: userA_EUID, token: nil)
165-
OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_a", value: "value_a")
166-
167-
// 2. Login to user B and add tag
168-
OneSignalUserManagerImpl.sharedInstance.login(externalId: userB_EUID, token: nil)
169-
OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_b", value: "value_b")
170-
171-
// 3. Run background threads
172-
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
173-
174-
/* Then */
175-
176-
// Assert that every request SDK makes has a response set, and is handled
177-
XCTAssertTrue(client.allRequestsHandled)
178-
179-
// Assert there is only one request containing these tags and they are sent to the Anon User
180-
// This is because the Identify User request succeeded, so the user remains the same
181-
XCTAssertTrue(client.onlyOneRequest(
182-
contains: "apps/test-app-id/users/by/onesignal_id/\(anonUserOSID)",
183-
contains: ["properties": ["tags": tagsUserA]])
184-
)
185-
// Assert there is only one request containing these tags and they are sent to userB
186-
XCTAssertTrue(client.onlyOneRequest(
187-
contains: "apps/test-app-id/users/by/onesignal_id/\(userB_OSID)",
188-
contains: ["properties": ["tags": tagsUserB]])
189-
)
190-
}
191-
192-
/**
193-
Motivation: We had a bug where we did not hydrate the middle user's OSID, so any pending updates got dropped.
194-
*/
195-
func testIdentifyUserWithConflict_whenNotCurrentUser_sendsCorrectTags() throws {
196-
/* Setup */
197-
198-
let client = MockOneSignalClient()
199-
OneSignalCoreImpl.setSharedClient(client)
200-
201-
// 1. Set up mock responses for the anonymous user
202-
MockUserRequests.setDefaultCreateAnonUserResponses(with: client)
203-
204-
// 2. Set up mock responses for User A with 409 conflict response
205-
let tagsUserA = ["tag_a": "value_a"]
206-
MockUserRequests.setDefaultIdentifyUserResponses(with: client, externalId: userA_EUID, conflicted: true)
207-
MockUserRequests.setAddTagsResponse(with: client, tags: tagsUserA)
208-
209-
// 3. Set up mock responses for User B
210-
let tagsUserB = ["tag_b": "value_b"]
211-
MockUserRequests.setDefaultCreateUserResponses(with: client, externalId: userB_EUID)
212-
MockUserRequests.setAddTagsResponse(with: client, tags: tagsUserB)
213-
214-
/* When */
215-
216-
// 1. Login to user A (will result in 409 conflict) and add tag
217-
OneSignalUserManagerImpl.sharedInstance.login(externalId: userA_EUID, token: nil)
218-
OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_a", value: "value_a")
219-
220-
// 2. Login to user B and add tag
221-
OneSignalUserManagerImpl.sharedInstance.login(externalId: userB_EUID, token: nil)
222-
OneSignalUserManagerImpl.sharedInstance.addTag(key: "tag_b", value: "value_b")
223-
224-
// 3. Run background threads
225-
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
226-
227-
/* Then */
228-
229-
// Assert that every request SDK makes has a response set, and is handled
230-
XCTAssertTrue(client.allRequestsHandled)
231-
232-
// Assert only one request containing these tags and they are sent to userA by external_id
233-
XCTAssertTrue(client.onlyOneRequest(
234-
contains: "apps/test-app-id/users/by/external_id/\(userA_EUID)",
235-
contains: ["properties": ["tags": tagsUserA]])
236-
)
237-
// Assert there is only one request containing these tags and they are sent to userB
238-
XCTAssertTrue(client.onlyOneRequest(
239-
contains: "apps/test-app-id/users/by/onesignal_id/\(userB_OSID)",
240-
contains: ["properties": ["tags": tagsUserB]])
241-
)
242-
}
243140
}

0 commit comments

Comments
 (0)