Skip to content

Commit 8201d84

Browse files
authored
Merge branch 'master' into evan/MOB-7285-prepare-for-6.4.16
2 parents 8087c4f + 5a29ec5 commit 8201d84

File tree

6 files changed

+190
-17
lines changed

6 files changed

+190
-17
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@
172172
5B5AA717284F1A6D0093FED4 /* MockNetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5AA710284F1A6D0093FED4 /* MockNetworkSession.swift */; };
173173
5B6C3C1127CE871F00B9A753 /* NavInboxSessionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C3C1027CE871F00B9A753 /* NavInboxSessionUITests.swift */; };
174174
5B88BC482805D09D004016E5 /* NetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B88BC472805D09D004016E5 /* NetworkSession.swift */; };
175+
9FF05EAC2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
176+
9FF05EAD2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
177+
9FF05EAE2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
178+
9FF05EAF2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
179+
9FF05EB02AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
180+
9FF05EB12AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
181+
9FF05EB22AFEA5FA005311F7 /* MockAuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */; };
175182
AC02480822791E2100495FB9 /* IterableInboxNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02480722791E2100495FB9 /* IterableInboxNavigationViewController.swift */; };
176183
AC02CAA6234E50B5006617E0 /* RegistrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */; };
177184
AC03094B21E532470003A288 /* InAppPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC03094A21E532470003A288 /* InAppPersistence.swift */; };
@@ -568,6 +575,7 @@
568575
5B6C3C1027CE871F00B9A753 /* NavInboxSessionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavInboxSessionUITests.swift; sourceTree = "<group>"; };
569576
5B88BC472805D09D004016E5 /* NetworkSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSession.swift; sourceTree = "<group>"; };
570577
5BFC7CED27FC9AF300E77479 /* inbox-ui-tests-app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "inbox-ui-tests-app.entitlements"; sourceTree = "<group>"; };
578+
9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthManager.swift; sourceTree = "<group>"; };
571579
AC02480722791E2100495FB9 /* IterableInboxNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxNavigationViewController.swift; sourceTree = "<group>"; };
572580
AC02CAA5234E50B5006617E0 /* RegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTests.swift; sourceTree = "<group>"; };
573581
AC03094A21E532470003A288 /* InAppPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPersistence.swift; sourceTree = "<group>"; };
@@ -1360,6 +1368,7 @@
13601368
5588DF7D28C04494000697D7 /* MockUrlDelegate.swift */,
13611369
5588DF8D28C044DE000697D7 /* MockUrlOpener.swift */,
13621370
5588DFD528C04683000697D7 /* MockWebView.swift */,
1371+
9FF05EAB2AFEA5FA005311F7 /* MockAuthManager.swift */,
13631372
);
13641373
path = common;
13651374
sourceTree = "<group>";
@@ -2089,6 +2098,7 @@
20892098
ACA2A91A24AB266F001DFD17 /* Mocks.swift in Sources */,
20902099
5588DFAB28C045AE000697D7 /* MockInAppFetcher.swift in Sources */,
20912100
AC29D05C24B5A7E000A9E019 /* CI.swift in Sources */,
2101+
9FF05EB12AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
20922102
);
20932103
runOnlyForDeploymentPostprocessing = 0;
20942104
};
@@ -2102,6 +2112,7 @@
21022112
AC2C668720D3435700D46CC9 /* ActionRunnerTests.swift in Sources */,
21032113
00CB31B621096129004ACDEC /* TestUtils.swift in Sources */,
21042114
AC89661E2124FBCE0051A6CD /* AutoRegistrationTests.swift in Sources */,
2115+
9FF05EAF2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
21052116
ACA8D1A921965B7D001B1332 /* InAppTests.swift in Sources */,
21062117
5588DFB928C045E3000697D7 /* MockInAppDelegate.swift in Sources */,
21072118
5588DFD128C0465E000697D7 /* MockAPNSTypeChecker.swift in Sources */,
@@ -2198,6 +2209,7 @@
21982209
buildActionMask = 2147483647;
21992210
files = (
22002211
5588DFB828C045E3000697D7 /* MockInAppDelegate.swift in Sources */,
2212+
9FF05EAE2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
22012213
5588DF7028C0442D000697D7 /* MockDateProvider.swift in Sources */,
22022214
5588DFF028C046FF000697D7 /* MockMessageViewControllerEventTracker.swift in Sources */,
22032215
ACB37AB124026C1E0093A8EA /* SampleInboxViewDelegateImplementations.swift in Sources */,
@@ -2261,6 +2273,7 @@
22612273
ACC6A8502323910D003CC4BE /* UITestsHelper.swift in Sources */,
22622274
5588DFAA28C045AE000697D7 /* MockInAppFetcher.swift in Sources */,
22632275
ACFF4287246569D300FDF10D /* CommonExtensions.swift in Sources */,
2276+
9FF05EB02AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
22642277
);
22652278
runOnlyForDeploymentPostprocessing = 0;
22662279
};
@@ -2287,6 +2300,7 @@
22872300
5588DFCE28C0465E000697D7 /* MockAPNSTypeChecker.swift in Sources */,
22882301
5588DFDE28C046B7000697D7 /* MockLocalStorage.swift in Sources */,
22892302
ACA8D1A52196309C001B1332 /* Common.swift in Sources */,
2303+
9FF05EAC2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
22902304
5588DFB628C045E3000697D7 /* MockInAppDelegate.swift in Sources */,
22912305
5588DF8628C044BE000697D7 /* MockCustomActionDelegate.swift in Sources */,
22922306
AC995F992166EE490099A184 /* CommonMocks.swift in Sources */,
@@ -2325,6 +2339,7 @@
23252339
5588DF7C28C04463000697D7 /* MockNotificationResponse.swift in Sources */,
23262340
5588DFD428C0465E000697D7 /* MockAPNSTypeChecker.swift in Sources */,
23272341
5588DFE428C046B7000697D7 /* MockLocalStorage.swift in Sources */,
2342+
9FF05EB22AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
23282343
ACC362C924D2CA8C002C67BA /* Common.swift in Sources */,
23292344
ACC362C524D2C190002C67BA /* TaskProcessorTests.swift in Sources */,
23302345
AC2AED4224EBC60C000EE5F3 /* TaskRunnerTests.swift in Sources */,
@@ -2343,6 +2358,7 @@
23432358
5588DFE728C046D7000697D7 /* MockInboxState.swift in Sources */,
23442359
ACFF42A424656CCE00FDF10D /* ViewController.swift in Sources */,
23452360
5588DF7F28C04494000697D7 /* MockUrlDelegate.swift in Sources */,
2361+
9FF05EAD2AFEA5FA005311F7 /* MockAuthManager.swift in Sources */,
23462362
5588DF8728C044BE000697D7 /* MockCustomActionDelegate.swift in Sources */,
23472363
5588DFD728C04683000697D7 /* MockWebView.swift in Sources */,
23482364
5588DFEF28C046FF000697D7 /* MockMessageViewControllerEventTracker.swift in Sources */,

