Skip to content

Commit 0aedce3

Browse files
committed
🧪 Added tests
1 parent 0e63d08 commit 0aedce3

10 files changed

+659
-2
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
092D01942D3038F600E3066A /* NotificationObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092D01932D3038F600E3066A /* NotificationObserverTests.swift */; };
1515
09876F3D2DF1D0290051F047 /* RedirectNetworkSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09876F3C2DF1D0290051F047 /* RedirectNetworkSessionTests.swift */; };
1616
09CAA47B2D4B9AD80057FB72 /* IterableApiCriteriaFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CAA47A2D4B9AD80057FB72 /* IterableApiCriteriaFetchTests.swift */; };
17+
09E8F2F92E29008200E92ABB /* ConsentTrackingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E8F2F82E29008200E92ABB /* ConsentTrackingTests.swift */; };
1718
1802C00F2CA2C99E009DEA2B /* CombinationComplexCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1802C00E2CA2C99E009DEA2B /* CombinationComplexCriteria.swift */; };
1819
181063DB2C9841460078E0ED /* CustomEventUserUpdateTestCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181063DA2C9841460078E0ED /* CustomEventUserUpdateTestCaseTests.swift */; };
1920
181063DD2C994FA40078E0ED /* ValidateCustomEventUserUpdateAPITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181063DC2C994FA40078E0ED /* ValidateCustomEventUserUpdateAPITest.swift */; };
@@ -176,8 +177,8 @@
176177
5B5AA716284F1A6D0093FED4 /* MockNetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5AA710284F1A6D0093FED4 /* MockNetworkSession.swift */; };
177178
5B5AA717284F1A6D0093FED4 /* MockNetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5AA710284F1A6D0093FED4 /* MockNetworkSession.swift */; };
178179
5B6C3C1127CE871F00B9A753 /* NavInboxSessionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C3C1027CE871F00B9A753 /* NavInboxSessionUITests.swift */; };
179-
8A272FD02DD3775800634559 /* IterableDataRegionObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A272FCF2DD3775800634559 /* IterableDataRegionObjCTests.m */; };
180180
5B88BC482805D09D004016E5 /* (null) in Sources */ = {isa = PBXBuildFile; };
181+
8A272FD02DD3775800634559 /* IterableDataRegionObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A272FCF2DD3775800634559 /* IterableDataRegionObjCTests.m */; };
181182
8AAA8BA92D07310600DF8220 /* IterableSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AAA8B6C2D07310600DF8220 /* IterableSDK.h */; };
182183
8AAA8BAB2D07310600DF8220 /* IterableAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8B1F2D07310600DF8220 /* IterableAction.swift */; };
183184
8AAA8BB12D07310600DF8220 /* IterableActionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AAA8B202D07310600DF8220 /* IterableActionContext.swift */; };
@@ -580,6 +581,7 @@
580581
092D01932D3038F600E3066A /* NotificationObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationObserverTests.swift; sourceTree = "<group>"; };
581582
09876F3C2DF1D0290051F047 /* RedirectNetworkSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedirectNetworkSessionTests.swift; sourceTree = "<group>"; };
582583
09CAA47A2D4B9AD80057FB72 /* IterableApiCriteriaFetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableApiCriteriaFetchTests.swift; sourceTree = "<group>"; };
584+
09E8F2F82E29008200E92ABB /* ConsentTrackingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentTrackingTests.swift; sourceTree = "<group>"; };
583585
1802C00E2CA2C99E009DEA2B /* CombinationComplexCriteria.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinationComplexCriteria.swift; sourceTree = "<group>"; };
584586
181063DA2C9841460078E0ED /* CustomEventUserUpdateTestCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventUserUpdateTestCaseTests.swift; sourceTree = "<group>"; };
585587
181063DC2C994FA40078E0ED /* ValidateCustomEventUserUpdateAPITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidateCustomEventUserUpdateAPITest.swift; sourceTree = "<group>"; };
@@ -1470,6 +1472,7 @@
14701472
AC7B142C20D02CE200877BFE /* unit-tests */ = {
14711473
isa = PBXGroup;
14721474
children = (
1475+
09E8F2F82E29008200E92ABB /* ConsentTrackingTests.swift */,
14731476
8A272FCF2DD3775800634559 /* IterableDataRegionObjCTests.m */,
14741477
E9EA7CA52C1EE39A00A9D6FB /* anonymous-tracking-tests */,
14751478
1CBFFE152A97AEDC00ED57EE /* embedded-messaging-tests */,
@@ -2429,6 +2432,7 @@
24292432
5531CDAE22A9C992000D05E2 /* ClassExtensionsTests.swift in Sources */,
24302433
AC995F9E2167E9FD0099A184 /* CommonExtensions.swift in Sources */,
24312434
5536781F2576FF9000DB3652 /* IterableUtilTests.swift in Sources */,
2435+
09E8F2F92E29008200E92ABB /* ConsentTrackingTests.swift in Sources */,
24322436
AC2C668020D31B1F00D46CC9 /* NotificationResponseTests.swift in Sources */,
24332437
55E02D39253F8D86009DB8BC /* WebViewProtocolTests.swift in Sources */,
24342438
1881A21B2C7602F80020C64D /* ComparatorDataTypeWithArrayInput.swift in Sources */,

tests/common/MockLocalStorage.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class MockLocalStorage: LocalStorageProtocol {
3232

3333
var anonymousUsageTrack: Bool = true
3434

35+
var visitorConsentTimestamp: Int64?
36+
3537
var anonymousUserUpdate: [AnyHashable : Any]?
3638

3739
var isNotificationsEnabled: Bool = false

tests/unit-tests/BlankApiClient.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import Foundation
77
@testable import IterableSDK
88

99
class BlankApiClient: ApiClientProtocol {
10+
func trackConsent(consentTimestamp: Int64, email: String?, userId: String?, isUserKnown: Bool) -> IterableSDK.Pending<IterableSDK.SendRequestValue, IterableSDK.SendRequestError> {
11+
Pending()
12+
}
13+
1014

1115
func updateCart(items: [IterableSDK.CommerceItem], createdAt: Int) -> IterableSDK.Pending<IterableSDK.SendRequestValue, IterableSDK.SendRequestError> {
1216
Pending()
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
//
2+
// ConsentTrackingTests.swift
3+
// swift-sdk
4+
//
5+
// Created by Anonymous User Activation on 23/01/2025.
6+
// Copyright © 2025 Iterable. All rights reserved.
7+
//
8+
9+
import XCTest
10+
11+
@testable import IterableSDK
12+
13+
class ConsentTrackingTests: XCTestCase {
14+
private var mockNetworkSession: MockNetworkSession!
15+
private var mockDateProvider: MockDateProvider!
16+
private var mockLocalStorage: MockLocalStorage!
17+
private var internalAPI: InternalIterableAPI!
18+
private static let apiKey = "test-api-key"
19+
private static let testEmail = "[email protected]"
20+
private static let testUserId = "test-user-123"
21+
private static let consentTimestamp: Int64 = 1639490139
22+
23+
override func setUp() {
24+
super.setUp()
25+
mockNetworkSession = MockNetworkSession()
26+
mockDateProvider = MockDateProvider()
27+
mockLocalStorage = MockLocalStorage()
28+
29+
// Set up consent timestamp
30+
mockLocalStorage.visitorConsentTimestamp = ConsentTrackingTests.consentTimestamp
31+
mockLocalStorage.anonymousUsageTrack = true
32+
33+
let config = IterableConfig()
34+
config.enableAnonActivation = true
35+
36+
internalAPI = InternalIterableAPI.initializeForTesting(
37+
apiKey: ConsentTrackingTests.apiKey,
38+
config: config,
39+
dateProvider: mockDateProvider,
40+
networkSession: mockNetworkSession,
41+
localStorage: mockLocalStorage
42+
)
43+
}
44+
45+
override func tearDown() {
46+
mockNetworkSession = nil
47+
mockDateProvider = nil
48+
mockLocalStorage = nil
49+
internalAPI = nil
50+
super.tearDown()
51+
}
52+
53+
// MARK: - Criteria Match Scenario Tests
54+
55+
func testConsentSentAfterCriteriaMatch() {
56+
let expectation = XCTestExpectation(description: "Consent tracked after criteria match")
57+
58+
var consentRequestReceived = false
59+
60+
mockNetworkSession.responseCallback = { url in
61+
let urlString = url.absoluteString
62+
63+
if urlString.contains(Const.Path.trackConsent) {
64+
consentRequestReceived = true
65+
expectation.fulfill()
66+
return MockNetworkSession.MockResponse(statusCode: 200)
67+
}
68+
69+
return MockNetworkSession.MockResponse(statusCode: 200)
70+
}
71+
72+
// Verify consent request body using requestCallback
73+
mockNetworkSession.requestCallback = { urlRequest in
74+
if urlRequest.url?.absoluteString.contains(Const.Path.trackConsent) == true {
75+
let body = urlRequest.httpBody?.json() as? [String: Any]
76+
XCTAssertEqual(body?[JsonKey.consentTimestamp] as? Int, Int(ConsentTrackingTests.consentTimestamp))
77+
XCTAssertEqual(body?[JsonKey.isUserKnown] as? Bool, false)
78+
XCTAssertNotNil(body?[JsonKey.userId] as? String)
79+
XCTAssertNil(body?[JsonKey.email])
80+
}
81+
}
82+
83+
// Directly test the consent sending logic by calling the API method
84+
// This simulates what happens when criteria are met and anonymous user is created
85+
let testUserId = "test-anon-user-id"
86+
87+
internalAPI.apiClient.trackConsent(
88+
consentTimestamp: ConsentTrackingTests.consentTimestamp,
89+
email: nil,
90+
userId: testUserId,
91+
isUserKnown: false
92+
)
93+
94+
wait(for: [expectation], timeout: 5.0)
95+
96+
XCTAssertTrue(consentRequestReceived)
97+
}
98+
99+
func testConsentNotSentWhenNoConsentTimestamp() {
100+
let expectation = XCTestExpectation(description: "No consent request when no timestamp")
101+
expectation.isInverted = true
102+
103+
// Clear consent timestamp
104+
mockLocalStorage.visitorConsentTimestamp = nil
105+
106+
mockNetworkSession.responseCallback = { url in
107+
if url.absoluteString.contains(Const.Path.trackConsent) {
108+
expectation.fulfill() // This should not happen
109+
}
110+
return MockNetworkSession.MockResponse(statusCode: 200)
111+
}
112+
113+
// Simulate criteria being met
114+
mockLocalStorage.criteriaData = "mock-criteria".data(using: .utf8)
115+
internalAPI.track("test-event")
116+
117+
wait(for: [expectation], timeout: 2.0)
118+
}
119+
120+
// MARK: - Replay Scenario Tests
121+
122+
func testConsentSentOnEmailSetForReplayScenario() {
123+
let expectation = XCTestExpectation(description: "Consent tracked on email set")
124+
125+
// Set up replay scenario (no anonymous user ID)
126+
mockLocalStorage.userIdAnnon = nil
127+
128+
mockNetworkSession.responseCallback = { url in
129+
if url.absoluteString.contains(Const.Path.trackConsent) {
130+
expectation.fulfill()
131+
return MockNetworkSession.MockResponse(statusCode: 200)
132+
}
133+
return MockNetworkSession.MockResponse(statusCode: 200)
134+
}
135+
136+
mockNetworkSession.requestCallback = { urlRequest in
137+
if urlRequest.url?.absoluteString.contains(Const.Path.trackConsent) == true {
138+
let body = urlRequest.httpBody?.json() as? [String: Any]
139+
XCTAssertEqual(body?[JsonKey.consentTimestamp] as? Int, Int(ConsentTrackingTests.consentTimestamp))
140+
XCTAssertEqual(body?[JsonKey.isUserKnown] as? Bool, true)
141+
XCTAssertEqual(body?[JsonKey.email] as? String, ConsentTrackingTests.testEmail)
142+
XCTAssertNil(body?[JsonKey.userId])
143+
}
144+
}
145+
146+
internalAPI.setEmail(ConsentTrackingTests.testEmail)
147+
148+
wait(for: [expectation], timeout: 5.0)
149+
}
150+
151+
func testConsentSentOnUserIdSetForReplayScenario() {
152+
let expectation = XCTestExpectation(description: "Consent tracked on userId set")
153+
154+
// Set up replay scenario (no anonymous user ID)
155+
mockLocalStorage.userIdAnnon = nil
156+
157+
mockNetworkSession.responseCallback = { url in
158+
if url.absoluteString.contains(Const.Path.trackConsent) {
159+
expectation.fulfill()
160+
return MockNetworkSession.MockResponse(statusCode: 200)
161+
}
162+
return MockNetworkSession.MockResponse(statusCode: 200)
163+
}
164+
165+
mockNetworkSession.requestCallback = { urlRequest in
166+
if urlRequest.url?.absoluteString.contains(Const.Path.trackConsent) == true {
167+
let body = urlRequest.httpBody?.json() as? [String: Any]
168+
XCTAssertEqual(body?[JsonKey.consentTimestamp] as? Int, Int(ConsentTrackingTests.consentTimestamp))
169+
XCTAssertEqual(body?[JsonKey.isUserKnown] as? Bool, true)
170+
XCTAssertEqual(body?[JsonKey.userId] as? String, ConsentTrackingTests.testUserId)
171+
XCTAssertNil(body?[JsonKey.email])
172+
}
173+
}
174+
175+
internalAPI.setUserId(ConsentTrackingTests.testUserId)
176+
177+
wait(for: [expectation], timeout: 5.0)
178+
}
179+
180+
func testConsentNotSentWhenAnonUserExists() {
181+
let expectation = XCTestExpectation(description: "No consent when anon user exists")
182+
expectation.isInverted = true
183+
184+
// Set up scenario with existing anonymous user (no replay needed)
185+
mockLocalStorage.userIdAnnon = "existing-anon-user-id"
186+
187+
mockNetworkSession.responseCallback = { url in
188+
if url.absoluteString.contains(Const.Path.trackConsent) {
189+
expectation.fulfill() // This should not happen
190+
}
191+
return MockNetworkSession.MockResponse(statusCode: 200)
192+
}
193+
194+
internalAPI.setEmail(ConsentTrackingTests.testEmail)
195+
196+
wait(for: [expectation], timeout: 2.0)
197+
}
198+
199+
func testConsentNotSentWhenNoTracking() {
200+
let expectation = XCTestExpectation(description: "No consent when tracking disabled")
201+
expectation.isInverted = true
202+
203+
// Disable anonymous usage tracking
204+
mockLocalStorage.anonymousUsageTrack = false
205+
206+
mockNetworkSession.responseCallback = { url in
207+
if url.absoluteString.contains(Const.Path.trackConsent) {
208+
expectation.fulfill() // This should not happen
209+
}
210+
return MockNetworkSession.MockResponse(statusCode: 200)
211+
}
212+
213+
internalAPI.setEmail(ConsentTrackingTests.testEmail)
214+
215+
wait(for: [expectation], timeout: 2.0)
216+
}
217+
218+
func testConsentNotSentWhenAnonActivationDisabled() {
219+
let expectation = XCTestExpectation(description: "No consent when anon activation disabled")
220+
expectation.isInverted = true
221+
222+
// Create API with anon activation disabled
223+
let config = IterableConfig()
224+
config.enableAnonActivation = false
225+
226+
let apiWithoutAnonActivation = InternalIterableAPI.initializeForTesting(
227+
apiKey: ConsentTrackingTests.apiKey,
228+
config: config,
229+
dateProvider: mockDateProvider,
230+
networkSession: mockNetworkSession,
231+
localStorage: mockLocalStorage
232+
)
233+
234+
mockNetworkSession.responseCallback = { url in
235+
if url.absoluteString.contains(Const.Path.trackConsent) {
236+
expectation.fulfill() // This should not happen
237+
}
238+
return MockNetworkSession.MockResponse(statusCode: 200)
239+
}
240+
241+
apiWithoutAnonActivation.setEmail(ConsentTrackingTests.testEmail)
242+
243+
wait(for: [expectation], timeout: 2.0)
244+
}
245+
246+
// MARK: - Error Handling Tests
247+
248+
func testConsentTrackingErrorHandling() {
249+
let expectation = XCTestExpectation(description: "Error handling for consent tracking")
250+
251+
mockNetworkSession.responseCallback = { url in
252+
if url.absoluteString.contains(Const.Path.trackConsent) {
253+
expectation.fulfill()
254+
// Simulate network error
255+
return MockNetworkSession.MockResponse(statusCode: 500, error: NSError(domain: "TestError", code: 500, userInfo: nil))
256+
}
257+
return MockNetworkSession.MockResponse(statusCode: 200)
258+
}
259+
260+
internalAPI.setEmail(ConsentTrackingTests.testEmail)
261+
262+
wait(for: [expectation], timeout: 5.0)
263+
// Test should not crash on error - error is logged internally
264+
}
265+
266+
// MARK: - Device Info Tests
267+
268+
func testConsentRequestIncludesDeviceInfo() {
269+
let expectation = XCTestExpectation(description: "Device info included in consent request")
270+
271+
mockNetworkSession.responseCallback = { url in
272+
if url.absoluteString.contains(Const.Path.trackConsent) {
273+
expectation.fulfill()
274+
return MockNetworkSession.MockResponse(statusCode: 200)
275+
}
276+
return MockNetworkSession.MockResponse(statusCode: 200)
277+
}
278+
279+
mockNetworkSession.requestCallback = { urlRequest in
280+
if urlRequest.url?.absoluteString.contains(Const.Path.trackConsent) == true {
281+
let body = urlRequest.httpBody?.json() as? [String: Any]
282+
let deviceInfo = body?[JsonKey.deviceInfo] as? [String: Any]
283+
284+
XCTAssertNotNil(deviceInfo)
285+
XCTAssertNotNil(deviceInfo?[JsonKey.deviceId])
286+
XCTAssertEqual(deviceInfo?[JsonKey.platform] as? String, JsonValue.iOS)
287+
XCTAssertNotNil(deviceInfo?[JsonKey.appPackageName])
288+
}
289+
}
290+
291+
internalAPI.setEmail(ConsentTrackingTests.testEmail)
292+
293+
wait(for: [expectation], timeout: 5.0)
294+
}
295+
}

0 commit comments

Comments
 (0)