Skip to content

Commit a82fcc7

Browse files
Merge pull request #29 from Iterable/feature/ITBL-6407
[ITBL-6407] - Promise and Future Refactor
2 parents b9414a3 + eba288a commit a82fcc7

File tree

8 files changed

+383
-74
lines changed

8 files changed

+383
-74
lines changed

Tests/swift-sdk-swift-tests/IterableActionRunnerTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import OHHTTPStubs
1010

1111
@testable import IterableSDK
1212

13-
let testExpectationTimeout = 15.0
13+
let testExpectationTimeout = 15.0 // How long to wait when we expect to succeed
14+
let testExpectationTimeoutForInverted = 1.0 // How long to wait when we expect to fail
1415

1516
class IterableActionRunnerTests: XCTestCase {
1617
override func setUp() {

Tests/swift-sdk-swift-tests/Mocks.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import UserNotifications
1111
@testable import IterableSDK
1212

1313
class MockNotificationStateProvider : NotificationStateProviderProtocol {
14-
var notificationsEnabled: Promise<Bool, Error> {
15-
let promise = Promise<Bool, Error>()
14+
var notificationsEnabled: Promise<Bool> {
15+
let promise = Promise<Bool>()
1616
DispatchQueue.main.async {
1717
promise.resolve(with: self.enabled)
1818
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
//
2+
//
3+
// Created by Tapash Majumder on 10/26/18.
4+
// Copyright © 2018 Iterable. All rights reserved.
5+
//
6+
7+
import XCTest
8+
9+
@testable import IterableSDK
10+
11+
class PromiseTests: XCTestCase {
12+
struct MyError : Error, CustomStringConvertible {
13+
let message: String
14+
15+
var description: String {
16+
return message
17+
}
18+
}
19+
20+
func testMap() {
21+
let expectation1 = expectation(description: "test map")
22+
let expectation2 = expectation(description: "test map, inverted")
23+
expectation2.isInverted = true
24+
25+
let f1 = createSucessfulFuture(withValue: "zeeString")
26+
let f2 = f1.map {$0.count}
27+
28+
f2.onSuccess { (value) in
29+
XCTAssertEqual(value, "zeeString".count)
30+
expectation1.fulfill()
31+
} .onFailure { _ in
32+
expectation2.fulfill()
33+
}
34+
35+
wait(for: [expectation1], timeout: testExpectationTimeout)
36+
wait(for: [expectation2], timeout: testExpectationTimeoutForInverted)
37+
}
38+
39+
func testMapFailure() {
40+
let expectation1 = expectation(description: "test map failure, inverted")
41+
expectation1.isInverted = true
42+
let expectation2 = expectation(description: "test map failure")
43+
44+
let f1: Future<String> = createFailureFuture(withError: MyError(message: "zeeErrorMessage"))
45+
let f2 = f1.map {$0.count}
46+
47+
f2.onSuccess { (value) in
48+
expectation1.fulfill()
49+
}.onFailure { error in
50+
if let myError = error as? MyError {
51+
XCTAssertEqual(myError.message, "zeeErrorMessage")
52+
expectation2.fulfill()
53+
}
54+
}
55+
56+
wait(for: [expectation1], timeout: testExpectationTimeoutForInverted)
57+
wait(for: [expectation2], timeout: testExpectationTimeout)
58+
}
59+
60+
61+
func testFlatMap() {
62+
let expectation1 = expectation(description: "test flatMap")
63+
let expectation2 = expectation(description: "test flatMap, inverted")
64+
expectation2.isInverted = true
65+
66+
let f1 = createSucessfulFuture(withValue: "zeeString")
67+
68+
let f2 = f1.flatMap { (firstValue) in
69+
return self.createSucessfulFuture(withValue: firstValue + firstValue)
70+
}
71+
72+
f2.onSuccess { (secondValue) in
73+
XCTAssertEqual(secondValue, "zeeStringzeeString")
74+
expectation1.fulfill()
75+
} .onFailure { _ in
76+
expectation2.fulfill()
77+
}
78+
79+
wait(for: [expectation1], timeout: testExpectationTimeout)
80+
wait(for: [expectation2], timeout: testExpectationTimeoutForInverted)
81+
}
82+
83+
// The first future fails
84+
func testFlatMapFailure1() {
85+
let expectation1 = expectation(description: "test flatMap failure, inverted")
86+
expectation1.isInverted = true
87+
let expectation2 = expectation(description: "test flatMap failure")
88+
89+
let f1: Future<String> = createFailureFuture(withError: MyError(message: "zeeErrorMessage"))
90+
91+
let f2 = f1.flatMap { (firstValue) -> Future<String> in
92+
return self.createSucessfulFuture(withValue: "zeeString")
93+
}
94+
95+
f2.onSuccess { (secondValue) in
96+
expectation1.fulfill()
97+
} .onFailure {(error) in
98+
if let myError = error as? MyError {
99+
XCTAssertEqual(myError.message, "zeeErrorMessage")
100+
expectation2.fulfill()
101+
}
102+
}
103+
104+
wait(for: [expectation1], timeout: testExpectationTimeoutForInverted)
105+
wait(for: [expectation2], timeout: testExpectationTimeout)
106+
}
107+
108+
// The second future fails
109+
func testFlatMapFailure2() {
110+
let expectation1 = expectation(description: "test flatMap success, inverted")
111+
expectation1.isInverted = true
112+
let expectation2 = expectation(description: "test flatMap failure")
113+
114+
let f1 = createSucessfulFuture(withValue: "zeeString")
115+
116+
let f2 = f1.flatMap { (firstValue) -> Future<String> in
117+
return self.createFailureFuture(withError: MyError(message: "zeeErrorMessage"))
118+
}
119+
120+
f2.onSuccess { (secondValue) in
121+
expectation1.fulfill()
122+
}.onFailure {(error) in
123+
if let myError = error as? MyError {
124+
XCTAssertEqual(myError.message, "zeeErrorMessage")
125+
expectation2.fulfill()
126+
}
127+
}
128+
129+
wait(for: [expectation1], timeout: testExpectationTimeoutForInverted)
130+
wait(for: [expectation2], timeout: testExpectationTimeout)
131+
}
132+
133+
func testFutureInitWithSuccess() {
134+
let expectation1 = expectation(description: "test future init with success")
135+
let expectation2 = expectation(description: "test future init with success, inverted")
136+
expectation2.isInverted = true
137+
138+
let f1: Future<String> = Promise<String>(value: "zeeValue")
139+
140+
f1.onSuccess { (value) in
141+
XCTAssertEqual(value, "zeeValue")
142+
expectation1.fulfill()
143+
}.onFailure { _ in
144+
expectation2.fulfill()
145+
}
146+
147+
wait(for: [expectation1], timeout: testExpectationTimeout)
148+
wait(for: [expectation2], timeout: testExpectationTimeoutForInverted)
149+
}
150+
151+
func testFutureInitWithFailure() {
152+
let expectation1 = expectation(description: "test future init with failure, inverted")
153+
expectation1.isInverted = true
154+
let expectation2 = expectation(description: "test future init with failure")
155+
156+
let f1: Future<String> = Promise<String>(error: MyError(message: "zeeErrorMessage"))
157+
158+
f1.onSuccess { (value) in
159+
expectation1.fulfill()
160+
}.onFailure { error in
161+
if let myError = error as? MyError {
162+
XCTAssertEqual(myError.message, "zeeErrorMessage")
163+
expectation2.fulfill()
164+
}
165+
}
166+
167+
wait(for: [expectation1], timeout: testExpectationTimeoutForInverted)
168+
wait(for: [expectation2], timeout: testExpectationTimeout)
169+
}
170+
171+
172+
private func createSucessfulFuture<T>(withValue value: T) -> Future<T> {
173+
let future = Promise<T>()
174+
175+
DispatchQueue.main.async {
176+
future.resolve(with: value)
177+
}
178+
179+
return future
180+
}
181+
182+
private func createFailureFuture<T>(withError error: MyError) -> Future<T> {
183+
let future = Promise<T>()
184+
185+
DispatchQueue.main.async {
186+
future.reject(with: error)
187+
}
188+
189+
return future
190+
}
191+
}
192+

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
ACE34AB5213776CB00691224 /* LocalStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACE34AB4213776CB00691224 /* LocalStorageTests.swift */; };
7878
ACE34AB72139D70B00691224 /* LocalStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACE34AB62139D70B00691224 /* LocalStorageProtocol.swift */; };
7979
ACED4C01213F50B30055A497 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACED4C00213F50B30055A497 /* LoggingTests.swift */; };
80+
ACEDF41D2183C2EC000B9BFE /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEDF41C2183C2EC000B9BFE /* Promise.swift */; };
81+
ACEDF41F2183C436000B9BFE /* PromiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEDF41E2183C436000B9BFE /* PromiseTests.swift */; };
8082
ACF560D620E443BF000AAC23 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF560D520E443BF000AAC23 /* AppDelegate.swift */; };
8183
ACF560D820E443BF000AAC23 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF560D720E443BF000AAC23 /* ViewController.swift */; };
8284
ACF560DB20E443BF000AAC23 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ACF560D920E443BF000AAC23 /* Main.storyboard */; };
@@ -236,6 +238,8 @@
236238
ACE34AB4213776CB00691224 /* LocalStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageTests.swift; sourceTree = "<group>"; };
237239
ACE34AB62139D70B00691224 /* LocalStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageProtocol.swift; sourceTree = "<group>"; };
238240
ACED4C00213F50B30055A497 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
241+
ACEDF41C2183C2EC000B9BFE /* Promise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = "<group>"; };
242+
ACEDF41E2183C436000B9BFE /* PromiseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseTests.swift; sourceTree = "<group>"; };
239243
ACF560D320E443BF000AAC23 /* host-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "host-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
240244
ACF560D520E443BF000AAC23 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
241245
ACF560D720E443BF000AAC23 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@@ -461,6 +465,7 @@
461465
AC2C667D20D3111900D46CC9 /* DateProvider.swift */,
462466
AC2C668120D32F2800D46CC9 /* IterableAppIntegrationInternal.swift */,
463467
ACD6116B2107D004003E7F6B /* NetworkHelper.swift */,
468+
ACEDF41C2183C2EC000B9BFE /* Promise.swift */,
464469
);
465470
path = Internal;
466471
sourceTree = "<group>";
@@ -483,6 +488,7 @@
483488
007960ED213F8B2300C53A6A /* DeferredDeeplinkTests.swift */,
484489
ACED4C00213F50B30055A497 /* LoggingTests.swift */,
485490
AC64626A2140AACF0046E1BD /* IterableAPIResponseTests.swift */,
491+
ACEDF41E2183C436000B9BFE /* PromiseTests.swift */,
486492
);
487493
path = "swift-sdk-swift-tests";
488494
sourceTree = "<group>";
@@ -955,6 +961,7 @@
955961
AC2C668220D32F2800D46CC9 /* IterableAppIntegrationInternal.swift in Sources */,
956962
AC72A0C820CF4CE2004D7997 /* ITBConsts.swift in Sources */,
957963
AC776DA6211A1B8A00C27C27 /* IterableRequestUtil.swift in Sources */,
964+
ACEDF41D2183C2EC000B9BFE /* Promise.swift in Sources */,
958965
AC7125EF20D4579E0043BBC1 /* IterableConfig.swift in Sources */,
959966
AC72A0B620CF4C53004D7997 /* IterableConstants.m in Sources */,
960967
AC72A0D120CF4D0B004D7997 /* IterableInAppManager.swift in Sources */,
@@ -986,6 +993,7 @@
986993
AC2C668420D3370600D46CC9 /* Mocks.swift in Sources */,
987994
AC64626B2140AACF0046E1BD /* IterableAPIResponseTests.swift in Sources */,
988995
ACD6116E21080564003E7F6B /* IterableAPITests.swift in Sources */,
996+
ACEDF41F2183C436000B9BFE /* PromiseTests.swift in Sources */,
989997
);
990998
runOnlyForDeploymentPostprocessing = 0;
991999
};

