| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714 |
- //
- // 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
- private struct MockError: Error {}
- private 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 = Endpoint().urlRequest
- let session = Session()
- var adapted = false
- let adapter = Adapter { request, _, completion in
- adapted = true
- completion(.success(request))
- }
- var result: Result<URLRequest, any Error>!
- // When
- adapter.adapt(urlRequest, for: session) { result = $0 }
- // Then
- XCTAssertTrue(adapted)
- XCTAssertTrue(result.isSuccess)
- }
- func testThatAdapterCallsAdaptHandlerWithStateAPI() {
- // Given
- final class StateCaptureAdapter: Adapter {
- private(set) var urlRequest: URLRequest?
- private(set) var state: RequestAdapterState?
- @preconcurrency
- override init(_ adaptHandler: @escaping AdaptHandler) {
- super.init(adaptHandler)
- }
- override func adapt(_ urlRequest: URLRequest,
- using state: RequestAdapterState,
- completion: @escaping (Result<URLRequest, any Error>) -> Void) {
- self.urlRequest = urlRequest
- self.state = state
- super.adapt(urlRequest, using: state, completion: completion)
- }
- }
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let requestID = UUID()
- var adapted = false
- let adapter = StateCaptureAdapter { urlRequest, _, completion in
- adapted = true
- completion(.success(urlRequest))
- }
- let state = RequestAdapterState(requestID: requestID, session: session)
- var result: Result<URLRequest, any Error>!
- // When
- adapter.adapt(urlRequest, using: state) { result = $0 }
- // Then
- XCTAssertTrue(adapted)
- XCTAssertTrue(result.isSuccess)
- XCTAssertEqual(adapter.urlRequest, urlRequest)
- XCTAssertEqual(adapter.state?.requestID, requestID)
- XCTAssertEqual(adapter.state?.session.session, session.session)
- }
- func testThatAdapterCallsRequestRetrierDefaultImplementationInProtocolExtension() {
- // Given
- let session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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)
- }
- @MainActor
- func testThatAdapterCanBeImplementedAsynchronously() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- var adapted = false
- let adapter = Adapter { request, _, completion in
- adapted = true
- DispatchQueue.main.async {
- completion(.success(request))
- }
- }
- var result: Result<URLRequest, any Error>!
- 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 session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- var retried = false
- let retrier = Retrier { _, _, _, 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 = Endpoint().urlRequest
- let session = Session()
- let retrier = Retrier { _, _, _, completion in
- completion(.retry)
- }
- var result: Result<URLRequest, any Error>!
- // When
- retrier.adapt(urlRequest, for: session) { result = $0 }
- // Then
- XCTAssertTrue(result.isSuccess)
- }
- @MainActor
- func testThatRetrierCanBeImplementedAsynchronously() {
- // Given
- let session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- var retried = false
- let retrier = Retrier { _, _, _, 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 InterceptorTests: 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 testThatInterceptorCanBeComposedOfMultipleRequestInterceptors() {
- // Given
- let adapter = Adapter { request, _, completion in completion(.success(request)) }
- let retrier = Retrier { _, _, _, completion in completion(.doNotRetry) }
- let inner = Interceptor(adapter: adapter, retrier: retrier)
- // When
- let interceptor = Interceptor(interceptors: [inner])
- // Then
- XCTAssertEqual(interceptor.adapters.count, 1)
- XCTAssertEqual(interceptor.retriers.count, 1)
- }
- func testThatInterceptorCanAdaptRequestWithNoAdapters() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let interceptor = Interceptor()
- var result: Result<URLRequest, any Error>!
- // When
- interceptor.adapt(urlRequest, for: session) { result = $0 }
- // Then
- XCTAssertTrue(result.isSuccess)
- XCTAssertEqual(result.success, urlRequest)
- }
- func testThatInterceptorCanAdaptRequestWithOneAdapter() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let adapter = Adapter { _, _, completion in completion(.failure(MockError())) }
- let interceptor = Interceptor(adapters: [adapter])
- var result: Result<URLRequest, any Error>!
- // When
- interceptor.adapt(urlRequest, for: session) { result = $0 }
- // Then
- XCTAssertTrue(result.isFailure)
- XCTAssertTrue(result.failure is MockError)
- }
- func testThatInterceptorCanAdaptRequestWithMultipleAdapters() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let adapter1 = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) }
- let adapter2 = Adapter { _, _, completion in completion(.failure(MockError())) }
- let interceptor = Interceptor(adapters: [adapter1, adapter2])
- var result: Result<URLRequest, any Error>!
- // When
- interceptor.adapt(urlRequest, for: session) { result = $0 }
- // Then
- XCTAssertTrue(result.isFailure)
- XCTAssertTrue(result.failure is MockError)
- }
- func testThatInterceptorCanAdaptRequestWithMultipleAdaptersUsingStateAPI() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let adapter1 = Adapter { urlRequest, _, completion in completion(.success(urlRequest)) }
- let adapter2 = Adapter { _, _, completion in completion(.failure(MockError())) }
- let interceptor = Interceptor(adapters: [adapter1, adapter2])
- let state = RequestAdapterState(requestID: UUID(), session: session)
- var result: Result<URLRequest, any Error>!
- // When
- interceptor.adapt(urlRequest, using: state) { result = $0 }
- // Then
- XCTAssertTrue(result.isFailure)
- XCTAssertTrue(result.failure is MockError)
- }
- @MainActor
- func testThatInterceptorCanAdaptRequestAsynchronously() {
- // Given
- let urlRequest = Endpoint().urlRequest
- let session = Session()
- let adapter = Adapter { _, _, completion in
- DispatchQueue.main.async {
- completion(.failure(MockError()))
- }
- }
- let interceptor = Interceptor(adapters: [adapter])
- var result: Result<URLRequest, any Error>!
- 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.failure is MockError)
- }
- func testThatInterceptorCanRetryRequestWithNoRetriers() {
- // Given
- let session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- let interceptor = Interceptor()
- var result: RetryResult!
- // When
- interceptor.retry(request, for: session, dueTo: MockError()) { result = $0 }
- // Then
- XCTAssertEqual(result, .doNotRetry)
- }
- func testThatInterceptorCanRetryRequestWithOneRetrier() {
- // Given
- let session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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 session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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)
- }
- @MainActor
- func testThatInterceptorCanRetryRequestAsynchronously() {
- // Given
- let session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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 session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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 session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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 session = Session(startRequestsImmediately: false)
- let request = session.request(.default)
- 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, RetryResult.doNotRetryWithError(RetryError()))
- XCTAssertTrue(result.error is RetryError)
- XCTAssertFalse(retrier2Called)
- }
- }
- // MARK: - Functional Tests
- final class InterceptorRequestTests: BaseTestCase {
- @MainActor
- func testThatRetryPolicyRetriesRequestTimeout() {
- // Given
- let interceptor = InspectorInterceptor(RetryPolicy(retryLimit: 1, exponentialBackoffScale: 0.1))
- let urlRequest = Endpoint.delay(1).modifying(\.timeout, to: 0.01)
- let expect = expectation(description: "request completed")
- // When
- let request = AF.request(urlRequest, interceptor: interceptor).response { _ in
- expect.fulfill()
- }
- waitForExpectations(timeout: timeout)
- // Then
- XCTAssertEqual(request.tasks.count, 2, "There should be two tasks, one original, one retry.")
- XCTAssertEqual(interceptor.retryCalledCount, 2, "retry() should be called twice.")
- XCTAssertEqual(interceptor.retries, [.retryWithDelay(0.1), .doNotRetry], "RetryResults should retryWithDelay, doNotRetry")
- }
- }
- // MARK: - Static Accessors
- final class StaticAccessorTests: BaseTestCase {
- func consumeRequestAdapter(_ requestAdapter: any RequestAdapter) {
- _ = requestAdapter
- }
- func consumeRequestRetrier(_ requestRetrier: any RequestRetrier) {
- _ = requestRetrier
- }
- func consumeRequestInterceptor(_ requestInterceptor: any RequestInterceptor) {
- _ = requestInterceptor
- }
- func testThatAdapterCanBeCreatedStaticallyFromProtocol() {
- // Given, When, Then
- consumeRequestAdapter(.adapter { request, _, completion in completion(.success(request)) })
- }
- func testThatRetrierCanBeCreatedStaticallyFromProtocol() {
- // Given, When, Then
- consumeRequestRetrier(.retrier { _, _, _, completion in completion(.doNotRetry) })
- }
- func testThatInterceptorCanBeCreatedStaticallyFromProtocol() {
- // Given, When, Then
- consumeRequestInterceptor(.interceptor())
- }
- func testThatRetryPolicyCanBeCreatedStaticallyFromProtocol() {
- // Given, When, Then
- consumeRequestInterceptor(.retryPolicy())
- }
- func testThatConnectionLostRetryPolicyCanBeCreatedStaticallyFromProtocol() {
- // Given, When, Then
- consumeRequestInterceptor(.connectionLostRetryPolicy())
- }
- }
- // MARK: - Helpers
- /// Class which captures the output of any underlying `RequestInterceptor`.
- final class InspectorInterceptor<Interceptor: RequestInterceptor>: RequestInterceptor {
- var onAdaptation: ((Result<URLRequest, any Error>) -> Void)?
- var onRetry: ((RetryResult) -> Void)?
- private(set) var adaptations: [Result<URLRequest, any Error>] = []
- private(set) var retries: [RetryResult] = []
- /// Number of times `retry` was called.
- var retryCalledCount: Int { retries.count }
- let interceptor: Interceptor
- init(_ interceptor: Interceptor) {
- self.interceptor = interceptor
- }
- func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
- interceptor.adapt(urlRequest, for: session) { result in
- self.adaptations.append(result)
- completion(result)
- self.onAdaptation?(result)
- }
- }
- func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) {
- interceptor.retry(request, for: session, dueTo: error) { result in
- self.retries.append(result)
- completion(result)
- self.onRetry?(result)
- }
- }
- }
- /// Retry a request once, allowing the second to succeed using the method path.
- final class SingleRetrier: RequestInterceptor {
- private var hasRetried = false
- func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
- if hasRetried {
- let method = urlRequest.method ?? .get
- let endpoint = Endpoint(path: .method(method),
- method: method,
- headers: urlRequest.headers)
- completion(.success(endpoint.urlRequest))
- } else {
- completion(.success(urlRequest))
- }
- }
- func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) {
- completion(hasRetried ? .doNotRetry : .retry)
- hasRetried = true
- }
- }
- extension Alamofire.RetryResult: Swift.Equatable {
- public static func ==(lhs: RetryResult, rhs: RetryResult) -> Bool {
- switch (lhs, rhs) {
- case (.retry, .retry),
- (.doNotRetry, .doNotRetry),
- (.doNotRetryWithError, .doNotRetryWithError):
- true
- case let (.retryWithDelay(leftDelay), .retryWithDelay(rightDelay)):
- leftDelay == rightDelay
- default:
- false
- }
- }
- }
|