RequestTests.swift 20 KB

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