swift-sdk/Internal/IterableAPIInternal.swift

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -111,28 +111,28 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
111111
}
112112

113113
if let userId = userId {
114-
createUser(
115-
withUserId: userId,
116-
onSuccess: { (_) in
117-
self.register(token: token, appName: appName, pushServicePlatform: self.config.pushPlatform, onSuccess: onSuccess, onFailure: onFailure)
118-
},
119-
onFailure: {(errorMessage, _) in
120-
if let errorMessage = errorMessage {
121-
ITBError("Could not create user: \(errorMessage)")
122-
} else {
123-
ITBError()
124-
}
125-
})
114+
// if we are using userId, then create a user first, then register
115+
createUser(withUserId: userId).flatMap({ (_) in
116+
return self.register(token: token, appName: appName, pushServicePlatform: self.config.pushPlatform)
117+
}).onSuccess { (json) in
118+
onSuccess?(json)
119+
}.onFailure { (error) in
120+
if let sendError = error as? SendRequestError {
121+
onFailure?(sendError.errorMessage, sendError.data)
122+
} else {
123+
onFailure?("failed to create user", nil)
124+
}
125+
}
126126
} else {
127127
register(token: token, appName: appName, pushServicePlatform: config.pushPlatform, onSuccess: onSuccess, onFailure: onFailure)
128128
}
129129
}
130130

