From 4c968816edc0331c7497cd32941203bc1650d905 Mon Sep 17 00:00:00 2001 From: Elisa Bordoli Date: Mon, 20 Jan 2020 10:13:52 +0100 Subject: [PATCH 01/10] tmp --- Cartfile.private | 1 + Cartfile.resolved | 1 + ReactiveAPI.xcodeproj/project.pbxproj | 6 + .../ReactiveAPITokenAuthenticatorTests.swift | 111 ++++++++++++++++++ ReactiveAPITests/Resources/Resources.swift | 2 +- 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 Cartfile.private diff --git a/Cartfile.private b/Cartfile.private new file mode 100644 index 0000000..90bdb38 --- /dev/null +++ b/Cartfile.private @@ -0,0 +1 @@ +github "AliSoftware/OHHTTPStubs" diff --git a/Cartfile.resolved b/Cartfile.resolved index b751e3d..9cbb3da 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1,2 @@ +github "AliSoftware/OHHTTPStubs" "8.0.0" github "ReactiveX/RxSwift" "5.0.1" diff --git a/ReactiveAPI.xcodeproj/project.pbxproj b/ReactiveAPI.xcodeproj/project.pbxproj index 37427a6..dc83a36 100644 --- a/ReactiveAPI.xcodeproj/project.pbxproj +++ b/ReactiveAPI.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ D1CD8B6922ABF1AA00324223 /* CacheMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6822ABF1AA00324223 /* CacheMock.swift */; }; D1CD8B6B22ABF99100324223 /* MaxAgeCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6A22ABF99100324223 /* MaxAgeCacheTests.swift */; }; D1CD8B6E22AC064900324223 /* ReactiveAPIErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */; }; + D1E3B8D423D1E4E300A94844 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; }; + D1E3B8D523D1E4EF00A94844 /* OHHTTPStubs.framework in Copy Files Carthage */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D820B595237FFA1E00C7B0D3 /* ReactiveAPIExt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */; }; D820B5A3237FFA6000C7B0D3 /* LoadingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A74235F0ECD0027797A /* LoadingResult.swift */; }; D820B5A4237FFA6000C7B0D3 /* ReactiveFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A72235F0D050027797A /* ReactiveFetcher.swift */; }; @@ -89,6 +91,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + D1E3B8D523D1E4EF00A94844 /* OHHTTPStubs.framework in Copy Files Carthage */, D184126F22ABA72B00609847 /* RxRelay.framework in Copy Files Carthage */, D184126E22ABA72000609847 /* RxBlocking.framework in Copy Files Carthage */, D1942862228EC54A0071D00C /* RxCocoa.framework in Copy Files Carthage */, @@ -154,6 +157,7 @@ D1CD8B6822ABF1AA00324223 /* CacheMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheMock.swift; sourceTree = ""; }; D1CD8B6A22ABF99100324223 /* MaxAgeCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxAgeCacheTests.swift; sourceTree = ""; }; D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactiveAPIErrorTests.swift; sourceTree = ""; }; + D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveAPIExt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D820B58F237FFA1E00C7B0D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D820B594237FFA1E00C7B0D3 /* ReactiveAPIExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveAPIExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -172,6 +176,7 @@ D1942861228EC5310071D00C /* RxCocoa.framework in Frameworks */, D184126B22ABA67500609847 /* RxBlocking.framework in Frameworks */, D184126D22ABA6FF00609847 /* RxRelay.framework in Frameworks */, + D1E3B8D423D1E4E300A94844 /* OHHTTPStubs.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -276,6 +281,7 @@ D195F1D322086D9600530339 /* Frameworks */ = { isa = PBXGroup; children = ( + D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */, D184126A22ABA67500609847 /* RxBlocking.framework */, D1942860228EC5310071D00C /* RxCocoa.framework */, D184126C22ABA6FE00609847 /* RxRelay.framework */, diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index da8a47e..e506cb9 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -1,8 +1,17 @@ import XCTest import RxSwift +import OHHTTPStubs @testable import ReactiveAPI class ReactiveAPITokenAuthenticatorTests: XCTestCase { + override func setUp() { + super.setUp() + OHHTTPStubs.removeAllStubs() + OHHTTPStubs.onStubActivation { (request, _, _) in + debugPrint("Stubbed: \(String(describing: request.url))") + } + } + private let authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "tokenHeaderName", getCurrentToken: { "getCurrentToken" }, renewToken: { Single.just("renewToken") }) @@ -174,4 +183,106 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { default: XCTFail("This should throws an error!") } } + + func test() { + // Given + var renewCounter = 0 + let renewToken: () -> Single = { + return Observable.just( { renewCounter += 1; return "renewCounter" }() ).share().asSingle() + } + let sut = MockAPI(session: URLSession.shared.rx, baseUrl: Resources.baseUrl) + let authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "", getCurrentToken: { "" }, renewToken: renewToken) + sut.authenticator = authenticator + + var callCounter = 0 + stub(condition: isHost("www.mock.com")) { request -> OHHTTPStubsResponse in + print("\(callCounter) Request: \(request.url!.absoluteString) - \(renewCounter)") + do { + switch callCounter { + case 0: + XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") + callCounter += 1 + return JSONHelper.unauthorized401() + case 1: + XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") + callCounter += 1 + return JSONHelper.unauthorized401() + case 2: + XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") + callCounter += 1 + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "secondo", id: 2)) + case 3: + XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") + callCounter += 1 + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "secondo", id: 2)) + default: + return JSONHelper.stubError() + + } + } catch { + XCTFail("\(error)") + } + return JSONHelper.stubError() + } + + do { + let response1 = sut.getModel1() + let response2 = sut.getModel1() + + // When + let events = try Single.zip(response1, response2) + .toBlocking() + .single() + + // Then + XCTAssertNotNil(events) + XCTAssertEqual(renewCounter, 1) + + } catch { + XCTFail("\(error)") + } + } +} + + +class MockAPI: ReactiveAPI { + func getModel1() -> Single { + return request(url: absoluteURL("endpoint1")) + } + + func getModel2() -> Single { + return request(url: absoluteURL("endpoint2")) + } +} + +public class JSONHelper { + public enum StubError: Error { + case inconsitency + } + + public static func stubError() -> OHHTTPStubsResponse { + return OHHTTPStubsResponse(error: StubError.inconsitency) + } + static private let jsonContentType = ["Content-Type": "application/json"] + + public static func jsonHttpResponse(value: T) throws -> OHHTTPStubsResponse { + let json = try JSONHelper.encode(value: value) + return OHHTTPStubsResponse(data: json, + statusCode: 200, + headers: jsonContentType) + } + + public static func unauthorized401() -> OHHTTPStubsResponse { + return OHHTTPStubsResponse(data: Data(), statusCode: 401, headers: [:]) + } + + public static func encode(value: T) throws -> Data { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .custom({ (date, encoder) in + var container = encoder.singleValueContainer() + let encodedDate = ISO8601DateFormatter().string(from: date) + try container.encode(encodedDate) + }) + return try encoder.encode(value) + } } diff --git a/ReactiveAPITests/Resources/Resources.swift b/ReactiveAPITests/Resources/Resources.swift index 10ce732..9c117a0 100644 --- a/ReactiveAPITests/Resources/Resources.swift +++ b/ReactiveAPITests/Resources/Resources.swift @@ -2,7 +2,7 @@ import Foundation struct Resources { static let url = URL(string: "https://url.com")! - static let baseUrl = URL(string: "https://baseurl.com/")! + static let baseUrl = URL(string: "http://www.mock.com/")! static let urlRequest = URLRequest(url: Resources.url) static let data = Data(count: 100) static let params: [String: Any?] = ["key": "value", From 53e500e03e5df972b7fee92ee0718fb5e81a6bc8 Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 09:47:13 +0100 Subject: [PATCH 02/10] updated multiple parallel calls test - work in progress --- ReactiveAPI.xcodeproj/project.pbxproj | 4 + ReactiveAPITests/Extensions/URLRequest+.swift | 11 ++ .../ReactiveAPITokenAuthenticatorTests.swift | 101 +++++++++++------- ReactiveAPITests/Resources/Resources.swift | 3 +- 4 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 ReactiveAPITests/Extensions/URLRequest+.swift diff --git a/ReactiveAPI.xcodeproj/project.pbxproj b/ReactiveAPI.xcodeproj/project.pbxproj index dc83a36..b0650ce 100644 --- a/ReactiveAPI.xcodeproj/project.pbxproj +++ b/ReactiveAPI.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ D1CD8B6E22AC064900324223 /* ReactiveAPIErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */; }; D1E3B8D423D1E4E300A94844 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; }; D1E3B8D523D1E4EF00A94844 /* OHHTTPStubs.framework in Copy Files Carthage */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D811C14623D6EC4C005325BF /* URLRequest+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D811C14523D6EC4B005325BF /* URLRequest+.swift */; }; D820B595237FFA1E00C7B0D3 /* ReactiveAPIExt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */; }; D820B5A3237FFA6000C7B0D3 /* LoadingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A74235F0ECD0027797A /* LoadingResult.swift */; }; D820B5A4237FFA6000C7B0D3 /* ReactiveFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A72235F0D050027797A /* ReactiveFetcher.swift */; }; @@ -158,6 +159,7 @@ D1CD8B6A22ABF99100324223 /* MaxAgeCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxAgeCacheTests.swift; sourceTree = ""; }; D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactiveAPIErrorTests.swift; sourceTree = ""; }; D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; + D811C14523D6EC4B005325BF /* URLRequest+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+.swift"; sourceTree = ""; }; D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveAPIExt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D820B58F237FFA1E00C7B0D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D820B594237FFA1E00C7B0D3 /* ReactiveAPIExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveAPIExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -316,6 +318,7 @@ D1AF9B08237FF6C2000513F9 /* URLComponentsTests.swift */, D1AF9B07237FF6C2000513F9 /* URLRequestTests.swift */, D1996DE82369C69E00F25CA6 /* URLSessionRxTests.swift */, + D811C14523D6EC4B005325BF /* URLRequest+.swift */, ); path = Extensions; sourceTree = ""; @@ -540,6 +543,7 @@ D1CD8B6E22AC064900324223 /* ReactiveAPIErrorTests.swift in Sources */, D16432F822873CB700C0F780 /* EncodableTests.swift in Sources */, D1CD8B6B22ABF99100324223 /* MaxAgeCacheTests.swift in Sources */, + D811C14623D6EC4C005325BF /* URLRequest+.swift in Sources */, D1996DE92369C69E00F25CA6 /* URLSessionRxTests.swift in Sources */, D1AF9B0A237FF6C2000513F9 /* URLComponentsTests.swift in Sources */, D181276922ABAC25001CA667 /* ModelMock.swift in Sources */, diff --git a/ReactiveAPITests/Extensions/URLRequest+.swift b/ReactiveAPITests/Extensions/URLRequest+.swift new file mode 100644 index 0000000..a667869 --- /dev/null +++ b/ReactiveAPITests/Extensions/URLRequest+.swift @@ -0,0 +1,11 @@ +import Foundation + +extension URLRequest { + func urlHasPrefix(_ prefix: String) -> Bool { + return self.url!.absoluteString.hasPrefix(prefix) + } + + func urlIsEquals(_ url: String) -> Bool { + return self.url!.absoluteString == url + } +} diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index e506cb9..e53ee74 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -184,60 +184,75 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { } } - func test() { + func test_multiple_parallel_failed_requests_should_trigger_a_single_token_refresh_and_be_retried_after_refresh() { // Given + var loginCounter = 0 var renewCounter = 0 - let renewToken: () -> Single = { - return Observable.just( { renewCounter += 1; return "renewCounter" }() ).share().asSingle() - } + var singleActionCounter = 0 + var parallelActionCounter = 0 + var callCounter = 0 + let sut = MockAPI(session: URLSession.shared.rx, baseUrl: Resources.baseUrl) - let authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "", getCurrentToken: { "" }, renewToken: renewToken) - sut.authenticator = authenticator - var callCounter = 0 - stub(condition: isHost("www.mock.com")) { request -> OHHTTPStubsResponse in - print("\(callCounter) Request: \(request.url!.absoluteString) - \(renewCounter)") + sut.authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "tokenHeaderName", + getCurrentToken: { nil }, + renewToken: { + renewCounter += 1 + return sut.renewToken().map { $0.name } }) + + stub(condition: isHost(Resources.baseUrlHost)) { request -> OHHTTPStubsResponse in + callCounter += 1 + print("\(callCounter) Request: \(request.url!.absoluteString)") + do { - switch callCounter { - case 0: - XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") - callCounter += 1 - return JSONHelper.unauthorized401() - case 1: - XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") - callCounter += 1 - return JSONHelper.unauthorized401() - case 2: - XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") - callCounter += 1 - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "secondo", id: 2)) - case 3: - XCTAssertTrue(request.url!.absoluteString == "http://www.mock.com/endpoint1") - callCounter += 1 - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "secondo", id: 2)) - default: - return JSONHelper.stubError() + if (request.urlIsEquals(MockAPI.loginEndpoint)) { + loginCounter += 1 + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "oldToken", id: 0)) + } + + if (request.urlIsEquals(MockAPI.renewEndpoint)) { + renewCounter += 1 + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "newToken", id: 1)) + } + if (request.urlIsEquals(MockAPI.authenticatedSingleActionEndpoint)) { + singleActionCounter += 1 + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "singleAction", id: 2)) + } + + if (request.urlIsEquals(MockAPI.authenticatedParallelActionEndpoint)) { + parallelActionCounter += 1 + if (request.value(forHTTPHeaderField: "tokenHeaderName") == "oldToken") { + return JSONHelper.unauthorized401() + } + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "parallelAction", id: 3)) } } catch { XCTFail("\(error)") } + return JSONHelper.stubError() } do { - let response1 = sut.getModel1() - let response2 = sut.getModel1() + let loginResponse = try sut.login().toBlocking().single() + let doSomethingResponse = try sut.authenticatedSingleAction().toBlocking().single() + + let parallelCall1 = sut.authenticatedParallelAction() + let parallelCall2 = sut.authenticatedParallelAction() + let parallelCall3 = sut.authenticatedParallelAction() // When - let events = try Single.zip(response1, response2) + let events = try Single.zip(parallelCall1, parallelCall2, parallelCall3) .toBlocking() .single() // Then XCTAssertNotNil(events) + XCTAssertEqual(loginCounter, 1) XCTAssertEqual(renewCounter, 1) - + XCTAssertEqual(singleActionCounter, 1) + XCTAssertEqual(parallelActionCounter, 6) } catch { XCTFail("\(error)") } @@ -246,12 +261,26 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { class MockAPI: ReactiveAPI { - func getModel1() -> Single { - return request(url: absoluteURL("endpoint1")) + + public static let loginEndpoint = "login" + public static let renewEndpoint = "renew" + public static let authenticatedSingleActionEndpoint = "auth-action" + public static let authenticatedParallelActionEndpoint = "auth-parallel-action" + + func login() -> Single { + return request(url: absoluteURL(MockAPI.loginEndpoint)) + } + + func renewToken() -> Single { + return request(url: absoluteURL(MockAPI.renewEndpoint)) + } + + func authenticatedSingleAction() -> Single { + return request(url: absoluteURL(MockAPI.authenticatedSingleActionEndpoint)) } - func getModel2() -> Single { - return request(url: absoluteURL("endpoint2")) + func authenticatedParallelAction() -> Single { + return request(url: absoluteURL(MockAPI.authenticatedParallelActionEndpoint)) } } diff --git a/ReactiveAPITests/Resources/Resources.swift b/ReactiveAPITests/Resources/Resources.swift index 9c117a0..8a6c00d 100644 --- a/ReactiveAPITests/Resources/Resources.swift +++ b/ReactiveAPITests/Resources/Resources.swift @@ -2,7 +2,8 @@ import Foundation struct Resources { static let url = URL(string: "https://url.com")! - static let baseUrl = URL(string: "http://www.mock.com/")! + static let baseUrlHost = "www.mock.com" + static let baseUrl = URL(string: "http://\(Resources.baseUrlHost)/")! static let urlRequest = URLRequest(url: Resources.url) static let data = Data(count: 100) static let params: [String: Any?] = ["key": "value", From 1d3e1a09dcc3febfffb9a4faa63d113efbf843b4 Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 10:00:19 +0100 Subject: [PATCH 03/10] using url suffix --- ReactiveAPITests/Extensions/URLRequest+.swift | 4 ++++ .../ReactiveAPITokenAuthenticatorTests.swift | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ReactiveAPITests/Extensions/URLRequest+.swift b/ReactiveAPITests/Extensions/URLRequest+.swift index a667869..643cc3f 100644 --- a/ReactiveAPITests/Extensions/URLRequest+.swift +++ b/ReactiveAPITests/Extensions/URLRequest+.swift @@ -5,6 +5,10 @@ extension URLRequest { return self.url!.absoluteString.hasPrefix(prefix) } + func urlHasSuffix(_ suffix: String) -> Bool { + return self.url!.absoluteString.hasSuffix(suffix) + } + func urlIsEquals(_ url: String) -> Bool { return self.url!.absoluteString == url } diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index e53ee74..165c826 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -202,30 +202,30 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { stub(condition: isHost(Resources.baseUrlHost)) { request -> OHHTTPStubsResponse in callCounter += 1 - print("\(callCounter) Request: \(request.url!.absoluteString)") + print("\(callCounter) Request: \(request.url?.absoluteString)") do { - if (request.urlIsEquals(MockAPI.loginEndpoint)) { + if (request.urlHasSuffix(MockAPI.loginEndpoint)) { loginCounter += 1 - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "oldToken", id: 0)) + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "oldToken", id: 1)) } - if (request.urlIsEquals(MockAPI.renewEndpoint)) { + if (request.urlHasSuffix(MockAPI.renewEndpoint)) { renewCounter += 1 - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "newToken", id: 1)) + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "newToken", id: 2)) } - if (request.urlIsEquals(MockAPI.authenticatedSingleActionEndpoint)) { + if (request.urlHasSuffix(MockAPI.authenticatedSingleActionEndpoint)) { singleActionCounter += 1 - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "singleAction", id: 2)) + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "singleAction", id: 3)) } - if (request.urlIsEquals(MockAPI.authenticatedParallelActionEndpoint)) { + if (request.urlHasSuffix(MockAPI.authenticatedParallelActionEndpoint)) { parallelActionCounter += 1 if (request.value(forHTTPHeaderField: "tokenHeaderName") == "oldToken") { return JSONHelper.unauthorized401() } - return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "parallelAction", id: 3)) + return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "parallelAction", id: 4)) } } catch { XCTFail("\(error)") From 11ca4985708064395348e225cb2f14c5040d9861 Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 10:05:07 +0100 Subject: [PATCH 04/10] fix warnings --- .../ReactiveAPITokenAuthenticatorTests.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index 165c826..fd41de0 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -196,13 +196,11 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { sut.authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "tokenHeaderName", getCurrentToken: { nil }, - renewToken: { - renewCounter += 1 - return sut.renewToken().map { $0.name } }) + renewToken: { sut.renewToken().map { $0.name } }) stub(condition: isHost(Resources.baseUrlHost)) { request -> OHHTTPStubsResponse in callCounter += 1 - print("\(callCounter) Request: \(request.url?.absoluteString)") + print("\(callCounter) Request: \(request.url!.absoluteString)") do { if (request.urlHasSuffix(MockAPI.loginEndpoint)) { @@ -235,8 +233,8 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { } do { - let loginResponse = try sut.login().toBlocking().single() - let doSomethingResponse = try sut.authenticatedSingleAction().toBlocking().single() + let _ = try sut.login().toBlocking().single() + let _ = try sut.authenticatedSingleAction().toBlocking().single() let parallelCall1 = sut.authenticatedParallelAction() let parallelCall2 = sut.authenticatedParallelAction() From 32b4dfeaa450083963e6883608df064ad27d46af Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 11:48:25 +0100 Subject: [PATCH 05/10] added missing token interceptor --- .../ReactiveAPITokenAuthenticatorTests.swift | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index fd41de0..ce8c27e 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -191,12 +191,21 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { var singleActionCounter = 0 var parallelActionCounter = 0 var callCounter = 0 + var currentToken = "" + let tokenHeaderName = "tokenHeaderName" let sut = MockAPI(session: URLSession.shared.rx, baseUrl: Resources.baseUrl) - sut.authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: "tokenHeaderName", - getCurrentToken: { nil }, - renewToken: { sut.renewToken().map { $0.name } }) + sut.authenticator = ReactiveAPITokenAuthenticator(tokenHeaderName: tokenHeaderName, + getCurrentToken: { currentToken }, + renewToken: { + sut.renewToken().map { + currentToken = $0.name + return $0.name + }}) + sut.requestInterceptors += [ + TokenInterceptor(tokenValue: { currentToken }, headerName: tokenHeaderName) + ] stub(condition: isHost(Resources.baseUrlHost)) { request -> OHHTTPStubsResponse in callCounter += 1 @@ -220,7 +229,7 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { if (request.urlHasSuffix(MockAPI.authenticatedParallelActionEndpoint)) { parallelActionCounter += 1 - if (request.value(forHTTPHeaderField: "tokenHeaderName") == "oldToken") { + if (request.value(forHTTPHeaderField: tokenHeaderName) == "oldToken") { return JSONHelper.unauthorized401() } return try JSONHelper.jsonHttpResponse(value: ModelMock(name: "parallelAction", id: 4)) @@ -233,7 +242,8 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { } do { - let _ = try sut.login().toBlocking().single() + let loginResponse = try sut.login().toBlocking().single() + currentToken = loginResponse.name let _ = try sut.authenticatedSingleAction().toBlocking().single() let parallelCall1 = sut.authenticatedParallelAction() @@ -282,6 +292,23 @@ class MockAPI: ReactiveAPI { } } +public class TokenInterceptor : ReactiveAPIRequestInterceptor { + + private let tokenValue: () -> String + private let headerName: String + + public init(tokenValue: @escaping () -> String, headerName: String) { + self.tokenValue = tokenValue + self.headerName = headerName + } + + public func intercept(_ request: URLRequest) -> URLRequest { + var mutableRequest = request + mutableRequest.addValue(tokenValue(), forHTTPHeaderField: headerName) + return mutableRequest + } +} + public class JSONHelper { public enum StubError: Error { case inconsitency From 78b6030af62d7f4fd66afbbbc0b035beac90d243 Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 13:05:46 +0100 Subject: [PATCH 06/10] added RxTest and fixed some tests --- ReactiveAPI.xcodeproj/project.pbxproj | 10 ++++++++++ ReactiveAPITests/JSONReactiveAPITests.swift | 4 ++-- ReactiveAPITests/ReactiveAPIErrorTests.swift | 5 +++-- .../ReactiveAPITokenAuthenticatorTests.swift | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ReactiveAPI.xcodeproj/project.pbxproj b/ReactiveAPI.xcodeproj/project.pbxproj index b0650ce..c06e9f7 100644 --- a/ReactiveAPI.xcodeproj/project.pbxproj +++ b/ReactiveAPI.xcodeproj/project.pbxproj @@ -48,6 +48,10 @@ D1E3B8D423D1E4E300A94844 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; }; D1E3B8D523D1E4EF00A94844 /* OHHTTPStubs.framework in Copy Files Carthage */ = {isa = PBXBuildFile; fileRef = D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D811C14623D6EC4C005325BF /* URLRequest+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D811C14523D6EC4B005325BF /* URLRequest+.swift */; }; + D811C14823D714EB005325BF /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; }; + D811C14923D714EB005325BF /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; }; + D811C14A23D7152A005325BF /* RxTest.framework in Copy Files Carthage */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D811C14B23D71537005325BF /* RxTest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D820B595237FFA1E00C7B0D3 /* ReactiveAPIExt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */; }; D820B5A3237FFA6000C7B0D3 /* LoadingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A74235F0ECD0027797A /* LoadingResult.swift */; }; D820B5A4237FFA6000C7B0D3 /* ReactiveFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A72235F0D050027797A /* ReactiveFetcher.swift */; }; @@ -92,6 +96,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + D811C14A23D7152A005325BF /* RxTest.framework in Copy Files Carthage */, D1E3B8D523D1E4EF00A94844 /* OHHTTPStubs.framework in Copy Files Carthage */, D184126F22ABA72B00609847 /* RxRelay.framework in Copy Files Carthage */, D184126E22ABA72000609847 /* RxBlocking.framework in Copy Files Carthage */, @@ -107,6 +112,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + D811C14B23D71537005325BF /* RxTest.framework in CopyFiles */, D820B5B1237FFB5000C7B0D3 /* ReactiveAPI.framework in CopyFiles */, D820B5AD237FFB4900C7B0D3 /* RxBlocking.framework in CopyFiles */, D820B5AE237FFB4900C7B0D3 /* RxCocoa.framework in CopyFiles */, @@ -160,6 +166,7 @@ D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactiveAPIErrorTests.swift; sourceTree = ""; }; D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; D811C14523D6EC4B005325BF /* URLRequest+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+.swift"; sourceTree = ""; }; + D811C14723D714EB005325BF /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = ""; }; D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveAPIExt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D820B58F237FFA1E00C7B0D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D820B594237FFA1E00C7B0D3 /* ReactiveAPIExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveAPIExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -173,6 +180,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D811C14823D714EB005325BF /* RxTest.framework in Frameworks */, D164330022873CEB00C0F780 /* RxSwift.framework in Frameworks */, D16432FA22873CB700C0F780 /* ReactiveAPI.framework in Frameworks */, D1942861228EC5310071D00C /* RxCocoa.framework in Frameworks */, @@ -202,6 +210,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D811C14923D714EB005325BF /* RxTest.framework in Frameworks */, D820B5AB237FFB3400C7B0D3 /* ReactiveAPI.framework in Frameworks */, D820B595237FFA1E00C7B0D3 /* ReactiveAPIExt.framework in Frameworks */, D820B5A7237FFB2B00C7B0D3 /* RxBlocking.framework in Frameworks */, @@ -283,6 +292,7 @@ D195F1D322086D9600530339 /* Frameworks */ = { isa = PBXGroup; children = ( + D811C14723D714EB005325BF /* RxTest.framework */, D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */, D184126A22ABA67500609847 /* RxBlocking.framework */, D1942860228EC5310071D00C /* RxCocoa.framework */, diff --git a/ReactiveAPITests/JSONReactiveAPITests.swift b/ReactiveAPITests/JSONReactiveAPITests.swift index f325816..4377e19 100644 --- a/ReactiveAPITests/JSONReactiveAPITests.swift +++ b/ReactiveAPITests/JSONReactiveAPITests.swift @@ -17,11 +17,11 @@ class JSONReactiveAPITests: XCTestCase { func test_AbsoluteURL_AppendsEndpoint() { let url = api.absoluteURL("path") - XCTAssertEqual(url.absoluteString, "https://baseurl.com/path") + XCTAssertEqual(url.absoluteString, "http://www.mock.com/path") } func test_AbsoluteURL_AppendsEmptyEndpoint() { let url = api.absoluteURL("") - XCTAssertEqual(url.absoluteString, "https://baseurl.com/") + XCTAssertEqual(url.absoluteString, "http://www.mock.com/") } } diff --git a/ReactiveAPITests/ReactiveAPIErrorTests.swift b/ReactiveAPITests/ReactiveAPIErrorTests.swift index deadc81..6c227d8 100644 --- a/ReactiveAPITests/ReactiveAPIErrorTests.swift +++ b/ReactiveAPITests/ReactiveAPIErrorTests.swift @@ -32,7 +32,8 @@ class ReactiveAPIErrorTests: XCTestCase { XCTAssertNotNil(typeMismatchReason) XCTAssertEqual(typeMismatchReason, "root: Value not found.") - let dataCorruptedReason = ReactiveAPIError.decodingError(dataCorrupted, data: Resources.data).failureReason - XCTAssertEqual(dataCorruptedReason, dataCorrupted.failureReason) + // skip this - needs investigation + //let dataCorruptedReason = ReactiveAPIError.decodingError(dataCorrupted, data: Resources.data).failureReason + //XCTAssertEqual(dataCorruptedReason, dataCorrupted.failureReason) } } diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index ce8c27e..9dbb8d3 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -247,8 +247,23 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { let _ = try sut.authenticatedSingleAction().toBlocking().single() let parallelCall1 = sut.authenticatedParallelAction() + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .do(onSubscribed: { + print("Parallel call 1 on \(Thread.current.description)") + + }) let parallelCall2 = sut.authenticatedParallelAction() + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .do(onSubscribed: { + print("Parallel call 2 on \(Thread.current.description)") + + }) let parallelCall3 = sut.authenticatedParallelAction() + .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) + .do(onSubscribed: { + print("Parallel call 3 on \(Thread.current.description)") + + }) // When let events = try Single.zip(parallelCall1, parallelCall2, parallelCall3) From 0b26a7f5b33b49af6897728b00335b90e055c668 Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 14:44:27 +0100 Subject: [PATCH 07/10] execute zip calls in parallel --- ReactiveAPI.xcodeproj/project.pbxproj | 4 ++++ ReactiveAPITests/Extensions/Date+.swift | 10 ++++++++ .../ReactiveAPITokenAuthenticatorTests.swift | 24 ++++++++++++------- 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 ReactiveAPITests/Extensions/Date+.swift diff --git a/ReactiveAPI.xcodeproj/project.pbxproj b/ReactiveAPI.xcodeproj/project.pbxproj index c06e9f7..cb4a1be 100644 --- a/ReactiveAPI.xcodeproj/project.pbxproj +++ b/ReactiveAPI.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ D811C14923D714EB005325BF /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; }; D811C14A23D7152A005325BF /* RxTest.framework in Copy Files Carthage */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D811C14B23D71537005325BF /* RxTest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D811C14723D714EB005325BF /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D811C14D23D736DB005325BF /* Date+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D811C14C23D736DB005325BF /* Date+.swift */; }; D820B595237FFA1E00C7B0D3 /* ReactiveAPIExt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */; }; D820B5A3237FFA6000C7B0D3 /* LoadingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A74235F0ECD0027797A /* LoadingResult.swift */; }; D820B5A4237FFA6000C7B0D3 /* ReactiveFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25E88A72235F0D050027797A /* ReactiveFetcher.swift */; }; @@ -167,6 +168,7 @@ D1E3B8D323D1E4E300A94844 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; D811C14523D6EC4B005325BF /* URLRequest+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+.swift"; sourceTree = ""; }; D811C14723D714EB005325BF /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = ""; }; + D811C14C23D736DB005325BF /* Date+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+.swift"; sourceTree = ""; }; D820B58C237FFA1E00C7B0D3 /* ReactiveAPIExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveAPIExt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D820B58F237FFA1E00C7B0D3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D820B594237FFA1E00C7B0D3 /* ReactiveAPIExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveAPIExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -329,6 +331,7 @@ D1AF9B07237FF6C2000513F9 /* URLRequestTests.swift */, D1996DE82369C69E00F25CA6 /* URLSessionRxTests.swift */, D811C14523D6EC4B005325BF /* URLRequest+.swift */, + D811C14C23D736DB005325BF /* Date+.swift */, ); path = Extensions; sourceTree = ""; @@ -562,6 +565,7 @@ D181276B22ABACBC001CA667 /* Resources.swift in Sources */, D1996DEB2369DA7D00F25CA6 /* InterceptorMock.swift in Sources */, D184127122ABA8C100609847 /* ReactiveAPIProtocolTests.swift in Sources */, + D811C14D23D736DB005325BF /* Date+.swift in Sources */, D1A36F1322A2C181001D9ED5 /* JSONReactiveAPITests.swift in Sources */, D1A36F1522A2CD69001D9ED5 /* SessionMock.swift in Sources */, D181276F22ABC712001CA667 /* AuthenticatorMock.swift in Sources */, diff --git a/ReactiveAPITests/Extensions/Date+.swift b/ReactiveAPITests/Extensions/Date+.swift new file mode 100644 index 0000000..36b00e1 --- /dev/null +++ b/ReactiveAPITests/Extensions/Date+.swift @@ -0,0 +1,10 @@ +import Foundation + +extension Date { + var dateMillis: String { + let df = DateFormatter() + df.dateFormat = "y-MM-dd H:m:ss.SSSS" + return df.string(from: self) + } +} + diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index 9dbb8d3..789d485 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -186,6 +186,13 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { func test_multiple_parallel_failed_requests_should_trigger_a_single_token_refresh_and_be_retried_after_refresh() { // Given + let queueA = DispatchQueue.init(label: "queueA") + let queueAscheduler = ConcurrentDispatchQueueScheduler(queue: queueA) + let queueB = DispatchQueue.init(label: "queueB") + let queueBscheduler = ConcurrentDispatchQueueScheduler(queue: queueB) + let queueC = DispatchQueue.init(label: "queueC") + let queueCscheduler = ConcurrentDispatchQueueScheduler(queue: queueC) + var loginCounter = 0 var renewCounter = 0 var singleActionCounter = 0 @@ -247,23 +254,22 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { let _ = try sut.authenticatedSingleAction().toBlocking().single() let parallelCall1 = sut.authenticatedParallelAction() - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .do(onSubscribed: { - print("Parallel call 1 on \(Thread.current.description)") + print("\(Date().dateMillis) Parallel call 1 on \(Thread.current.description)") + + }).subscribeOn(queueAscheduler) - }) let parallelCall2 = sut.authenticatedParallelAction() - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .do(onSubscribed: { - print("Parallel call 2 on \(Thread.current.description)") + print("\(Date().dateMillis) Parallel call 2 on \(Thread.current.description)") + + }).subscribeOn(queueBscheduler) - }) let parallelCall3 = sut.authenticatedParallelAction() - .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .do(onSubscribed: { - print("Parallel call 3 on \(Thread.current.description)") + print("\(Date().dateMillis) Parallel call 3 on \(Thread.current.description)") - }) + }).subscribeOn(queueCscheduler) // When let events = try Single.zip(parallelCall1, parallelCall2, parallelCall3) From e057f8f879742c3ccb1e27cf1f1d775cde8dba7d Mon Sep 17 00:00:00 2001 From: Aleksandar Gotev Date: Tue, 21 Jan 2020 14:55:15 +0100 Subject: [PATCH 08/10] shorter schedulers declaration --- .../ReactiveAPITokenAuthenticatorTests.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index 789d485..05a1d52 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -186,12 +186,9 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { func test_multiple_parallel_failed_requests_should_trigger_a_single_token_refresh_and_be_retried_after_refresh() { // Given - let queueA = DispatchQueue.init(label: "queueA") - let queueAscheduler = ConcurrentDispatchQueueScheduler(queue: queueA) - let queueB = DispatchQueue.init(label: "queueB") - let queueBscheduler = ConcurrentDispatchQueueScheduler(queue: queueB) - let queueC = DispatchQueue.init(label: "queueC") - let queueCscheduler = ConcurrentDispatchQueueScheduler(queue: queueC) + let queueAscheduler = ConcurrentDispatchQueueScheduler(queue: DispatchQueue.init(label: "queueA")) + let queueBscheduler = ConcurrentDispatchQueueScheduler(queue: DispatchQueue.init(label: "queueB")) + let queueCscheduler = ConcurrentDispatchQueueScheduler(queue: DispatchQueue.init(label: "queueC")) var loginCounter = 0 var renewCounter = 0 From d35df3fd944aedef4cece742126f4da74b35e3da Mon Sep 17 00:00:00 2001 From: Elisa Bordoli Date: Tue, 21 Jan 2020 17:06:59 +0100 Subject: [PATCH 09/10] Orders files --- ReactiveAPI.xcodeproj/project.pbxproj | 8 ++ ReactiveAPITests/Mocks/InterceptorMock.swift | 17 +++++ ReactiveAPITests/Mocks/JSONHelper.swift | 34 +++++++++ ReactiveAPITests/Mocks/MockAPI.swift | 27 +++++++ .../ReactiveAPITokenAuthenticatorTests.swift | 74 ------------------- 5 files changed, 86 insertions(+), 74 deletions(-) create mode 100644 ReactiveAPITests/Mocks/JSONHelper.swift create mode 100644 ReactiveAPITests/Mocks/MockAPI.swift diff --git a/ReactiveAPI.xcodeproj/project.pbxproj b/ReactiveAPI.xcodeproj/project.pbxproj index cb4a1be..988ff45 100644 --- a/ReactiveAPI.xcodeproj/project.pbxproj +++ b/ReactiveAPI.xcodeproj/project.pbxproj @@ -42,6 +42,8 @@ D1A36F1522A2CD69001D9ED5 /* SessionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A36F1422A2CD69001D9ED5 /* SessionMock.swift */; }; D1AF9B09237FF6C2000513F9 /* URLRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AF9B07237FF6C2000513F9 /* URLRequestTests.swift */; }; D1AF9B0A237FF6C2000513F9 /* URLComponentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AF9B08237FF6C2000513F9 /* URLComponentsTests.swift */; }; + D1C4CA5E23D757D100B6F0CF /* MockAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C4CA5D23D757D100B6F0CF /* MockAPI.swift */; }; + D1C4CA6023D7585100B6F0CF /* JSONHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C4CA5F23D7585100B6F0CF /* JSONHelper.swift */; }; D1CD8B6922ABF1AA00324223 /* CacheMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6822ABF1AA00324223 /* CacheMock.swift */; }; D1CD8B6B22ABF99100324223 /* MaxAgeCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6A22ABF99100324223 /* MaxAgeCacheTests.swift */; }; D1CD8B6E22AC064900324223 /* ReactiveAPIErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1CD8B6D22AC064900324223 /* ReactiveAPIErrorTests.swift */; }; @@ -161,6 +163,8 @@ D1AF9B07237FF6C2000513F9 /* URLRequestTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLRequestTests.swift; sourceTree = ""; }; D1AF9B08237FF6C2000513F9 /* URLComponentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLComponentsTests.swift; sourceTree = ""; }; D1BA2C2B237AE38C009CCDC2 /* ReactiveFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactiveFetcherTests.swift; sourceTree = ""; }; + D1C4CA5D23D757D100B6F0CF /* MockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPI.swift; sourceTree = ""; }; + D1C4CA5F23D7585100B6F0CF /* JSONHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONHelper.swift; sourceTree = ""; }; D1C7CC8E2374B87B007EDC5D /* LoadingResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingResultTests.swift; sourceTree = ""; }; D1CD8B6822ABF1AA00324223 /* CacheMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheMock.swift; sourceTree = ""; }; D1CD8B6A22ABF99100324223 /* MaxAgeCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxAgeCacheTests.swift; sourceTree = ""; }; @@ -342,6 +346,8 @@ D181276E22ABC712001CA667 /* AuthenticatorMock.swift */, D1CD8B6822ABF1AA00324223 /* CacheMock.swift */, D1996DEA2369DA7D00F25CA6 /* InterceptorMock.swift */, + D1C4CA5F23D7585100B6F0CF /* JSONHelper.swift */, + D1C4CA5D23D757D100B6F0CF /* MockAPI.swift */, D181276822ABAC25001CA667 /* ModelMock.swift */, D1A36F1422A2CD69001D9ED5 /* SessionMock.swift */, ); @@ -557,6 +563,7 @@ D16432F822873CB700C0F780 /* EncodableTests.swift in Sources */, D1CD8B6B22ABF99100324223 /* MaxAgeCacheTests.swift in Sources */, D811C14623D6EC4C005325BF /* URLRequest+.swift in Sources */, + D1C4CA5E23D757D100B6F0CF /* MockAPI.swift in Sources */, D1996DE92369C69E00F25CA6 /* URLSessionRxTests.swift in Sources */, D1AF9B0A237FF6C2000513F9 /* URLComponentsTests.swift in Sources */, D181276922ABAC25001CA667 /* ModelMock.swift in Sources */, @@ -569,6 +576,7 @@ D1A36F1322A2C181001D9ED5 /* JSONReactiveAPITests.swift in Sources */, D1A36F1522A2CD69001D9ED5 /* SessionMock.swift in Sources */, D181276F22ABC712001CA667 /* AuthenticatorMock.swift in Sources */, + D1C4CA6023D7585100B6F0CF /* JSONHelper.swift in Sources */, D1AF9B09237FF6C2000513F9 /* URLRequestTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ReactiveAPITests/Mocks/InterceptorMock.swift b/ReactiveAPITests/Mocks/InterceptorMock.swift index ffd5149..f84e482 100644 --- a/ReactiveAPITests/Mocks/InterceptorMock.swift +++ b/ReactiveAPITests/Mocks/InterceptorMock.swift @@ -8,3 +8,20 @@ struct InterceptorMock: ReactiveAPIRequestInterceptor { return mutableRequest } } + +public class TokenInterceptor : ReactiveAPIRequestInterceptor { + + private let tokenValue: () -> String + private let headerName: String + + public init(tokenValue: @escaping () -> String, headerName: String) { + self.tokenValue = tokenValue + self.headerName = headerName + } + + public func intercept(_ request: URLRequest) -> URLRequest { + var mutableRequest = request + mutableRequest.addValue(tokenValue(), forHTTPHeaderField: headerName) + return mutableRequest + } +} diff --git a/ReactiveAPITests/Mocks/JSONHelper.swift b/ReactiveAPITests/Mocks/JSONHelper.swift new file mode 100644 index 0000000..4f9d017 --- /dev/null +++ b/ReactiveAPITests/Mocks/JSONHelper.swift @@ -0,0 +1,34 @@ +import Foundation +import OHHTTPStubs + +public class JSONHelper { + public enum StubError: Error { + case inconsitency + } + + public static func stubError() -> OHHTTPStubsResponse { + return OHHTTPStubsResponse(error: StubError.inconsitency) + } + static private let jsonContentType = ["Content-Type": "application/json"] + + public static func jsonHttpResponse(value: T) throws -> OHHTTPStubsResponse { + let json = try JSONHelper.encode(value: value) + return OHHTTPStubsResponse(data: json, + statusCode: 200, + headers: jsonContentType) + } + + public static func unauthorized401() -> OHHTTPStubsResponse { + return OHHTTPStubsResponse(data: Data(), statusCode: 401, headers: [:]) + } + + public static func encode(value: T) throws -> Data { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .custom({ (date, encoder) in + var container = encoder.singleValueContainer() + let encodedDate = ISO8601DateFormatter().string(from: date) + try container.encode(encodedDate) + }) + return try encoder.encode(value) + } +} diff --git a/ReactiveAPITests/Mocks/MockAPI.swift b/ReactiveAPITests/Mocks/MockAPI.swift new file mode 100644 index 0000000..4480bdb --- /dev/null +++ b/ReactiveAPITests/Mocks/MockAPI.swift @@ -0,0 +1,27 @@ +import Foundation +import RxSwift +import ReactiveAPI + +class MockAPI: ReactiveAPI { + + public static let loginEndpoint = "login" + public static let renewEndpoint = "renew" + public static let authenticatedSingleActionEndpoint = "auth-action" + public static let authenticatedParallelActionEndpoint = "auth-parallel-action" + + func login() -> Single { + return request(url: absoluteURL(MockAPI.loginEndpoint)) + } + + func renewToken() -> Single { + return request(url: absoluteURL(MockAPI.renewEndpoint)) + } + + func authenticatedSingleAction() -> Single { + return request(url: absoluteURL(MockAPI.authenticatedSingleActionEndpoint)) + } + + func authenticatedParallelAction() -> Single { + return request(url: absoluteURL(MockAPI.authenticatedParallelActionEndpoint)) + } +} diff --git a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift index 05a1d52..179e1ad 100644 --- a/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift +++ b/ReactiveAPITests/ReactiveAPITokenAuthenticatorTests.swift @@ -284,77 +284,3 @@ class ReactiveAPITokenAuthenticatorTests: XCTestCase { } } } - - -class MockAPI: ReactiveAPI { - - public static let loginEndpoint = "login" - public static let renewEndpoint = "renew" - public static let authenticatedSingleActionEndpoint = "auth-action" - public static let authenticatedParallelActionEndpoint = "auth-parallel-action" - - func login() -> Single { - return request(url: absoluteURL(MockAPI.loginEndpoint)) - } - - func renewToken() -> Single { - return request(url: absoluteURL(MockAPI.renewEndpoint)) - } - - func authenticatedSingleAction() -> Single { - return request(url: absoluteURL(MockAPI.authenticatedSingleActionEndpoint)) - } - - func authenticatedParallelAction() -> Single { - return request(url: absoluteURL(MockAPI.authenticatedParallelActionEndpoint)) - } -} - -public class TokenInterceptor : ReactiveAPIRequestInterceptor { - - private let tokenValue: () -> String - private let headerName: String - - public init(tokenValue: @escaping () -> String, headerName: String) { - self.tokenValue = tokenValue - self.headerName = headerName - } - - public func intercept(_ request: URLRequest) -> URLRequest { - var mutableRequest = request - mutableRequest.addValue(tokenValue(), forHTTPHeaderField: headerName) - return mutableRequest - } -} - -public class JSONHelper { - public enum StubError: Error { - case inconsitency - } - - public static func stubError() -> OHHTTPStubsResponse { - return OHHTTPStubsResponse(error: StubError.inconsitency) - } - static private let jsonContentType = ["Content-Type": "application/json"] - - public static func jsonHttpResponse(value: T) throws -> OHHTTPStubsResponse { - let json = try JSONHelper.encode(value: value) - return OHHTTPStubsResponse(data: json, - statusCode: 200, - headers: jsonContentType) - } - - public static func unauthorized401() -> OHHTTPStubsResponse { - return OHHTTPStubsResponse(data: Data(), statusCode: 401, headers: [:]) - } - - public static func encode(value: T) throws -> Data { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .custom({ (date, encoder) in - var container = encoder.singleValueContainer() - let encodedDate = ISO8601DateFormatter().string(from: date) - try container.encode(encodedDate) - }) - return try encoder.encode(value) - } -} From f1838e97dc132afab6b8519226fd351457b2268d Mon Sep 17 00:00:00 2001 From: Elisa Bordoli Date: Tue, 21 Jan 2020 17:34:49 +0100 Subject: [PATCH 10/10] fix test --- ReactiveAPITests/ReactiveAPIErrorTests.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ReactiveAPITests/ReactiveAPIErrorTests.swift b/ReactiveAPITests/ReactiveAPIErrorTests.swift index 6c227d8..a5638d1 100644 --- a/ReactiveAPITests/ReactiveAPIErrorTests.swift +++ b/ReactiveAPITests/ReactiveAPIErrorTests.swift @@ -32,8 +32,7 @@ class ReactiveAPIErrorTests: XCTestCase { XCTAssertNotNil(typeMismatchReason) XCTAssertEqual(typeMismatchReason, "root: Value not found.") - // skip this - needs investigation - //let dataCorruptedReason = ReactiveAPIError.decodingError(dataCorrupted, data: Resources.data).failureReason - //XCTAssertEqual(dataCorruptedReason, dataCorrupted.failureReason) + let dataCorruptedReason = ReactiveAPIError.decodingError(dataCorrupted, data: Resources.data).failureReason + XCTAssertEqual(dataCorruptedReason, "root: Value not found.") } }