ValidationTests.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. //
  2. // ValidationTests.swift
  3. //
  4. // Copyright (c) 2014-2016 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. private let fileURL = URL(fileURLWithPath: FileManager.documentsDirectory + "/test_response.json")
  28. class StatusCodeValidationTestCase: BaseTestCase {
  29. func testThatValidationForRequestWithAcceptableStatusCodeResponseSucceeds() {
  30. // Given
  31. let urlString = "https://httpbin.org/status/200"
  32. let expectation1 = self.expectation(description: "request should return 200 status code")
  33. let expectation2 = self.expectation(description: "download should return 200 status code")
  34. var requestError: Error?
  35. var downloadError: Error?
  36. // When
  37. Alamofire.request(urlString, withMethod: .get)
  38. .validate(statusCode: 200..<300)
  39. .response { resp in
  40. requestError = resp.error
  41. expectation1.fulfill()
  42. }
  43. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  44. .validate(statusCode: 200..<300)
  45. .response { resp in
  46. downloadError = resp.error
  47. expectation2.fulfill()
  48. }
  49. waitForExpectations(timeout: timeout, handler: nil)
  50. // Then
  51. XCTAssertNil(requestError)
  52. XCTAssertNil(downloadError)
  53. }
  54. func testThatValidationForRequestWithUnacceptableStatusCodeResponseFails() {
  55. // Given
  56. let urlString = "https://httpbin.org/status/404"
  57. let expectation1 = self.expectation(description: "request should return 404 status code")
  58. let expectation2 = self.expectation(description: "download should return 404 status code")
  59. var requestError: Error?
  60. var downloadError: Error?
  61. // When
  62. Alamofire.request(urlString, withMethod: .get)
  63. .validate(statusCode: [200])
  64. .response { resp in
  65. requestError = resp.error
  66. expectation1.fulfill()
  67. }
  68. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  69. .validate(statusCode: [200])
  70. .response { resp in
  71. downloadError = resp.error
  72. expectation2.fulfill()
  73. }
  74. waitForExpectations(timeout: timeout, handler: nil)
  75. // Then
  76. XCTAssertNotNil(requestError)
  77. XCTAssertNotNil(downloadError)
  78. for error in [requestError, downloadError] {
  79. if let error = error as? AFError, let statusCode = error.responseCode {
  80. XCTAssertTrue(error.isUnacceptableStatusCode)
  81. XCTAssertEqual(statusCode, 404)
  82. } else {
  83. XCTFail("Error should not be nil, should be an AFError, and should have an associated statusCode.")
  84. }
  85. }
  86. }
  87. func testThatValidationForRequestWithNoAcceptableStatusCodesFails() {
  88. // Given
  89. let urlString = "https://httpbin.org/status/201"
  90. let expectation1 = self.expectation(description: "request should return 201 status code")
  91. let expectation2 = self.expectation(description: "download should return 201 status code")
  92. var requestError: Error?
  93. var downloadError: Error?
  94. // When
  95. Alamofire.request(urlString, withMethod: .get)
  96. .validate(statusCode: [])
  97. .response { resp in
  98. requestError = resp.error
  99. expectation1.fulfill()
  100. }
  101. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  102. .validate(statusCode: [])
  103. .response { resp in
  104. downloadError = resp.error
  105. expectation2.fulfill()
  106. }
  107. waitForExpectations(timeout: timeout, handler: nil)
  108. // Then
  109. XCTAssertNotNil(requestError)
  110. XCTAssertNotNil(downloadError)
  111. for error in [requestError, downloadError] {
  112. if let error = error as? AFError, let statusCode = error.responseCode {
  113. XCTAssertTrue(error.isUnacceptableStatusCode)
  114. XCTAssertEqual(statusCode, 201)
  115. } else {
  116. XCTFail("Error should not be nil, should be an AFError, and should have an associated statusCode.")
  117. }
  118. }
  119. }
  120. }
  121. // MARK: -
  122. class ContentTypeValidationTestCase: BaseTestCase {
  123. func testThatValidationForRequestWithAcceptableContentTypeResponseSucceeds() {
  124. // Given
  125. let urlString = "https://httpbin.org/ip"
  126. let expectation1 = self.expectation(description: "request should succeed and return ip")
  127. let expectation2 = self.expectation(description: "download should succeed and return ip")
  128. var requestError: Error?
  129. var downloadError: Error?
  130. // When
  131. Alamofire.request(urlString, withMethod: .get)
  132. .validate(contentType: ["application/json"])
  133. .validate(contentType: ["application/json;charset=utf8"])
  134. .validate(contentType: ["application/json;q=0.8;charset=utf8"])
  135. .response { resp in
  136. requestError = resp.error
  137. expectation1.fulfill()
  138. }
  139. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  140. .validate(contentType: ["application/json"])
  141. .validate(contentType: ["application/json;charset=utf8"])
  142. .validate(contentType: ["application/json;q=0.8;charset=utf8"])
  143. .response { resp in
  144. downloadError = resp.error
  145. expectation2.fulfill()
  146. }
  147. waitForExpectations(timeout: timeout, handler: nil)
  148. // Then
  149. XCTAssertNil(requestError)
  150. XCTAssertNil(downloadError)
  151. }
  152. func testThatValidationForRequestWithAcceptableWildcardContentTypeResponseSucceeds() {
  153. // Given
  154. let urlString = "https://httpbin.org/ip"
  155. let expectation1 = self.expectation(description: "request should succeed and return ip")
  156. let expectation2 = self.expectation(description: "download should succeed and return ip")
  157. var requestError: Error?
  158. var downloadError: Error?
  159. // When
  160. Alamofire.request(urlString, withMethod: .get)
  161. .validate(contentType: ["*/*"])
  162. .validate(contentType: ["application/*"])
  163. .validate(contentType: ["*/json"])
  164. .response { resp in
  165. requestError = resp.error
  166. expectation1.fulfill()
  167. }
  168. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  169. .validate(contentType: ["*/*"])
  170. .validate(contentType: ["application/*"])
  171. .validate(contentType: ["*/json"])
  172. .response { resp in
  173. downloadError = resp.error
  174. expectation2.fulfill()
  175. }
  176. waitForExpectations(timeout: timeout, handler: nil)
  177. // Then
  178. XCTAssertNil(requestError)
  179. XCTAssertNil(downloadError)
  180. }
  181. func testThatValidationForRequestWithUnacceptableContentTypeResponseFails() {
  182. // Given
  183. let urlString = "https://httpbin.org/xml"
  184. let expectation1 = self.expectation(description: "request should succeed and return xml")
  185. let expectation2 = self.expectation(description: "download should succeed and return xml")
  186. var requestError: Error?
  187. var downloadError: Error?
  188. // When
  189. Alamofire.request(urlString, withMethod: .get)
  190. .validate(contentType: ["application/octet-stream"])
  191. .response { resp in
  192. requestError = resp.error
  193. expectation1.fulfill()
  194. }
  195. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  196. .validate(contentType: ["application/octet-stream"])
  197. .response { resp in
  198. downloadError = resp.error
  199. expectation2.fulfill()
  200. }
  201. waitForExpectations(timeout: timeout, handler: nil)
  202. // Then
  203. XCTAssertNotNil(requestError)
  204. XCTAssertNotNil(downloadError)
  205. for error in [requestError, downloadError] {
  206. if let error = error as? AFError {
  207. XCTAssertTrue(error.isUnacceptableContentType)
  208. XCTAssertEqual(error.responseContentType, "application/xml")
  209. XCTAssertEqual(error.acceptableContentTypes?.first, "application/octet-stream")
  210. } else {
  211. XCTFail("error should not be nil")
  212. }
  213. }
  214. }
  215. func testThatValidationForRequestWithNoAcceptableContentTypeResponseFails() {
  216. // Given
  217. let urlString = "https://httpbin.org/xml"
  218. let expectation1 = self.expectation(description: "request should succeed and return xml")
  219. let expectation2 = self.expectation(description: "download should succeed and return xml")
  220. var requestError: Error?
  221. var downloadError: Error?
  222. // When
  223. Alamofire.request(urlString, withMethod: .get)
  224. .validate(contentType: [])
  225. .response { resp in
  226. requestError = resp.error
  227. expectation1.fulfill()
  228. }
  229. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  230. .validate(contentType: [])
  231. .response { resp in
  232. downloadError = resp.error
  233. expectation2.fulfill()
  234. }
  235. waitForExpectations(timeout: timeout, handler: nil)
  236. // Then
  237. XCTAssertNotNil(requestError)
  238. XCTAssertNotNil(downloadError)
  239. for error in [requestError, downloadError] {
  240. if let error = error as? AFError {
  241. XCTAssertTrue(error.isUnacceptableContentType)
  242. XCTAssertEqual(error.responseContentType, "application/xml")
  243. XCTAssertTrue(error.acceptableContentTypes?.isEmpty ?? false)
  244. } else {
  245. XCTFail("error should not be nil")
  246. }
  247. }
  248. }
  249. func testThatValidationForRequestWithNoAcceptableContentTypeResponseSucceedsWhenNoDataIsReturned() {
  250. // Given
  251. let urlString = "https://httpbin.org/status/204"
  252. let expectation1 = self.expectation(description: "request should succeed and return no data")
  253. let expectation2 = self.expectation(description: "download should succeed and return no data")
  254. var requestError: Error?
  255. var downloadError: Error?
  256. // When
  257. Alamofire.request(urlString, withMethod: .get)
  258. .validate(contentType: [])
  259. .response { resp in
  260. requestError = resp.error
  261. expectation1.fulfill()
  262. }
  263. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  264. .validate(contentType: [])
  265. .response { resp in
  266. downloadError = resp.error
  267. expectation2.fulfill()
  268. }
  269. waitForExpectations(timeout: timeout, handler: nil)
  270. // Then
  271. XCTAssertNil(requestError)
  272. XCTAssertNil(downloadError)
  273. }
  274. func testThatValidationForRequestWithAcceptableWildcardContentTypeResponseSucceedsWhenResponseIsNil() {
  275. // Given
  276. class MockManager: SessionManager {
  277. override func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  278. let originalRequest = urlRequest.urlRequest
  279. let adaptedRequest = originalRequest.adapt(using: adapter)
  280. let task: URLSessionDataTask = queue.syncResult { session.dataTask(with: adaptedRequest) }
  281. let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
  282. let request = MockDataRequest(session: session, task: task, originalTask: originalTask)
  283. delegate[request.delegate.task] = request
  284. if startRequestsImmediately { request.resume() }
  285. return request
  286. }
  287. override func download(
  288. _ urlRequest: URLRequestConvertible,
  289. to destination: DownloadRequest.DownloadFileDestination)
  290. -> DownloadRequest
  291. {
  292. let originalRequest = urlRequest.urlRequest
  293. let adaptedRequest = originalRequest.adapt(using: adapter)
  294. var task: URLSessionDownloadTask!
  295. queue.sync { task = self.session.downloadTask(with: adaptedRequest) }
  296. let originalTask = DownloadRequest.Downloadable.request(originalRequest)
  297. let request = MockDownloadRequest(session: session, task: task, originalTask: originalTask)
  298. request.downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, task, URL in
  299. return destination(URL, task.response as! HTTPURLResponse)
  300. }
  301. delegate[request.delegate.task] = request
  302. if startRequestsImmediately { request.resume() }
  303. return request
  304. }
  305. }
  306. class MockDataRequest: DataRequest {
  307. override var response: HTTPURLResponse? {
  308. return MockHTTPURLResponse(
  309. url: URL(string: request!.urlString)!,
  310. statusCode: 204,
  311. httpVersion: "HTTP/1.1",
  312. headerFields: nil
  313. )
  314. }
  315. }
  316. class MockDownloadRequest: DownloadRequest {
  317. override var response: HTTPURLResponse? {
  318. return MockHTTPURLResponse(
  319. url: URL(string: request!.urlString)!,
  320. statusCode: 204,
  321. httpVersion: "HTTP/1.1",
  322. headerFields: nil
  323. )
  324. }
  325. }
  326. class MockHTTPURLResponse: HTTPURLResponse {
  327. override var mimeType: String? { return nil }
  328. }
  329. let manager: SessionManager = {
  330. let configuration: URLSessionConfiguration = {
  331. let configuration = URLSessionConfiguration.ephemeral
  332. configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
  333. return configuration
  334. }()
  335. return MockManager(configuration: configuration)
  336. }()
  337. let urlString = "https://httpbin.org/delete"
  338. let expectation1 = self.expectation(description: "request should be stubbed and return 204 status code")
  339. let expectation2 = self.expectation(description: "download should be stubbed and return 204 status code")
  340. var requestResponse: DefaultDataResponse?
  341. var downloadResponse: DefaultDownloadResponse?
  342. // When
  343. manager.request(urlString, withMethod: .delete)
  344. .validate(contentType: ["*/*"])
  345. .response { resp in
  346. requestResponse = resp
  347. expectation1.fulfill()
  348. }
  349. manager.download(urlString, to: { _, _ in fileURL }, withMethod: .delete)
  350. .validate(contentType: ["*/*"])
  351. .response { resp in
  352. downloadResponse = resp
  353. expectation2.fulfill()
  354. }
  355. waitForExpectations(timeout: timeout, handler: nil)
  356. // Then
  357. XCTAssertNotNil(requestResponse?.response)
  358. XCTAssertNotNil(requestResponse?.data)
  359. XCTAssertNil(requestResponse?.error)
  360. XCTAssertEqual(requestResponse?.response?.statusCode, 204)
  361. XCTAssertNil(requestResponse?.response?.mimeType)
  362. XCTAssertNotNil(downloadResponse?.response)
  363. XCTAssertNotNil(downloadResponse?.destinationURL)
  364. XCTAssertNil(downloadResponse?.error)
  365. XCTAssertEqual(downloadResponse?.response?.statusCode, 204)
  366. XCTAssertNil(downloadResponse?.response?.mimeType)
  367. }
  368. }
  369. // MARK: -
  370. class MultipleValidationTestCase: BaseTestCase {
  371. func testThatValidationForRequestWithAcceptableStatusCodeAndContentTypeResponseSucceeds() {
  372. // Given
  373. let urlString = "https://httpbin.org/ip"
  374. let expectation1 = self.expectation(description: "request should succeed and return ip")
  375. let expectation2 = self.expectation(description: "request should succeed and return ip")
  376. var requestError: Error?
  377. var downloadError: Error?
  378. // When
  379. Alamofire.request(urlString, withMethod: .get)
  380. .validate(statusCode: 200..<300)
  381. .validate(contentType: ["application/json"])
  382. .response { resp in
  383. requestError = resp.error
  384. expectation1.fulfill()
  385. }
  386. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  387. .validate(statusCode: 200..<300)
  388. .validate(contentType: ["application/json"])
  389. .response { resp in
  390. downloadError = resp.error
  391. expectation2.fulfill()
  392. }
  393. waitForExpectations(timeout: timeout, handler: nil)
  394. // Then
  395. XCTAssertNil(requestError)
  396. XCTAssertNil(downloadError)
  397. }
  398. func testThatValidationForRequestWithUnacceptableStatusCodeAndContentTypeResponseFailsWithStatusCodeError() {
  399. // Given
  400. let urlString = "https://httpbin.org/xml"
  401. let expectation1 = self.expectation(description: "request should succeed and return xml")
  402. let expectation2 = self.expectation(description: "download should succeed and return xml")
  403. var requestError: Error?
  404. var downloadError: Error?
  405. // When
  406. Alamofire.request(urlString, withMethod: .get)
  407. .validate(statusCode: 400..<600)
  408. .validate(contentType: ["application/octet-stream"])
  409. .response { resp in
  410. requestError = resp.error
  411. expectation1.fulfill()
  412. }
  413. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  414. .validate(statusCode: 400..<600)
  415. .validate(contentType: ["application/octet-stream"])
  416. .response { resp in
  417. downloadError = resp.error
  418. expectation2.fulfill()
  419. }
  420. waitForExpectations(timeout: timeout, handler: nil)
  421. // Then
  422. XCTAssertNotNil(requestError)
  423. XCTAssertNotNil(downloadError)
  424. for error in [requestError, downloadError] {
  425. if let error = error as? AFError {
  426. XCTAssertTrue(error.isUnacceptableStatusCode)
  427. XCTAssertEqual(error.responseCode, 200)
  428. } else {
  429. XCTFail("error should not be nil")
  430. }
  431. }
  432. }
  433. func testThatValidationForRequestWithUnacceptableStatusCodeAndContentTypeResponseFailsWithContentTypeError() {
  434. // Given
  435. let urlString = "https://httpbin.org/xml"
  436. let expectation1 = self.expectation(description: "request should succeed and return xml")
  437. let expectation2 = self.expectation(description: "download should succeed and return xml")
  438. var requestError: Error?
  439. var downloadError: Error?
  440. // When
  441. Alamofire.request(urlString, withMethod: .get)
  442. .validate(contentType: ["application/octet-stream"])
  443. .validate(statusCode: 400..<600)
  444. .response { resp in
  445. requestError = resp.error
  446. expectation1.fulfill()
  447. }
  448. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  449. .validate(contentType: ["application/octet-stream"])
  450. .validate(statusCode: 400..<600)
  451. .response { resp in
  452. downloadError = resp.error
  453. expectation2.fulfill()
  454. }
  455. waitForExpectations(timeout: timeout, handler: nil)
  456. // Then
  457. XCTAssertNotNil(requestError)
  458. XCTAssertNotNil(downloadError)
  459. for error in [requestError, downloadError] {
  460. if let error = error as? AFError {
  461. XCTAssertTrue(error.isUnacceptableContentType)
  462. XCTAssertEqual(error.responseContentType, "application/xml")
  463. XCTAssertEqual(error.acceptableContentTypes?.first, "application/octet-stream")
  464. } else {
  465. XCTFail("error should not be nil")
  466. }
  467. }
  468. }
  469. }
  470. // MARK: -
  471. class AutomaticValidationTestCase: BaseTestCase {
  472. func testThatValidationForRequestWithAcceptableStatusCodeAndContentTypeResponseSucceeds() {
  473. // Given
  474. let url = URL(string: "https://httpbin.org/ip")!
  475. var urlRequest = URLRequest(url: url)
  476. urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
  477. let expectation1 = self.expectation(description: "request should succeed and return ip")
  478. let expectation2 = self.expectation(description: "download should succeed and return ip")
  479. var requestError: Error?
  480. var downloadError: Error?
  481. // When
  482. Alamofire.request(urlRequest)
  483. .validate()
  484. .response { resp in
  485. requestError = resp.error
  486. expectation1.fulfill()
  487. }
  488. Alamofire.download(urlRequest, to: { _, _ in fileURL })
  489. .validate()
  490. .response { resp in
  491. downloadError = resp.error
  492. expectation2.fulfill()
  493. }
  494. waitForExpectations(timeout: timeout, handler: nil)
  495. // Then
  496. XCTAssertNil(requestError)
  497. XCTAssertNil(downloadError)
  498. }
  499. func testThatValidationForRequestWithUnacceptableStatusCodeResponseFails() {
  500. // Given
  501. let urlString = "https://httpbin.org/status/404"
  502. let expectation1 = self.expectation(description: "request should return 404 status code")
  503. let expectation2 = self.expectation(description: "download should return 404 status code")
  504. var requestError: Error?
  505. var downloadError: Error?
  506. // When
  507. Alamofire.request(urlString, withMethod: .get)
  508. .validate()
  509. .response { resp in
  510. requestError = resp.error
  511. expectation1.fulfill()
  512. }
  513. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  514. .validate()
  515. .response { resp in
  516. downloadError = resp.error
  517. expectation2.fulfill()
  518. }
  519. waitForExpectations(timeout: timeout, handler: nil)
  520. // Then
  521. XCTAssertNotNil(requestError)
  522. XCTAssertNotNil(downloadError)
  523. for error in [requestError, downloadError] {
  524. if let error = error as? AFError, let statusCode = error.responseCode {
  525. XCTAssertTrue(error.isUnacceptableStatusCode)
  526. XCTAssertEqual(statusCode, 404)
  527. } else {
  528. XCTFail("error should not be nil")
  529. }
  530. }
  531. }
  532. func testThatValidationForRequestWithAcceptableWildcardContentTypeResponseSucceeds() {
  533. // Given
  534. let url = URL(string: "https://httpbin.org/ip")!
  535. var urlRequest = URLRequest(url: url)
  536. urlRequest.setValue("application/*", forHTTPHeaderField: "Accept")
  537. let expectation1 = self.expectation(description: "request should succeed and return ip")
  538. let expectation2 = self.expectation(description: "download should succeed and return ip")
  539. var requestError: Error?
  540. var downloadError: Error?
  541. // When
  542. Alamofire.request(urlRequest)
  543. .validate()
  544. .response { resp in
  545. requestError = resp.error
  546. expectation1.fulfill()
  547. }
  548. Alamofire.download(urlRequest, to: { _, _ in fileURL })
  549. .validate()
  550. .response { resp in
  551. downloadError = resp.error
  552. expectation2.fulfill()
  553. }
  554. waitForExpectations(timeout: timeout, handler: nil)
  555. // Then
  556. XCTAssertNil(requestError)
  557. XCTAssertNil(downloadError)
  558. }
  559. func testThatValidationForRequestWithAcceptableComplexContentTypeResponseSucceeds() {
  560. // Given
  561. let url = URL(string: "https://httpbin.org/xml")!
  562. var urlRequest = URLRequest(url: url)
  563. let headerValue = "text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8,*/*;q=0.5"
  564. urlRequest.setValue(headerValue, forHTTPHeaderField: "Accept")
  565. let expectation1 = self.expectation(description: "request should succeed and return xml")
  566. let expectation2 = self.expectation(description: "request should succeed and return xml")
  567. var requestError: Error?
  568. var downloadError: Error?
  569. // When
  570. Alamofire.request(urlRequest)
  571. .validate()
  572. .response { resp in
  573. requestError = resp.error
  574. expectation1.fulfill()
  575. }
  576. Alamofire.download(urlRequest, to: { _, _ in fileURL })
  577. .validate()
  578. .response { resp in
  579. downloadError = resp.error
  580. expectation2.fulfill()
  581. }
  582. waitForExpectations(timeout: timeout, handler: nil)
  583. // Then
  584. XCTAssertNil(requestError)
  585. XCTAssertNil(downloadError)
  586. }
  587. func testThatValidationForRequestWithUnacceptableContentTypeResponseFails() {
  588. // Given
  589. let url = URL(string: "https://httpbin.org/xml")!
  590. var urlRequest = URLRequest(url: url)
  591. urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
  592. let expectation1 = self.expectation(description: "request should succeed and return xml")
  593. let expectation2 = self.expectation(description: "download should succeed and return xml")
  594. var requestError: Error?
  595. var downloadError: Error?
  596. // When
  597. Alamofire.request(urlRequest)
  598. .validate()
  599. .response { resp in
  600. requestError = resp.error
  601. expectation1.fulfill()
  602. }
  603. Alamofire.download(urlRequest, to: { _, _ in fileURL })
  604. .validate()
  605. .response { resp in
  606. downloadError = resp.error
  607. expectation2.fulfill()
  608. }
  609. waitForExpectations(timeout: timeout, handler: nil)
  610. // Then
  611. XCTAssertNotNil(requestError)
  612. XCTAssertNotNil(downloadError)
  613. for error in [requestError, downloadError] {
  614. if let error = error as? AFError {
  615. XCTAssertTrue(error.isUnacceptableContentType)
  616. XCTAssertEqual(error.responseContentType, "application/xml")
  617. XCTAssertEqual(error.acceptableContentTypes?.first, "application/json")
  618. } else {
  619. XCTFail("error should not be nil")
  620. }
  621. }
  622. }
  623. }
  624. // MARK: -
  625. private enum ValidationError: Error {
  626. case missingData, missingFile, fileReadFailed
  627. }
  628. extension DataRequest {
  629. func validateDataExists() -> Self {
  630. return validate { request, response, data in
  631. guard data != nil else { return .failure(ValidationError.missingData) }
  632. return .success
  633. }
  634. }
  635. func validate(with error: Error) -> Self {
  636. return validate { _, _, _ in .failure(error) }
  637. }
  638. }
  639. extension DownloadRequest {
  640. func validateDataExists() -> Self {
  641. return validate { request, response, fileURL in
  642. guard let fileURL = fileURL else { return .failure(ValidationError.missingFile) }
  643. do {
  644. let _ = try Data(contentsOf: fileURL)
  645. return .success
  646. } catch {
  647. return .failure(ValidationError.fileReadFailed)
  648. }
  649. }
  650. }
  651. func validate(with error: Error) -> Self {
  652. return validate { _, _, _ in .failure(error) }
  653. }
  654. }
  655. // MARK: -
  656. class CustomValidationTestCase: BaseTestCase {
  657. func testThatCustomValidationClosureHasAccessToServerResponseData() {
  658. // Given
  659. let urlString = "https://httpbin.org/get"
  660. let expectation1 = self.expectation(description: "request should return 200 status code")
  661. let expectation2 = self.expectation(description: "download should return 200 status code")
  662. var requestError: Error?
  663. var downloadError: Error?
  664. // When
  665. Alamofire.request(urlString, withMethod: .get)
  666. .validate { request, response, data in
  667. guard data != nil else { return .failure(ValidationError.missingData) }
  668. return .success
  669. }
  670. .response { resp in
  671. requestError = resp.error
  672. expectation1.fulfill()
  673. }
  674. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  675. .validate { request, response, fileURL in
  676. guard let fileURL = fileURL else { return .failure(ValidationError.missingFile) }
  677. do {
  678. let _ = try Data(contentsOf: fileURL)
  679. return .success
  680. } catch {
  681. return .failure(ValidationError.fileReadFailed)
  682. }
  683. }
  684. .response { resp in
  685. downloadError = resp.error
  686. expectation2.fulfill()
  687. }
  688. waitForExpectations(timeout: timeout, handler: nil)
  689. // Then
  690. XCTAssertNil(requestError)
  691. XCTAssertNil(downloadError)
  692. }
  693. func testThatCustomValidationCanThrowCustomError() {
  694. // Given
  695. let urlString = "https://httpbin.org/get"
  696. let expectation1 = self.expectation(description: "request should return 200 status code")
  697. let expectation2 = self.expectation(description: "download should return 200 status code")
  698. var requestError: Error?
  699. var downloadError: Error?
  700. // When
  701. Alamofire.request(urlString, withMethod: .get)
  702. .validate { _, _, _ in .failure(ValidationError.missingData) }
  703. .validate { _, _, _ in .failure(ValidationError.missingFile) } // should be ignored
  704. .response { resp in
  705. requestError = resp.error
  706. expectation1.fulfill()
  707. }
  708. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  709. .validate { _, _, _ in .failure(ValidationError.missingFile) }
  710. .validate { _, _, _ in .failure(ValidationError.fileReadFailed) } // should be ignored
  711. .response { resp in
  712. downloadError = resp.error
  713. expectation2.fulfill()
  714. }
  715. waitForExpectations(timeout: timeout, handler: nil)
  716. // Then
  717. XCTAssertEqual(requestError as? ValidationError, ValidationError.missingData)
  718. XCTAssertEqual(downloadError as? ValidationError, ValidationError.missingFile)
  719. }
  720. func testThatValidationExtensionHasAccessToServerResponseData() {
  721. // Given
  722. let urlString = "https://httpbin.org/get"
  723. let expectation1 = self.expectation(description: "request should return 200 status code")
  724. let expectation2 = self.expectation(description: "download should return 200 status code")
  725. var requestError: Error?
  726. var downloadError: Error?
  727. // When
  728. Alamofire.request(urlString, withMethod: .get)
  729. .validateDataExists()
  730. .response { resp in
  731. requestError = resp.error
  732. expectation1.fulfill()
  733. }
  734. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  735. .validateDataExists()
  736. .response { resp in
  737. downloadError = resp.error
  738. expectation2.fulfill()
  739. }
  740. waitForExpectations(timeout: timeout, handler: nil)
  741. // Then
  742. XCTAssertNil(requestError)
  743. XCTAssertNil(downloadError)
  744. }
  745. func testThatValidationExtensionCanThrowCustomError() {
  746. // Given
  747. let urlString = "https://httpbin.org/get"
  748. let expectation1 = self.expectation(description: "request should return 200 status code")
  749. let expectation2 = self.expectation(description: "download should return 200 status code")
  750. var requestError: Error?
  751. var downloadError: Error?
  752. // When
  753. Alamofire.request(urlString, withMethod: .get)
  754. .validate(with: ValidationError.missingData)
  755. .validate(with: ValidationError.missingFile) // should be ignored
  756. .response { resp in
  757. requestError = resp.error
  758. expectation1.fulfill()
  759. }
  760. Alamofire.download(urlString, to: { _, _ in fileURL }, withMethod: .get)
  761. .validate(with: ValidationError.missingFile)
  762. .validate(with: ValidationError.fileReadFailed) // should be ignored
  763. .response { resp in
  764. downloadError = resp.error
  765. expectation2.fulfill()
  766. }
  767. waitForExpectations(timeout: timeout, handler: nil)
  768. // Then
  769. XCTAssertEqual(requestError as? ValidationError, ValidationError.missingData)
  770. XCTAssertEqual(downloadError as? ValidationError, ValidationError.missingFile)
  771. }
  772. }