131-
private func register(token: Data, appName: String, pushServicePlatform: PushServicePlatform, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) {
131+
@discardableResult private func register(token: Data, appName: String, pushServicePlatform: PushServicePlatform, onSuccess: OnSuccessHandler? = nil, onFailure: OnFailureHandler? = nil) -> Future<SendRequestValue> {
132132
guard email != nil || userId != nil else {
133133
ITBError("Both email and userId are nil")
134134
onFailure?("Both email and userId are nil", nil)
135-
return
135+
return SendRequestError.createFailedFuture(reason: "Both email and userId are nil")
136136
}
137137

138138
hexToken = (token as NSData).iteHexadecimalString()
@@ -176,9 +176,9 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
176176
addEmailOrUserId(args: &args)
177177

178178
ITBInfo("sending registerToken request with args \(args)")
179-
if let request = createPostRequest(forPath: .ITBL_PATH_REGISTER_DEVICE_TOKEN, withBody: args) {
180-
sendRequest(request, onSuccess: onSuccess, onFailure: onFailure)
181-
}
179+
return
180+
createPostRequest(forPath: .ITBL_PATH_REGISTER_DEVICE_TOKEN, withBody: args)
181+
.map {sendRequest($0, onSuccess: onSuccess, onFailure: onFailure)} ?? SendRequestError.createFailedFuture(reason: "Couldn't create register request")
182182
}
183183

184184
func disableDeviceForCurrentUser() {
@@ -236,13 +236,13 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
236236
}
237237
}
238238

