RequestTests.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. //
  2. // RequestTests.swift
  3. //
  4. // Copyright (c) 2014-2018 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. import Alamofire
  25. import Foundation
  26. import XCTest
  27. // MARK: -
  28. class RequestResponseTestCase: BaseTestCase {
  29. func testRequestResponse() {
  30. // Given
  31. let urlString = "https://httpbin.org/get"
  32. let expectation = self.expectation(description: "GET request should succeed: \(urlString)")
  33. var response: DataResponse<Data?>?
  34. // When
  35. AF.request(urlString, parameters: ["foo": "bar"])
  36. .response { resp in
  37. response = resp
  38. expectation.fulfill()
  39. }
  40. waitForExpectations(timeout: timeout, handler: nil)
  41. // Then
  42. XCTAssertNotNil(response?.request)
  43. XCTAssertNotNil(response?.response)
  44. XCTAssertNotNil(response?.data)
  45. XCTAssertNil(response?.error)
  46. }
  47. func testRequestResponseWithProgress() {
  48. // Given
  49. let randomBytes = 1 * 1024 * 1024
  50. let urlString = "https://httpbin.org/bytes/\(randomBytes)"
  51. let expectation = self.expectation(description: "Bytes download progress should be reported: \(urlString)")
  52. var progressValues: [Double] = []
  53. var response: DataResponse<Data?>?
  54. // When
  55. AF.request(urlString)
  56. .downloadProgress { progress in
  57. progressValues.append(progress.fractionCompleted)
  58. }
  59. .response { resp in
  60. response = resp
  61. expectation.fulfill()
  62. }
  63. waitForExpectations(timeout: timeout, handler: nil)
  64. // Then
  65. XCTAssertNotNil(response?.request)
  66. XCTAssertNotNil(response?.response)
  67. XCTAssertNotNil(response?.data)
  68. XCTAssertNil(response?.error)
  69. var previousProgress: Double = progressValues.first ?? 0.0
  70. for progress in progressValues {
  71. XCTAssertGreaterThanOrEqual(progress, previousProgress)
  72. previousProgress = progress
  73. }
  74. if let lastProgressValue = progressValues.last {
  75. XCTAssertEqual(lastProgressValue, 1.0)
  76. } else {
  77. XCTFail("last item in progressValues should not be nil")
  78. }
  79. }
  80. func testPOSTRequestWithUnicodeParameters() {
  81. // Given
  82. let urlString = "https://httpbin.org/post"
  83. let parameters = [
  84. "french": "français",
  85. "japanese": "日本語",
  86. "arabic": "العربية",
  87. "emoji": "😃"
  88. ]
  89. let expectation = self.expectation(description: "request should succeed")
  90. var response: DataResponse<Any>?
  91. // When
  92. AF.request(urlString, method: .post, parameters: parameters)
  93. .responseJSON { closureResponse in
  94. response = closureResponse
  95. expectation.fulfill()
  96. }
  97. waitForExpectations(timeout: timeout, handler: nil)
  98. // Then
  99. XCTAssertNotNil(response?.request)
  100. XCTAssertNotNil(response?.response)
  101. XCTAssertNotNil(response?.data)
  102. if let json = response?.result.value as? [String: Any], let form = json["form"] as? [String: String] {
  103. XCTAssertEqual(form["french"], parameters["french"])
  104. XCTAssertEqual(form["japanese"], parameters["japanese"])
  105. XCTAssertEqual(form["arabic"], parameters["arabic"])
  106. XCTAssertEqual(form["emoji"], parameters["emoji"])
  107. } else {
  108. XCTFail("form parameter in JSON should not be nil")
  109. }
  110. }
  111. func testPOSTRequestWithBase64EncodedImages() {
  112. // Given
  113. let urlString = "https://httpbin.org/post"
  114. let pngBase64EncodedString: String = {
  115. let URL = url(forResource: "unicorn", withExtension: "png")
  116. let data = try! Data(contentsOf: URL)
  117. return data.base64EncodedString(options: .lineLength64Characters)
  118. }()
  119. let jpegBase64EncodedString: String = {
  120. let URL = url(forResource: "rainbow", withExtension: "jpg")
  121. let data = try! Data(contentsOf: URL)
  122. return data.base64EncodedString(options: .lineLength64Characters)
  123. }()
  124. let parameters = [
  125. "email": "user@alamofire.org",
  126. "png_image": pngBase64EncodedString,
  127. "jpeg_image": jpegBase64EncodedString
  128. ]
  129. let expectation = self.expectation(description: "request should succeed")
  130. var response: DataResponse<Any>?
  131. // When
  132. AF.request(urlString, method: .post, parameters: parameters)
  133. .responseJSON { closureResponse in
  134. response = closureResponse
  135. expectation.fulfill()
  136. }
  137. waitForExpectations(timeout: timeout, handler: nil)
  138. // Then
  139. XCTAssertNotNil(response?.request)
  140. XCTAssertNotNil(response?.response)
  141. XCTAssertNotNil(response?.data)
  142. XCTAssertEqual(response?.result.isSuccess, true)
  143. if let json = response?.result.value as? [String: Any], let form = json["form"] as? [String: String] {
  144. XCTAssertEqual(form["email"], parameters["email"])
  145. XCTAssertEqual(form["png_image"], parameters["png_image"])
  146. XCTAssertEqual(form["jpeg_image"], parameters["jpeg_image"])
  147. } else {
  148. XCTFail("form parameter in JSON should not be nil")
  149. }
  150. }
  151. // MARK: Serialization Queue
  152. func testThatResponseSerializationWorksWithSerializationQueue() {
  153. // Given
  154. let queue = DispatchQueue(label: "org.alamofire.serializationQueue")
  155. let manager = Session(serializationQueue: queue)
  156. let expectation = self.expectation(description: "request should complete")
  157. var response: DataResponse<Any>?
  158. // When
  159. manager.request("https://httpbin.org/get").responseJSON { (resp) in
  160. response = resp
  161. expectation.fulfill()
  162. }
  163. waitForExpectations(timeout: timeout, handler: nil)
  164. // Then
  165. XCTAssertEqual(response?.result.isSuccess, true)
  166. }
  167. // MARK: Encodable Parameters
  168. func testThatRequestsCanPassEncodableParametersAsJSONBodyData() {
  169. // Given
  170. let parameters = HTTPBinParameters(property: "one")
  171. let expect = expectation(description: "request should complete")
  172. var receivedResponse: DataResponse<HTTPBinResponse>?
  173. // When
  174. AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default)
  175. .responseJSONDecodable { (response: DataResponse<HTTPBinResponse>) in
  176. receivedResponse = response
  177. expect.fulfill()
  178. }
  179. waitForExpectations(timeout: timeout, handler: nil)
  180. // Then
  181. XCTAssertEqual(receivedResponse?.result.value?.data, "{\"property\":\"one\"}")
  182. }
  183. func testThatRequestsCanPassEncodableParametersAsAURLQuery() {
  184. // Given
  185. let parameters = HTTPBinParameters(property: "one")
  186. let expect = expectation(description: "request should complete")
  187. var receivedResponse: DataResponse<HTTPBinResponse>?
  188. // When
  189. AF.request("https://httpbin.org/get", method: .get, parameters: parameters)
  190. .responseJSONDecodable { (response: DataResponse<HTTPBinResponse>) in
  191. receivedResponse = response
  192. expect.fulfill()
  193. }
  194. waitForExpectations(timeout: timeout, handler: nil)
  195. // Then
  196. XCTAssertEqual(receivedResponse?.result.value?.args, ["property": "one"])
  197. }
  198. func testThatRequestsCanPassEncodableParametersAsURLEncodedBodyData() {
  199. // Given
  200. let parameters = HTTPBinParameters(property: "one")
  201. let expect = expectation(description: "request should complete")
  202. var receivedResponse: DataResponse<HTTPBinResponse>?
  203. // When
  204. AF.request("https://httpbin.org/post", method: .post, parameters: parameters)
  205. .responseJSONDecodable { (response: DataResponse<HTTPBinResponse>) in
  206. receivedResponse = response
  207. expect.fulfill()
  208. }
  209. waitForExpectations(timeout: timeout, handler: nil)
  210. // Then
  211. XCTAssertEqual(receivedResponse?.result.value?.form, ["property": "one"])
  212. }
  213. }
  214. // MARK: -
  215. class RequestDescriptionTestCase: BaseTestCase {
  216. func testRequestDescription() {
  217. // Given
  218. let urlString = "https://httpbin.org/get"
  219. let manager = Session(startRequestsImmediately: false)
  220. let request = manager.request(urlString)
  221. let initialRequestDescription = request.description
  222. let expectation = self.expectation(description: "Request description should update: \(urlString)")
  223. var response: HTTPURLResponse?
  224. // When
  225. request.response { resp in
  226. response = resp.response
  227. expectation.fulfill()
  228. }.resume()
  229. waitForExpectations(timeout: timeout, handler: nil)
  230. let finalRequestDescription = request.description
  231. // Then
  232. XCTAssertEqual(initialRequestDescription, "No request created yet.")
  233. XCTAssertEqual(finalRequestDescription, "GET https://httpbin.org/get (\(response?.statusCode ?? -1))")
  234. }
  235. }
  236. // MARK: -
  237. class RequestDebugDescriptionTestCase: BaseTestCase {
  238. // MARK: Properties
  239. let manager: Session = {
  240. let manager = Session()
  241. return manager
  242. }()
  243. let managerWithAcceptLanguageHeader: Session = {
  244. var headers = HTTPHeaders.default
  245. headers["Accept-Language"] = "en-US"
  246. let configuration = URLSessionConfiguration.alamofireDefault
  247. configuration.httpHeaders = headers
  248. let manager = Session(configuration: configuration)
  249. return manager
  250. }()
  251. let managerWithContentTypeHeader: Session = {
  252. var headers = HTTPHeaders.default
  253. headers["Content-Type"] = "application/json"
  254. let configuration = URLSessionConfiguration.alamofireDefault
  255. configuration.httpHeaders = headers
  256. let manager = Session(configuration: configuration)
  257. return manager
  258. }()
  259. func managerWithCookie(_ cookie: HTTPCookie) -> Session {
  260. let configuration = URLSessionConfiguration.alamofireDefault
  261. configuration.httpCookieStorage?.setCookie(cookie)
  262. return Session(configuration: configuration)
  263. }
  264. let managerDisallowingCookies: Session = {
  265. let configuration = URLSessionConfiguration.alamofireDefault
  266. configuration.httpShouldSetCookies = false
  267. let manager = Session(configuration: configuration)
  268. return manager
  269. }()
  270. // MARK: Tests
  271. func testGETRequestDebugDescription() {
  272. // Given
  273. let urlString = "https://httpbin.org/get"
  274. let expectation = self.expectation(description: "request should complete")
  275. // When
  276. let request = manager.request(urlString).response { _ in expectation.fulfill() }
  277. waitForExpectations(timeout: timeout, handler: nil)
  278. let components = cURLCommandComponents(for: request)
  279. // Then
  280. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  281. XCTAssertTrue(components.contains("-X"))
  282. XCTAssertEqual(components.last, "\"\(urlString)\"")
  283. }
  284. func testGETRequestWithJSONHeaderDebugDescription() {
  285. // Given
  286. let urlString = "https://httpbin.org/get"
  287. let expectation = self.expectation(description: "request should complete")
  288. // When
  289. let headers: HTTPHeaders = [ "X-Custom-Header": "{\"key\": \"value\"}" ]
  290. let request = manager.request(urlString, headers: headers).response { _ in expectation.fulfill() }
  291. waitForExpectations(timeout: timeout, handler: nil)
  292. // Then
  293. XCTAssertNotNil(request.debugDescription.range(of: "-H \"X-Custom-Header: {\\\"key\\\": \\\"value\\\"}\""))
  294. }
  295. func testGETRequestWithDuplicateHeadersDebugDescription() {
  296. // Given
  297. let urlString = "https://httpbin.org/get"
  298. let expectation = self.expectation(description: "request should complete")
  299. // When
  300. let headers: HTTPHeaders = [ "Accept-Language": "en-GB" ]
  301. let request = managerWithAcceptLanguageHeader.request(urlString, headers: headers).response { _ in expectation.fulfill() }
  302. waitForExpectations(timeout: timeout, handler: nil)
  303. let components = cURLCommandComponents(for: request)
  304. // Then
  305. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  306. XCTAssertTrue(components.contains("-X"))
  307. XCTAssertEqual(components.last, "\"\(urlString)\"")
  308. let tokens = request.debugDescription.components(separatedBy: "Accept-Language:")
  309. XCTAssertTrue(tokens.count == 2, "command should contain a single Accept-Language header")
  310. XCTAssertNotNil(request.debugDescription.range(of: "-H \"Accept-Language: en-GB\""))
  311. }
  312. func testPOSTRequestDebugDescription() {
  313. // Given
  314. let urlString = "https://httpbin.org/post"
  315. let expectation = self.expectation(description: "request should complete")
  316. // When
  317. let request = manager.request(urlString, method: .post).response { _ in expectation.fulfill() }
  318. waitForExpectations(timeout: timeout, handler: nil)
  319. let components = cURLCommandComponents(for: request)
  320. // Then
  321. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  322. XCTAssertEqual(components[3..<5], ["-X", "POST"])
  323. XCTAssertEqual(components.last, "\"\(urlString)\"")
  324. }
  325. func testPOSTRequestWithJSONParametersDebugDescription() {
  326. // Given
  327. let urlString = "https://httpbin.org/post"
  328. let expectation = self.expectation(description: "request should complete")
  329. let parameters = [
  330. "foo": "bar",
  331. "fo\"o": "b\"ar",
  332. "f'oo": "ba'r"
  333. ]
  334. // When
  335. let request = manager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default).response {
  336. _ in expectation.fulfill()
  337. }
  338. waitForExpectations(timeout: timeout, handler: nil)
  339. let components = cURLCommandComponents(for: request)
  340. // Then
  341. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  342. XCTAssertEqual(components[3..<5], ["-X", "POST"])
  343. XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: application/json\""))
  344. XCTAssertNotNil(request.debugDescription.range(of: "-d \"{"))
  345. XCTAssertNotNil(request.debugDescription.range(of: "\\\"f'oo\\\":\\\"ba'r\\\""))
  346. XCTAssertNotNil(request.debugDescription.range(of: "\\\"fo\\\\\\\"o\\\":\\\"b\\\\\\\"ar\\\""))
  347. XCTAssertNotNil(request.debugDescription.range(of: "\\\"foo\\\":\\\"bar\\"))
  348. XCTAssertEqual(components.last, "\"\(urlString)\"")
  349. }
  350. func testPOSTRequestWithCookieDebugDescription() {
  351. // Given
  352. let urlString = "https://httpbin.org/post"
  353. let properties = [
  354. HTTPCookiePropertyKey.domain: "httpbin.org",
  355. HTTPCookiePropertyKey.path: "/post",
  356. HTTPCookiePropertyKey.name: "foo",
  357. HTTPCookiePropertyKey.value: "bar",
  358. ]
  359. let cookie = HTTPCookie(properties: properties)!
  360. let cookieManager = managerWithCookie(cookie)
  361. let expectation = self.expectation(description: "request should complete")
  362. // When
  363. let request = cookieManager.request(urlString, method: .post).response { _ in expectation.fulfill() }
  364. waitForExpectations(timeout: timeout, handler: nil)
  365. let components = cURLCommandComponents(for: request)
  366. // Then
  367. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  368. XCTAssertEqual(components[3..<5], ["-X", "POST"])
  369. XCTAssertEqual(components.last, "\"\(urlString)\"")
  370. XCTAssertEqual(components[5..<6], ["-b"])
  371. }
  372. func testPOSTRequestWithCookiesDisabledDebugDescription() {
  373. // Given
  374. let urlString = "https://httpbin.org/post"
  375. let properties = [
  376. HTTPCookiePropertyKey.domain: "httpbin.org",
  377. HTTPCookiePropertyKey.path: "/post",
  378. HTTPCookiePropertyKey.name: "foo",
  379. HTTPCookiePropertyKey.value: "bar",
  380. ]
  381. let cookie = HTTPCookie(properties: properties)!
  382. managerDisallowingCookies.session.configuration.httpCookieStorage?.setCookie(cookie)
  383. // When
  384. let request = managerDisallowingCookies.request(urlString, method: .post)
  385. let components = cURLCommandComponents(for: request)
  386. // Then
  387. let cookieComponents = components.filter { $0 == "-b" }
  388. XCTAssertTrue(cookieComponents.isEmpty)
  389. }
  390. func testMultipartFormDataRequestWithDuplicateHeadersDebugDescription() {
  391. // Given
  392. let urlString = "https://httpbin.org/post"
  393. let japaneseData = Data("日本語".utf8)
  394. let expectation = self.expectation(description: "multipart form data encoding should succeed")
  395. // When
  396. let request = managerWithContentTypeHeader.upload(multipartFormData: { (data) in
  397. data.append(japaneseData, withName: "japanese")
  398. }, to: urlString)
  399. .response { _ in
  400. expectation.fulfill()
  401. }
  402. waitForExpectations(timeout: timeout, handler: nil)
  403. let components = cURLCommandComponents(for: request)
  404. // Then
  405. XCTAssertEqual(components[0..<3], ["$", "curl", "-v"])
  406. XCTAssertTrue(components.contains("-X"))
  407. XCTAssertEqual(components.last, "\"\(urlString)\"")
  408. let tokens = request.debugDescription.components(separatedBy: "Content-Type:")
  409. XCTAssertTrue(tokens.count == 2, "command should contain a single Content-Type header")
  410. XCTAssertNotNil(request.debugDescription.range(of: "-H \"Content-Type: multipart/form-data;"))
  411. }
  412. func testThatRequestWithInvalidURLDebugDescription() {
  413. // Given
  414. let urlString = "invalid_url"
  415. let expectation = self.expectation(description: "request should complete")
  416. // When
  417. let request = manager.request(urlString).response { _ in expectation.fulfill() }
  418. waitForExpectations(timeout: timeout, handler: nil)
  419. let debugDescription = request.debugDescription
  420. // Then
  421. XCTAssertNotNil(debugDescription, "debugDescription should not crash")
  422. }
  423. // MARK: Test Helper Methods
  424. private func cURLCommandComponents(for request: Request) -> [String] {
  425. let whitespaceCharacterSet = CharacterSet.whitespacesAndNewlines
  426. return request.debugDescription
  427. .components(separatedBy: whitespaceCharacterSet)
  428. .filter { $0 != "" && $0 != "\\" }
  429. }
  430. }