AuthenticationInterceptorTests.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. //
  2. // AuthenticationInterceptorTests.swift
  3. //
  4. // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. @testable import Alamofire
  25. import Foundation
  26. import XCTest
  27. class AuthenticationInterceptorTestCase: BaseTestCase {
  28. // MARK: - Helper Types
  29. struct OAuthCredential: AuthenticationCredential {
  30. let accessToken: String
  31. let refreshToken: String
  32. let userID: String
  33. let expiration: Date
  34. let requiresRefresh: Bool
  35. init(accessToken: String = "a0",
  36. refreshToken: String = "r0",
  37. userID: String = "u0",
  38. expiration: Date = Date(),
  39. requiresRefresh: Bool = false) {
  40. self.accessToken = accessToken
  41. self.refreshToken = refreshToken
  42. self.userID = userID
  43. self.expiration = expiration
  44. self.requiresRefresh = requiresRefresh
  45. }
  46. }
  47. enum OAuthError: Error {
  48. case refreshNetworkFailure
  49. }
  50. class OAuthAuthenticator: Authenticator {
  51. private(set) var applyCount = 0
  52. private(set) var refreshCount = 0
  53. private(set) var didRequestFailDueToAuthErrorCount = 0
  54. private(set) var isRequestAuthenticatedWithCredentialCount = 0
  55. let refreshResult: Result<OAuthCredential, Error>?
  56. let lock = NSLock()
  57. init(refreshResult: Result<OAuthCredential, Error>? = nil) {
  58. self.refreshResult = refreshResult
  59. }
  60. func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
  61. lock.lock(); defer { lock.unlock() }
  62. applyCount += 1
  63. urlRequest.headers.add(.authorization(bearerToken: credential.accessToken))
  64. }
  65. func refresh(_ credential: OAuthCredential,
  66. for session: Session,
  67. completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
  68. lock.lock(); defer { lock.unlock() }
  69. refreshCount += 1
  70. let refreshResult = self.refreshResult ?? .success(
  71. OAuthCredential(accessToken: "a\(refreshCount)",
  72. refreshToken: "a\(refreshCount)",
  73. userID: "u1",
  74. expiration: Date())
  75. )
  76. // The 100 ms delay here is important to allow multiple requests to queue up while refreshing
  77. DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 0.1) { completion(refreshResult) }
  78. }
  79. func didRequest(_ urlRequest: URLRequest,
  80. with response: HTTPURLResponse,
  81. failDueToAuthenticationError error: Error)
  82. -> Bool {
  83. lock.lock(); defer { lock.unlock() }
  84. didRequestFailDueToAuthErrorCount += 1
  85. return response.statusCode == 401
  86. }
  87. func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
  88. lock.lock(); defer { lock.unlock() }
  89. isRequestAuthenticatedWithCredentialCount += 1
  90. let bearerToken = HTTPHeader.authorization(bearerToken: credential.accessToken).value
  91. return urlRequest.headers["Authorization"] == bearerToken
  92. }
  93. }
  94. class PathAdapter: RequestAdapter {
  95. var paths: [String]
  96. init(paths: [String]) {
  97. self.paths = paths
  98. }
  99. func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
  100. var request = urlRequest
  101. var urlComponents = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)!
  102. urlComponents.path = paths.removeFirst()
  103. request.url = urlComponents.url
  104. completion(.success(request))
  105. }
  106. }
  107. // MARK: - Tests - Adapt
  108. func testThatInterceptorCanAdaptURLRequest() {
  109. // Given
  110. let credential = OAuthCredential()
  111. let authenticator = OAuthAuthenticator()
  112. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  113. let urlRequest = URLRequest.makeHTTPBinRequest()
  114. let session = Session()
  115. let expect = expectation(description: "request should complete")
  116. var response: AFDataResponse<Data?>?
  117. // When
  118. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  119. response = $0
  120. expect.fulfill()
  121. }
  122. waitForExpectations(timeout: timeout)
  123. // Then
  124. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a0")
  125. XCTAssertEqual(response?.result.isSuccess, true)
  126. XCTAssertEqual(authenticator.applyCount, 1)
  127. XCTAssertEqual(authenticator.refreshCount, 0)
  128. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 0)
  129. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  130. XCTAssertEqual(request.retryCount, 0)
  131. }
  132. func testThatInterceptorQueuesAdaptOperationWhenRefreshing() {
  133. // Given
  134. let credential = OAuthCredential(requiresRefresh: true)
  135. let authenticator = OAuthAuthenticator()
  136. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  137. let urlRequest1 = URLRequest.makeHTTPBinRequest(path: "/status/200")
  138. let urlRequest2 = URLRequest.makeHTTPBinRequest(path: "/status/202")
  139. let session = Session()
  140. let expect = expectation(description: "both requests should complete")
  141. expect.expectedFulfillmentCount = 2
  142. var response1: AFDataResponse<Data?>?
  143. var response2: AFDataResponse<Data?>?
  144. // When
  145. let request1 = session.request(urlRequest1, interceptor: interceptor).validate().response {
  146. response1 = $0
  147. expect.fulfill()
  148. }
  149. let request2 = session.request(urlRequest2, interceptor: interceptor).validate().response {
  150. response2 = $0
  151. expect.fulfill()
  152. }
  153. waitForExpectations(timeout: timeout)
  154. // Then
  155. XCTAssertEqual(response1?.request?.headers["Authorization"], "Bearer a1")
  156. XCTAssertEqual(response2?.request?.headers["Authorization"], "Bearer a1")
  157. XCTAssertEqual(response1?.result.isSuccess, true)
  158. XCTAssertEqual(response2?.result.isSuccess, true)
  159. XCTAssertEqual(authenticator.applyCount, 2)
  160. XCTAssertEqual(authenticator.refreshCount, 1)
  161. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 0)
  162. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  163. XCTAssertEqual(request1.retryCount, 0)
  164. XCTAssertEqual(request2.retryCount, 0)
  165. }
  166. func testThatInterceptorThrowsMissingCredentialErrorWhenCredentialIsNil() {
  167. // Given
  168. let authenticator = OAuthAuthenticator()
  169. let interceptor = AuthenticationInterceptor(authenticator: authenticator)
  170. let urlRequest = URLRequest.makeHTTPBinRequest()
  171. let session = Session()
  172. let expect = expectation(description: "request should complete")
  173. var response: AFDataResponse<Data?>?
  174. // When
  175. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  176. response = $0
  177. expect.fulfill()
  178. }
  179. waitForExpectations(timeout: timeout)
  180. // Then
  181. XCTAssertEqual(response?.request?.headers.count, 0)
  182. XCTAssertEqual(response?.result.isFailure, true)
  183. XCTAssertEqual(response?.result.failure?.asAFError?.isRequestAdaptationError, true)
  184. XCTAssertEqual(response?.result.failure?.asAFError?.underlyingError as? AuthenticationError, .missingCredential)
  185. XCTAssertEqual(authenticator.applyCount, 0)
  186. XCTAssertEqual(authenticator.refreshCount, 0)
  187. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 0)
  188. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  189. XCTAssertEqual(request.retryCount, 0)
  190. }
  191. func testThatInterceptorRethrowsRefreshErrorFromAdapt() {
  192. // Given
  193. let credential = OAuthCredential(requiresRefresh: true)
  194. let authenticator = OAuthAuthenticator(refreshResult: .failure(OAuthError.refreshNetworkFailure))
  195. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  196. let session = Session()
  197. let urlRequest = URLRequest.makeHTTPBinRequest()
  198. let expect = expectation(description: "request should complete")
  199. var response: AFDataResponse<Data?>?
  200. // When
  201. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  202. response = $0
  203. expect.fulfill()
  204. }
  205. waitForExpectations(timeout: timeout)
  206. // Then
  207. XCTAssertEqual(response?.request?.headers.count, 0)
  208. XCTAssertEqual(response?.result.isFailure, true)
  209. XCTAssertEqual(response?.result.failure?.asAFError?.isRequestAdaptationError, true)
  210. XCTAssertEqual(response?.result.failure?.asAFError?.underlyingError as? OAuthError, .refreshNetworkFailure)
  211. if case let .requestRetryFailed(_, originalError) = response?.result.failure {
  212. XCTAssertEqual(originalError.asAFError?.isResponseValidationError, true)
  213. XCTAssertEqual(originalError.asAFError?.responseCode, 401)
  214. }
  215. XCTAssertEqual(authenticator.applyCount, 0)
  216. XCTAssertEqual(authenticator.refreshCount, 1)
  217. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 0)
  218. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  219. XCTAssertEqual(request.retryCount, 0)
  220. }
  221. // MARK: - Tests - Retry
  222. func testThatInterceptorDoesNotRetryWithoutResponse() {
  223. // Given
  224. let credential = OAuthCredential()
  225. let authenticator = OAuthAuthenticator()
  226. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  227. let urlRequest = URLRequest(url: URL(string: "/invalid/path")!)
  228. let session = Session()
  229. let expect = expectation(description: "request should complete")
  230. var response: AFDataResponse<Data?>?
  231. // When
  232. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  233. response = $0
  234. expect.fulfill()
  235. }
  236. waitForExpectations(timeout: timeout)
  237. // Then
  238. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a0")
  239. XCTAssertEqual(response?.result.isFailure, true)
  240. XCTAssertEqual(response?.result.failure?.asAFError?.isSessionTaskError, true)
  241. XCTAssertEqual(authenticator.applyCount, 1)
  242. XCTAssertEqual(authenticator.refreshCount, 0)
  243. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 0)
  244. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  245. XCTAssertEqual(request.retryCount, 0)
  246. }
  247. func testThatInterceptorDoesNotRetryWhenRequestDoesNotFailDueToAuthError() {
  248. // Given
  249. let credential = OAuthCredential()
  250. let authenticator = OAuthAuthenticator()
  251. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  252. let urlRequest = URLRequest.makeHTTPBinRequest(path: "status/500")
  253. let session = Session()
  254. let expect = expectation(description: "request should complete")
  255. var response: AFDataResponse<Data?>?
  256. // When
  257. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  258. response = $0
  259. expect.fulfill()
  260. }
  261. waitForExpectations(timeout: timeout)
  262. // Then
  263. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a0")
  264. XCTAssertEqual(response?.result.isFailure, true)
  265. XCTAssertEqual(response?.result.failure?.asAFError?.isResponseValidationError, true)
  266. XCTAssertEqual(response?.result.failure?.asAFError?.responseCode, 500)
  267. XCTAssertEqual(authenticator.applyCount, 1)
  268. XCTAssertEqual(authenticator.refreshCount, 0)
  269. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 1)
  270. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  271. XCTAssertEqual(request.retryCount, 0)
  272. }
  273. func testThatInterceptorThrowsMissingCredentialErrorWhenCredentialIsNilAndRequestShouldBeRetried() {
  274. // Given
  275. let credential = OAuthCredential()
  276. let authenticator = OAuthAuthenticator()
  277. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  278. let eventMonitor = ClosureEventMonitor()
  279. eventMonitor.requestDidCreateTask = { _, _ in interceptor.credential = nil }
  280. let session = Session(eventMonitors: [eventMonitor])
  281. let urlRequest = URLRequest.makeHTTPBinRequest(path: "status/401")
  282. let expect = expectation(description: "request should complete")
  283. var response: AFDataResponse<Data?>?
  284. // When
  285. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  286. response = $0
  287. expect.fulfill()
  288. }
  289. waitForExpectations(timeout: timeout)
  290. // Then
  291. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a0")
  292. XCTAssertEqual(response?.result.isFailure, true)
  293. XCTAssertEqual(response?.result.failure?.asAFError?.isRequestRetryError, true)
  294. XCTAssertEqual(response?.result.failure?.asAFError?.underlyingError as? AuthenticationError, .missingCredential)
  295. if case let .requestRetryFailed(_, originalError) = response?.result.failure {
  296. XCTAssertEqual(originalError.asAFError?.isResponseValidationError, true)
  297. XCTAssertEqual(originalError.asAFError?.responseCode, 401)
  298. }
  299. XCTAssertEqual(authenticator.applyCount, 1)
  300. XCTAssertEqual(authenticator.refreshCount, 0)
  301. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 1)
  302. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 0)
  303. XCTAssertEqual(request.retryCount, 0)
  304. }
  305. func testThatInterceptorRetriesRequestThatFailedWithOutdatedCredential() {
  306. // Given
  307. let credential = OAuthCredential()
  308. let authenticator = OAuthAuthenticator()
  309. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  310. let eventMonitor = ClosureEventMonitor()
  311. eventMonitor.requestDidCreateTask = { _, _ in
  312. interceptor.credential = OAuthCredential(accessToken: "a1",
  313. refreshToken: "r1",
  314. userID: "u0",
  315. expiration: Date(),
  316. requiresRefresh: false)
  317. }
  318. let session = Session(eventMonitors: [eventMonitor])
  319. let pathAdapter = PathAdapter(paths: ["/status/401", "/status/200"])
  320. let compositeInterceptor = Interceptor(adapters: [pathAdapter, interceptor], retriers: [interceptor])
  321. let urlRequest = URLRequest.makeHTTPBinRequest()
  322. let expect = expectation(description: "request should complete")
  323. var response: AFDataResponse<Data?>?
  324. // When
  325. let request = session.request(urlRequest, interceptor: compositeInterceptor).validate().response {
  326. response = $0
  327. expect.fulfill()
  328. }
  329. waitForExpectations(timeout: timeout)
  330. // Then
  331. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a1")
  332. XCTAssertEqual(response?.result.isSuccess, true)
  333. XCTAssertEqual(authenticator.applyCount, 2)
  334. XCTAssertEqual(authenticator.refreshCount, 0)
  335. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 1)
  336. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 1)
  337. XCTAssertEqual(request.retryCount, 1)
  338. }
  339. func testThatInterceptorRetriesRequestAfterRefresh() {
  340. // Given
  341. let credential = OAuthCredential()
  342. let authenticator = OAuthAuthenticator()
  343. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  344. let pathAdapter = PathAdapter(paths: ["/status/401", "/status/200"])
  345. let compositeInterceptor = Interceptor(adapters: [pathAdapter, interceptor], retriers: [interceptor])
  346. let session = Session()
  347. let urlRequest = URLRequest.makeHTTPBinRequest()
  348. let expect = expectation(description: "request should complete")
  349. var response: AFDataResponse<Data?>?
  350. // When
  351. let request = session.request(urlRequest, interceptor: compositeInterceptor).validate().response {
  352. response = $0
  353. expect.fulfill()
  354. }
  355. waitForExpectations(timeout: timeout)
  356. // Then
  357. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a1")
  358. XCTAssertEqual(response?.result.isSuccess, true)
  359. XCTAssertEqual(authenticator.applyCount, 2)
  360. XCTAssertEqual(authenticator.refreshCount, 1)
  361. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 1)
  362. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 1)
  363. XCTAssertEqual(request.retryCount, 1)
  364. }
  365. func testThatInterceptorRethrowsRefreshErrorFromRetry() {
  366. // Given
  367. let credential = OAuthCredential()
  368. let authenticator = OAuthAuthenticator(refreshResult: .failure(OAuthError.refreshNetworkFailure))
  369. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  370. let session = Session()
  371. let urlRequest = URLRequest.makeHTTPBinRequest(path: "/status/401")
  372. let expect = expectation(description: "request should complete")
  373. var response: AFDataResponse<Data?>?
  374. // When
  375. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  376. response = $0
  377. expect.fulfill()
  378. }
  379. waitForExpectations(timeout: timeout)
  380. // Then
  381. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a0")
  382. XCTAssertEqual(response?.result.isFailure, true)
  383. XCTAssertEqual(response?.result.failure?.asAFError?.isRequestRetryError, true)
  384. XCTAssertEqual(response?.result.failure?.asAFError?.underlyingError as? OAuthError, .refreshNetworkFailure)
  385. if case let .requestRetryFailed(_, originalError) = response?.result.failure {
  386. XCTAssertEqual(originalError.asAFError?.isResponseValidationError, true)
  387. XCTAssertEqual(originalError.asAFError?.responseCode, 401)
  388. }
  389. XCTAssertEqual(authenticator.applyCount, 1)
  390. XCTAssertEqual(authenticator.refreshCount, 1)
  391. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 1)
  392. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 1)
  393. XCTAssertEqual(request.retryCount, 0)
  394. }
  395. func testThatInterceptorTriggersRefreshWithMultipleParallelRequestsReturning401Responses() {
  396. // Given
  397. let credential = OAuthCredential()
  398. let authenticator = OAuthAuthenticator()
  399. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  400. let requestCount = 6
  401. let urlRequest = URLRequest.makeHTTPBinRequest()
  402. let session = Session()
  403. let expect = expectation(description: "both requests should complete")
  404. expect.expectedFulfillmentCount = requestCount
  405. var requests: [Int: Request] = [:]
  406. var responses: [Int: AFDataResponse<Data?>] = [:]
  407. for index in 0..<requestCount {
  408. let pathAdapter = PathAdapter(paths: ["/status/401", "/status/20\(index)"])
  409. let compositeInterceptor = Interceptor(adapters: [pathAdapter, interceptor], retriers: [interceptor])
  410. // When
  411. let request = session.request(urlRequest, interceptor: compositeInterceptor).validate().response {
  412. responses[index] = $0
  413. expect.fulfill()
  414. }
  415. requests[index] = request
  416. }
  417. waitForExpectations(timeout: timeout)
  418. // Then
  419. for index in 0..<requestCount {
  420. let response = responses[index]
  421. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a1")
  422. XCTAssertEqual(response?.result.isSuccess, true)
  423. let request = requests[index]
  424. XCTAssertEqual(request?.retryCount, 1)
  425. }
  426. XCTAssertEqual(authenticator.applyCount, 12)
  427. XCTAssertEqual(authenticator.refreshCount, 1)
  428. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 6)
  429. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 6)
  430. }
  431. // MARK: - Tests - Excessive Refresh
  432. func testThatInterceptorIgnoresExcessiveRefreshWhenRefreshWindowIsNil() {
  433. // Given
  434. let credential = OAuthCredential()
  435. let authenticator = OAuthAuthenticator()
  436. let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
  437. let pathAdapter = PathAdapter(paths: ["/status/401",
  438. "/status/401",
  439. "/status/401",
  440. "/status/401",
  441. "/status/401",
  442. "/status/200"])
  443. let compositeInterceptor = Interceptor(adapters: [pathAdapter, interceptor], retriers: [interceptor])
  444. let session = Session()
  445. let urlRequest = URLRequest.makeHTTPBinRequest()
  446. let expect = expectation(description: "request should complete")
  447. var response: AFDataResponse<Data?>?
  448. // When
  449. let request = session.request(urlRequest, interceptor: compositeInterceptor).validate().response {
  450. response = $0
  451. expect.fulfill()
  452. }
  453. waitForExpectations(timeout: timeout)
  454. // Then
  455. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a5")
  456. XCTAssertEqual(response?.result.isSuccess, true)
  457. XCTAssertEqual(authenticator.applyCount, 6)
  458. XCTAssertEqual(authenticator.refreshCount, 5)
  459. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 5)
  460. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 5)
  461. XCTAssertEqual(request.retryCount, 5)
  462. }
  463. func testThatInterceptorThrowsExcessiveRefreshErrorWhenExcessiveRefreshOccurs() {
  464. // Given
  465. let credential = OAuthCredential()
  466. let authenticator = OAuthAuthenticator()
  467. let interceptor = AuthenticationInterceptor(authenticator: authenticator,
  468. credential: credential,
  469. refreshWindow: .init(interval: 30, maximumAttempts: 2))
  470. let session = Session()
  471. let urlRequest = URLRequest.makeHTTPBinRequest(path: "/status/401")
  472. let expect = expectation(description: "request should complete")
  473. var response: AFDataResponse<Data?>?
  474. // When
  475. let request = session.request(urlRequest, interceptor: interceptor).validate().response {
  476. response = $0
  477. expect.fulfill()
  478. }
  479. waitForExpectations(timeout: timeout)
  480. // Then
  481. XCTAssertEqual(response?.request?.headers["Authorization"], "Bearer a2")
  482. XCTAssertEqual(response?.result.isFailure, true)
  483. XCTAssertEqual(response?.result.failure?.asAFError?.isRequestRetryError, true)
  484. XCTAssertEqual(response?.result.failure?.asAFError?.underlyingError as? AuthenticationError, .excessiveRefresh)
  485. if case let .requestRetryFailed(_, originalError) = response?.result.failure {
  486. XCTAssertEqual(originalError.asAFError?.isResponseValidationError, true)
  487. XCTAssertEqual(originalError.asAFError?.responseCode, 401)
  488. }
  489. XCTAssertEqual(authenticator.applyCount, 3)
  490. XCTAssertEqual(authenticator.refreshCount, 2)
  491. XCTAssertEqual(authenticator.didRequestFailDueToAuthErrorCount, 3)
  492. XCTAssertEqual(authenticator.isRequestAuthenticatedWithCredentialCount, 3)
  493. XCTAssertEqual(request.retryCount, 2)
  494. }
  495. }