239-
private func createUser(withUserId userId: String, onSuccess: OnSuccessHandler?, onFailure: OnFailureHandler?) {
239+
private func createUser(withUserId userId: String) -> Future<SendRequestValue> {
240240
var args = [AnyHashable : Any]()
241241
args[.ITBL_KEY_USER_ID] = userId
242242

243-
if let request = createPostRequest(forPath: .ITBL_PATH_CREATE_USER, withBody: args) {
244-
sendRequest(request, onSuccess: onSuccess, onFailure: onFailure)
245-
}
243+
return createPostRequest(forPath: .ITBL_PATH_CREATE_USER, withBody: args).map {
244+
sendRequest($0)
245+
} ?? SendRequestError.createFailedFuture(reason: "Could not create createUser Reqeust")
246246
}
247247

248248
func trackPurchase(_ total: NSNumber, items: [CommerceItem]) {
@@ -530,13 +530,14 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
530530
return IterableRequestUtil.createPostRequest(forApiEndPoint: .ITBL_ENDPOINT_API, path: path, args: [AnyHashable.ITBL_KEY_API_KEY : apiKey], body: body)
531531
}
532532

533-
func sendRequest(_ request: URLRequest, onSuccess: OnSuccessHandler? = nil, onFailure: OnFailureHandler? = nil) {
534-
NetworkHelper.sendRequest(request, usingSession: networkSession).observe { (result) in
535-
switch result {
536-
case .value(let json):
537-
onSuccess?(json)
538-
case .error(let failureInfo):
539-
onFailure?(failureInfo.errorMessage, failureInfo.data)
533+
@discardableResult func sendRequest(_ request: URLRequest, onSuccess: OnSuccessHandler? = nil, onFailure: OnFailureHandler? = nil) -> Future<SendRequestValue> {
534+
return NetworkHelper.sendRequest(request, usingSession: networkSession).onSuccess { (json) in
535+
onSuccess?(json)
536+
}.onFailure { (failureInfo) in
537+
if let sendError = failureInfo as? SendRequestError {
538+
onFailure?(sendError.errorMessage, sendError.data)
539+
} else {
540+
onFailure?("send request failed", nil)
540541
}
541542
}
542543
}
@@ -603,9 +604,9 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
603604
guard config.autoPushRegistration == true, isEitherUserIdOrEmailSet() else {
604605
return
605606
}
606-
607-
notificationStateProvider.notificationsEnabled.observe { (authResult) in
608-
if case let Result.value(authorized) = authResult, authorized == true {
607+
608+
notificationStateProvider.notificationsEnabled.onSuccess { (authorized) in
609+
if authorized {
609610
self.notificationStateProvider.registerForRemoteNotifications()
610611
}
611612
}
@@ -793,14 +794,13 @@ final class IterableAPIInternal : NSObject, PushTrackerProtocol {
793794
return
794795
}
795796

796-
NetworkHelper.sendRequest(request, usingSession: networkSession).observe {(result) in
797-
switch result {
798-
case .value(let json):
799-
self.handleDDL(json: json)
800-
case .error(let failureInfo):
801-
if let errorMessage = failureInfo.errorMessage {
802-
ITBError(errorMessage)
803-
}
797+
NetworkHelper.sendRequest(request, usingSession: networkSession).onSuccess { (json) in
798+
self.handleDDL(json: json)
799+
}.onFailure { (failureInfo) in
800+
if let sendError = failureInfo as? SendRequestError, let errorMessage = sendError.errorMessage {
801+
ITBError(errorMessage)
802+
} else {
803+
ITBError("failed to send handleDDl request")
804804
}
805805
}
806806
}

0 commit comments

Comments
 (0)