swift-sdk/Internal/NetworkHelper.swift

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ extension NetworkError: LocalizedError {
2828
}
2929

3030
struct NetworkHelper {
31+
static let maxRetryCount = 5
32+
static let retryDelaySeconds = 2
33+
3134
static func getData(fromUrl url: URL, usingSession networkSession: NetworkSessionProtocol) -> Pending<Data, Error> {
3235
let fulfill = Fulfill<Data, Error>()
3336

@@ -52,8 +55,9 @@ struct NetworkHelper {
5255
static func sendRequest<T>(_ request: URLRequest,
5356
converter: @escaping (Data) throws -> T?,
5457
usingSession networkSession: NetworkSessionProtocol) -> Pending<T, NetworkError> {
55-
#if NETWORK_DEBUG
58+
5659
let requestId = IterableUtil.generateUUID()
60+
#if NETWORK_DEBUG
5761
print()
5862
print("====================================================>")
5963
print("sending request: \(request)")
@@ -73,29 +77,65 @@ struct NetworkHelper {
7377
#endif
7478

7579
let fulfill = Fulfill<T, NetworkError>()
76-
77-
networkSession.makeRequest(request) { data, response, error in
78-
let result = createResultFromNetworkResponse(data: data,
79-
converter: converter,
80-
response: response,
81-
error: error)
8280

83-
switch result {
84-
case let .success(value):
85-
#if NETWORK_DEBUG
86-
print("request with id: \(requestId) successfully sent, response:")
87-
print(value)
88-
#endif
89-
fulfill.resolve(with: value)
90-
case let .failure(error):
81+
func sendRequestWithRetries(request: URLRequest, requestId: String, retriesLeft: Int) {
82+
networkSession.makeRequest(request) { data, response, error in
83+
let result = createResultFromNetworkResponse(data: data,
84+
converter: converter,
85+
response: response,
86+
error: error)
87+
switch result {
88+
case let .success(value):
89+
handleSuccess(requestId: requestId, value: value)
90+
case let .failure(error):
91+
handleFailure(requestId: requestId, request: request, error: error, retriesLeft: retriesLeft)
92+
}
93+
}
94+
}
95+
96+
func handleSuccess(requestId: String, value: T) {
97+
#if NETWORK_DEBUG
98+
print("request with id: \(requestId) successfully sent, response:")
99+
print(value)
100+
#endif
101+
fulfill.resolve(with: value)
102+
}
103+
104+
func handleFailure(requestId: String, request: URLRequest, error: NetworkError, retriesLeft: Int) {
105+
if shouldRetry(error: error, retriesLeft: retriesLeft) {
106+
retryRequest(requestId: requestId, request: request, error: error, retriesLeft: retriesLeft)
107+
} else {
91108
#if NETWORK_DEBUG
92109
print("request with id: \(requestId) errored")
93110
print(error)
94111
#endif
95112
fulfill.reject(with: error)
96113
}
114+
97115
}
98116

117+
func shouldRetry(error: NetworkError, retriesLeft: Int) -> Bool {
118+
return error.httpStatusCode ?? 0 >= 500 && retriesLeft > 0
119+
}
120+
121+
func retryRequest(requestId: String, request: URLRequest, error: NetworkError, retriesLeft: Int) {
122+
#if NETWORK_DEBUG
123+
print("retry attempt: \(maxRetryCount-retriesLeft+1) for url: \(request.url?.absoluteString ?? "")")
124+
print(error)
125+
#endif
126+
127+
var delay: DispatchTimeInterval = .seconds(0)
128+
if retriesLeft <= 3 {
129+
delay = .seconds(retryDelaySeconds)
130+
}
131+
132+
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
133+
sendRequestWithRetries(request: request, requestId: requestId, retriesLeft: retriesLeft - 1)
134+
}
135+
}
136+
137+
sendRequestWithRetries(request: request, requestId: requestId, retriesLeft: maxRetryCount)
138+
99139
return fulfill
100140
}
101141

swift-sdk/Internal/RequestProcessorUtil.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct RequestProcessorUtil {
1818
.onError { error in
1919
if error.httpStatusCode == 401, error.iterableCode == JsonValue.Code.invalidJwtPayload {
2020
ITBError("invalid JWT token, trying again: \(error.reason ?? "")")
21-
authManager?.requestNewAuthToken(hasFailedPriorAuth: true) { _ in
21+
authManager?.requestNewAuthToken(hasFailedPriorAuth: false) { _ in
2222
requestProvider().onSuccess { json in
2323
reportSuccess(result: result, value: json, successHandler: onSuccess, identifier: identifier)
2424
}.onError { error in

tests/common/MockAuthManager.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// MockAuthManager.swift
3+
// swift-sdk
4+
//
5+
// Created by HARDIK MASHRU on 08/11/23.
6+
// Copyright © 2023 Iterable. All rights reserved.
7+
//
8+
import Foundation
9+
@testable import IterableSDK
10+
11+
class MockAuthManager: IterableAuthManagerProtocol {
12+
var shouldRetry = true
13+
var retryWasRequested = false
14+
15+
func getAuthToken() -> String? {
16+
return "AuthToken"
17+
}
18+
19+
func resetFailedAuthCount() {
20+
21+
}
22+
23+
func requestNewAuthToken(hasFailedPriorAuth: Bool, onSuccess: ((String?) -> Void)?) {
24+
if shouldRetry {
25+
// Simulate the authManager obtaining a new token
26+
retryWasRequested = true
27+
shouldRetry = false
28+
onSuccess?("newAuthToken")
29+
} else {
30+
// Simulate failing to obtain a new token
31+
retryWasRequested = false
32+
onSuccess?(nil)
33+
}
34+
}
35+
36+
func setNewToken(_ newToken: String) {
37+
38+
}
39+
40+
func logoutUser() {
41+
42+
}
43+
}

tests/unit-tests/AuthTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,9 @@ class AuthTests: XCTestCase {
525525
XCTAssertNil(internalAPI.auth.authToken)
526526
}
527527

528-
func testAuthTokenRefreshRetryOnlyOnce() {
528+
func testAuthTokenRefreshRetryOnlyOnce() throws {
529+
throw XCTSkip("skipping this test - auth token retries should occur more than once")
530+
529531
let condition1 = expectation(description: "\(#function) - callback not called correctly in some form")
530532
condition1.expectedFulfillmentCount = 2
531533

tests/unit-tests/IterableAPIResponseTests.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,54 @@ class IterableAPIResponseTests: XCTestCase {
100100
wait(for: [xpectation], timeout: testExpectationTimeout)
101101
}
102102

103+
func testRetryOnInvalidJwtPayload() {
104+
let xpectation = expectation(description: "retry on 401 with invalidJWTPayload")
105+
106+
// Mock the dependencies and requestProvider for your test
107+
let authManager = MockAuthManager()
108+
109+
let networkErrorSession = MockNetworkSession() { _ in
110+
MockNetworkSession.MockResponse(statusCode: 401,
111+
data: ["code":"InvalidJwtPayload"].toJsonData(),
112+
delay: 1)
113+
}
114+
115+
let networkSuccessSession = MockNetworkSession() { _ in
116+
MockNetworkSession.MockResponse(statusCode: 200,
117+
data: ["msg": "success"].toJsonData(),
118+
delay: 1)
119+
}
120+
121+
let urlErrorRequest = createApiClient(networkSession: networkErrorSession).convertToURLRequest(iterableRequest: IterableRequest.post(PostRequest(path: "", args: nil, body: [:])))!
122+
123+
124+
let urlSuccessRequest = createApiClient(networkSession: networkSuccessSession).convertToURLRequest(iterableRequest: IterableRequest.post(PostRequest(path: "", args: nil, body: [:])))!
125+
126+
let requestProvider: () -> Pending<SendRequestValue, SendRequestError> = {
127+
if authManager.retryWasRequested {
128+
return RequestSender.sendRequest(urlSuccessRequest, usingSession: networkSuccessSession)
129+
}
130+
return RequestSender.sendRequest(urlErrorRequest, usingSession: networkErrorSession)
131+
}
132+
133+
let result = RequestProcessorUtil.sendRequest(
134+
requestProvider: requestProvider,
135+
authManager: authManager,
136+
requestIdentifier: "TestIdentifier"
137+
)
138+
139+
result.onSuccess { value in
140+
xpectation.fulfill()
141+
XCTAssert(true)
142+
}.onError { error in
143+
if authManager.retryWasRequested {
144+
xpectation.fulfill()
145+
}
146+
}
147+
148+
waitForExpectations(timeout: testExpectationTimeout)
149+
}
150+
103151
func testResponseCode401() { // 401 = unauthorized
104152
let xpectation = expectation(description: "401")
105153
let iterableRequest = IterableRequest.post(PostRequest(path: "", args: nil, body: [:]))
@@ -152,6 +200,30 @@ class IterableAPIResponseTests: XCTestCase {
152200
wait(for: [xpectation], timeout: testExpectationTimeout)
153201
}
154202

203+
func testSendRequestWithRetry() {
204+
let xpectation = expectation(description: "retry on status code >= 500")
205+
206+
let networkSession = MockNetworkSession { _ in
207+
MockNetworkSession.MockResponse(statusCode: 503,
208+
data: Data(),
209+
delay: 0)
210+
}
211+
212+
let iterableRequest = IterableRequest.post(PostRequest(path: "", args: nil, body: [:]))
213+
214+
let apiClient = createApiClient(networkSession: networkSession)
215+
var urlRequest = apiClient.convertToURLRequest(iterableRequest: iterableRequest)!
216+
urlRequest.timeoutInterval = 1
217+
218+
RequestSender.sendRequest(urlRequest, usingSession: networkSession).onError { sendError in
219+
xpectation.fulfill()
220+
XCTAssert(sendError.reason!.lowercased().contains("internal server error"))
221+
}
222+
223+
wait(for: [xpectation], timeout: testExpectationTimeout)
224+
}
225+
226+
155227
func testNetworkTimeoutResponse() {
156228
let xpectation = expectation(description: "timeout network response")
157229

0 commit comments

Comments
 (0)