// // RequestInterceptorTests.swift // // Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // @testable import Alamofire import Foundation import XCTest fileprivate struct MockError: Error {} fileprivate struct RetryError: Error {} // MARK: - final class RetryResultTestCase: BaseTestCase { func testRetryRequiredProperty() { // Given, When let retry = RetryResult.retry let retryWithDelay = RetryResult.retryWithDelay(1.0) let doNotRetry = RetryResult.doNotRetry let doNotRetryWithError = RetryResult.doNotRetryWithError(MockError()) // Then XCTAssertTrue(retry.retryRequired) XCTAssertTrue(retryWithDelay.retryRequired) XCTAssertFalse(doNotRetry.retryRequired) XCTAssertFalse(doNotRetryWithError.retryRequired) } func testDelayProperty() { // Given, When let retry = RetryResult.retry let retryWithDelay = RetryResult.retryWithDelay(1.0) let doNotRetry = RetryResult.doNotRetry let doNotRetryWithError = RetryResult.doNotRetryWithError(MockError()) // Then XCTAssertEqual(retry.delay, nil) XCTAssertEqual(retryWithDelay.delay, 1.0) XCTAssertEqual(doNotRetry.delay, nil) XCTAssertEqual(doNotRetryWithError.delay, nil) } func testErrorProperty() { // Given, When let retry = RetryResult.retry let retryWithDelay = RetryResult.retryWithDelay(1.0) let doNotRetry = RetryResult.doNotRetry let doNotRetryWithError = RetryResult.doNotRetryWithError(MockError()) // Then XCTAssertNil(retry.error) XCTAssertNil(retryWithDelay.error) XCTAssertNil(doNotRetry.error) XCTAssertTrue(doNotRetryWithError.error is MockError) } } // MARK: - final class AdapterTestCase: BaseTestCase { func testThatAdapterCallsAdaptHandler() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() var adapted = false let adapter = Adapter { request, _, completion in adapted = true completion(.success(request)) } var result: AFResult! // When adapter.adapt(urlRequest, for: session) { result = $0 } // Then XCTAssertTrue(adapted) XCTAssertTrue(result.isSuccess) } func testThatAdapterCallsRequestRetrierDefaultImplementationInProtocolExtension() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) let adapter = Adapter { request, _, completion in completion(.success(request)) } var result: RetryResult! // When adapter.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .doNotRetry) } func testThatAdapterCanBeImplementedAsynchronously() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() var adapted = false let adapter = Adapter { request, _, completion in adapted = true DispatchQueue.main.async { completion(.success(request)) } } var result: AFResult! let completesExpectation = expectation(description: "adapter completes") // When adapter.adapt(urlRequest, for: session) { result = $0 completesExpectation.fulfill() } waitForExpectations(timeout: timeout) // Then XCTAssertTrue(adapted) XCTAssertTrue(result.isSuccess) } } // MARK: - final class RetrierTestCase: BaseTestCase { func testThatRetrierCallsRetryHandler() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) var retried = false let retrier = Retrier { request, session, error, completion in retried = true completion(.retry) } var result: RetryResult! // When retrier.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertTrue(retried) XCTAssertEqual(result, .retry) } func testThatRetrierCallsRequestAdapterDefaultImplementationInProtocolExtension() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() let retrier = Retrier { _, _, _, completion in completion(.retry) } var result: AFResult! // When retrier.adapt(urlRequest, for: session) { result = $0 } // Then XCTAssertTrue(result.isSuccess) } func testThatRetrierCanBeImplementedAsynchronously() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) var retried = false let retrier = Retrier { request, session, error, completion in retried = true DispatchQueue.main.async { completion(.retry) } } var result: RetryResult! let completesExpectation = expectation(description: "retrier completes") // When retrier.retry(request, for: session, dueTo: MockError()) { result = $0 completesExpectation.fulfill() } waitForExpectations(timeout: timeout) // Then XCTAssertTrue(retried) XCTAssertEqual(result, .retry) } } // MARK: - final class InterceptorTestCase: BaseTestCase { func testAdaptHandlerAndRetryHandlerDefaultInitializer() { // Given let adaptHandler: AdaptHandler = { urlRequest, _, completion in completion(.success(urlRequest)) } let retryHandler: RetryHandler = { _, _, _, completion in completion(.doNotRetry) } // When let interceptor = Interceptor(adaptHandler: adaptHandler, retryHandler: retryHandler) // Then XCTAssertEqual(interceptor.adapters.count, 1) XCTAssertEqual(interceptor.retriers.count, 1) } func testAdapterAndRetrierDefaultInitializer() { // Given let adapter = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) } let retrier = Retrier { _, _, _, completion in completion(.doNotRetry) } // When let interceptor = Interceptor(adapter: adapter, retrier: retrier) // Then XCTAssertEqual(interceptor.adapters.count, 1) XCTAssertEqual(interceptor.retriers.count, 1) } func testAdaptersAndRetriersDefaultInitializer() { // Given let adapter = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) } let retrier = Retrier { _, _, _, completion in completion(.doNotRetry) } // When let interceptor = Interceptor(adapters: [adapter, adapter], retriers: [retrier, retrier]) // Then XCTAssertEqual(interceptor.adapters.count, 2) XCTAssertEqual(interceptor.retriers.count, 2) } func testThatInterceptorCanAdaptRequestWithNoAdapters() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() let interceptor = Interceptor() var result: AFResult! // When interceptor.adapt(urlRequest, for: session) { result = $0 } // Then XCTAssertTrue(result.isSuccess) XCTAssertEqual(result.value, urlRequest) } func testThatInterceptorCanAdaptRequestWithOneAdapter() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() let adapter = Adapter { urlRequest, _, completion in completion(.failure(MockError())) } let interceptor = Interceptor(adapters: [adapter]) var result: AFResult! // When interceptor.adapt(urlRequest, for: session) { result = $0 } // Then XCTAssertTrue(result.isFailure) XCTAssertTrue(result.error is MockError) } func testThatInterceptorCanAdaptRequestWithMultipleAdapters() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() let adapter1 = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) } let adapter2 = Adapter { urlRequest, _, completion in completion(.failure(MockError())) } let interceptor = Interceptor(adapters: [adapter1, adapter2]) var result: AFResult! // When interceptor.adapt(urlRequest, for: session) { result = $0 } // Then XCTAssertTrue(result.isFailure) XCTAssertTrue(result.error is MockError) } func testThatInterceptorCanAdaptRequestAsynchronously() { // Given let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let session = Session() let adapter = Adapter { urlRequest, _, completion in DispatchQueue.main.async { completion(.failure(MockError())) } } let interceptor = Interceptor(adapters: [adapter]) var result: AFResult! let completesExpectation = expectation(description: "interceptor completes") // When interceptor.adapt(urlRequest, for: session) { result = $0 completesExpectation.fulfill() } waitForExpectations(timeout: timeout) // Then XCTAssertTrue(result.isFailure) XCTAssertTrue(result.error is MockError) } func testThatInterceptorCanRetryRequestWithNoRetriers() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) let interceptor = Interceptor() var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .doNotRetry) } func testThatInterceptorCanRetryRequestWithOneRetrier() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) let retrier = Retrier { _, _, _, completion in completion(.retry) } let interceptor = Interceptor(retriers: [retrier]) var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .retry) } func testThatInterceptorCanRetryRequestWithMultipleRetriers() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) let retrier1 = Retrier { _, _, _, completion in completion(.doNotRetry) } let retrier2 = Retrier { _, _, _, completion in completion(.retry) } let interceptor = Interceptor(retriers: [retrier1, retrier2]) var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .retry) } func testThatInterceptorCanRetryRequestAsynchronously() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) let retrier = Retrier { _, _, _, completion in DispatchQueue.main.async { completion(.retry) } } let interceptor = Interceptor(retriers: [retrier]) var result: RetryResult! let completesExpectation = expectation(description: "interceptor completes") // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 completesExpectation.fulfill() } waitForExpectations(timeout: timeout) // Then XCTAssertEqual(result, .retry) } func testThatInterceptorStopsIteratingThroughPendingRetriersWithRetryResult() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) var retrier2Called = false let retrier1 = Retrier { _, _, _, completion in completion(.retry) } let retrier2 = Retrier { _, _, _, completion in retrier2Called = true; completion(.doNotRetry) } let interceptor = Interceptor(retriers: [retrier1, retrier2]) var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .retry) XCTAssertFalse(retrier2Called) } func testThatInterceptorStopsIteratingThroughPendingRetriersWithRetryWithDelayResult() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) var retrier2Called = false let retrier1 = Retrier { _, _, _, completion in completion(.retryWithDelay(1.0)) } let retrier2 = Retrier { _, _, _, completion in retrier2Called = true; completion(.doNotRetry) } let interceptor = Interceptor(retriers: [retrier1, retrier2]) var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .retryWithDelay(1.0)) XCTAssertEqual(result.delay, 1.0) XCTAssertFalse(retrier2Called) } func testThatInterceptorStopsIteratingThroughPendingRetriersWithDoNotRetryResult() { // Given let url = URL(string: "https://httpbin.org/get")! let session = Session(startRequestsImmediately: false) let request = session.request(url) var retrier2Called = false let retrier1 = Retrier { _, _, _, completion in completion(.doNotRetryWithError(RetryError())) } let retrier2 = Retrier { _, _, _, completion in retrier2Called = true; completion(.doNotRetry) } let interceptor = Interceptor(retriers: [retrier1, retrier2]) var result: RetryResult! // When interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 } // Then XCTAssertEqual(result, .doNotRetryWithError(RetryError())) XCTAssertTrue(result.error is RetryError) XCTAssertFalse(retrier2Called) } } // MARK: - extension RetryResult: Equatable { public static func == (lhs: RetryResult, rhs: RetryResult) -> Bool { switch (lhs, rhs) { case (.retry, .retry), (.retryWithDelay, .retryWithDelay), (.doNotRetry, .doNotRetry), (.doNotRetryWithError, .doNotRetryWithError): return true default: return false } } }