MultipartFormDataTests.swift 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. //
  2. // MultipartFormDataTests.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. struct EncodingCharacters {
  28. static let crlf = "\r\n"
  29. }
  30. struct BoundaryGenerator {
  31. enum BoundaryType {
  32. case initial, encapsulated, final
  33. }
  34. static func boundary(forBoundaryType boundaryType: BoundaryType, boundaryKey: String) -> String {
  35. let boundary: String
  36. switch boundaryType {
  37. case .initial:
  38. boundary = "--\(boundaryKey)\(EncodingCharacters.crlf)"
  39. case .encapsulated:
  40. boundary = "\(EncodingCharacters.crlf)--\(boundaryKey)\(EncodingCharacters.crlf)"
  41. case .final:
  42. boundary = "\(EncodingCharacters.crlf)--\(boundaryKey)--\(EncodingCharacters.crlf)"
  43. }
  44. return boundary
  45. }
  46. static func boundaryData(boundaryType: BoundaryType, boundaryKey: String) -> Data {
  47. return BoundaryGenerator.boundary(
  48. forBoundaryType: boundaryType,
  49. boundaryKey: boundaryKey
  50. ).data(using: .utf8, allowLossyConversion: false)!
  51. }
  52. }
  53. private func temporaryFileURL() -> URL { return BaseTestCase.testDirectoryURL.appendingPathComponent(UUID().uuidString) }
  54. // MARK: -
  55. class MultipartFormDataPropertiesTestCase: BaseTestCase {
  56. func testThatContentTypeContainsBoundary() {
  57. // Given
  58. let multipartFormData = MultipartFormData()
  59. // When
  60. let boundary = multipartFormData.boundary
  61. // Then
  62. let expectedContentType = "multipart/form-data; boundary=\(boundary)"
  63. XCTAssertEqual(multipartFormData.contentType, expectedContentType, "contentType should match expected value")
  64. }
  65. func testThatContentLengthMatchesTotalBodyPartSize() {
  66. // Given
  67. let multipartFormData = MultipartFormData()
  68. let data1 = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  69. let data2 = "Vim at integre alterum.".data(using: .utf8, allowLossyConversion: false)!
  70. // When
  71. multipartFormData.append(data1, withName: "data1")
  72. multipartFormData.append(data2, withName: "data2")
  73. // Then
  74. let expectedContentLength = UInt64(data1.count + data2.count)
  75. XCTAssertEqual(multipartFormData.contentLength, expectedContentLength, "content length should match expected value")
  76. }
  77. }
  78. // MARK: -
  79. class MultipartFormDataEncodingTestCase: BaseTestCase {
  80. let crlf = EncodingCharacters.crlf
  81. func testEncodingDataBodyPart() {
  82. // Given
  83. let multipartFormData = MultipartFormData()
  84. let data = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  85. multipartFormData.append(data, withName: "data")
  86. var encodedData: Data?
  87. // When
  88. do {
  89. encodedData = try multipartFormData.encode()
  90. } catch {
  91. // No-op
  92. }
  93. // Then
  94. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  95. if let encodedData = encodedData {
  96. let boundary = multipartFormData.boundary
  97. let expectedData = (
  98. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  99. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  100. "Lorem ipsum dolor sit amet." +
  101. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  102. ).data(using: .utf8, allowLossyConversion: false)!
  103. XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  104. }
  105. }
  106. func testEncodingMultipleDataBodyParts() {
  107. // Given
  108. let multipartFormData = MultipartFormData()
  109. let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
  110. let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!
  111. let emojiData = "😃👍🏻🍻🎉".data(using: .utf8, allowLossyConversion: false)!
  112. multipartFormData.append(frenchData, withName: "french")
  113. multipartFormData.append(japaneseData, withName: "japanese", mimeType: "text/plain")
  114. multipartFormData.append(emojiData, withName: "emoji", mimeType: "text/plain")
  115. var encodedData: Data?
  116. // When
  117. do {
  118. encodedData = try multipartFormData.encode()
  119. } catch {
  120. // No-op
  121. }
  122. // Then
  123. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  124. if let encodedData = encodedData {
  125. let boundary = multipartFormData.boundary
  126. let expectedData = (
  127. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  128. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  129. "français" +
  130. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  131. "Content-Disposition: form-data; name=\"japanese\"\(crlf)" +
  132. "Content-Type: text/plain\(crlf)\(crlf)" +
  133. "日本語" +
  134. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  135. "Content-Disposition: form-data; name=\"emoji\"\(crlf)" +
  136. "Content-Type: text/plain\(crlf)\(crlf)" +
  137. "😃👍🏻🍻🎉" +
  138. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  139. ).data(using: .utf8, allowLossyConversion: false)!
  140. XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  141. }
  142. }
  143. func testEncodingFileBodyPart() {
  144. // Given
  145. let multipartFormData = MultipartFormData()
  146. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  147. multipartFormData.append(unicornImageURL, withName: "unicorn")
  148. var encodedData: Data?
  149. // When
  150. do {
  151. encodedData = try multipartFormData.encode()
  152. } catch {
  153. // No-op
  154. }
  155. // Then
  156. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  157. if let encodedData = encodedData {
  158. let boundary = multipartFormData.boundary
  159. var expectedData = Data()
  160. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  161. expectedData.append((
  162. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  163. "Content-Type: image/png\(crlf)\(crlf)"
  164. ).data(using: .utf8, allowLossyConversion: false)!
  165. )
  166. expectedData.append(try! Data(contentsOf: unicornImageURL))
  167. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  168. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  169. }
  170. }
  171. func testEncodingMultipleFileBodyParts() {
  172. // Given
  173. let multipartFormData = MultipartFormData()
  174. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  175. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  176. multipartFormData.append(unicornImageURL, withName: "unicorn")
  177. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  178. var encodedData: Data?
  179. // When
  180. do {
  181. encodedData = try multipartFormData.encode()
  182. } catch {
  183. // No-op
  184. }
  185. // Then
  186. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  187. if let encodedData = encodedData {
  188. let boundary = multipartFormData.boundary
  189. var expectedData = Data()
  190. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  191. expectedData.append((
  192. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  193. "Content-Type: image/png\(crlf)\(crlf)"
  194. ).data(using: .utf8, allowLossyConversion: false)!
  195. )
  196. expectedData.append(try! Data(contentsOf: unicornImageURL))
  197. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  198. expectedData.append((
  199. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  200. "Content-Type: image/jpeg\(crlf)\(crlf)"
  201. ).data(using: .utf8, allowLossyConversion: false)!
  202. )
  203. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  204. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  205. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  206. }
  207. }
  208. func testEncodingStreamBodyPart() {
  209. // Given
  210. let multipartFormData = MultipartFormData()
  211. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  212. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  213. let unicornStream = InputStream(url: unicornImageURL)!
  214. multipartFormData.append(
  215. unicornStream,
  216. withLength: unicornDataLength,
  217. name: "unicorn",
  218. fileName: "unicorn.png",
  219. mimeType: "image/png"
  220. )
  221. var encodedData: Data?
  222. // When
  223. do {
  224. encodedData = try multipartFormData.encode()
  225. } catch {
  226. // No-op
  227. }
  228. // Then
  229. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  230. if let encodedData = encodedData {
  231. let boundary = multipartFormData.boundary
  232. var expectedData = Data()
  233. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  234. expectedData.append((
  235. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  236. "Content-Type: image/png\(crlf)\(crlf)"
  237. ).data(using: .utf8, allowLossyConversion: false)!
  238. )
  239. expectedData.append(try! Data(contentsOf: unicornImageURL))
  240. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  241. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  242. }
  243. }
  244. func testEncodingMultipleStreamBodyParts() {
  245. // Given
  246. let multipartFormData = MultipartFormData()
  247. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  248. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  249. let unicornStream = InputStream(url: unicornImageURL)!
  250. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  251. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  252. let rainbowStream = InputStream(url: rainbowImageURL)!
  253. multipartFormData.append(
  254. unicornStream,
  255. withLength: unicornDataLength,
  256. name: "unicorn",
  257. fileName: "unicorn.png",
  258. mimeType: "image/png"
  259. )
  260. multipartFormData.append(
  261. rainbowStream,
  262. withLength: rainbowDataLength,
  263. name: "rainbow",
  264. fileName: "rainbow.jpg",
  265. mimeType: "image/jpeg"
  266. )
  267. var encodedData: Data?
  268. // When
  269. do {
  270. encodedData = try multipartFormData.encode()
  271. } catch {
  272. // No-op
  273. }
  274. // Then
  275. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  276. if let encodedData = encodedData {
  277. let boundary = multipartFormData.boundary
  278. var expectedData = Data()
  279. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  280. expectedData.append((
  281. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  282. "Content-Type: image/png\(crlf)\(crlf)"
  283. ).data(using: .utf8, allowLossyConversion: false)!
  284. )
  285. expectedData.append(try! Data(contentsOf: unicornImageURL))
  286. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  287. expectedData.append((
  288. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  289. "Content-Type: image/jpeg\(crlf)\(crlf)"
  290. ).data(using: .utf8, allowLossyConversion: false)!
  291. )
  292. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  293. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  294. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  295. }
  296. }
  297. func testEncodingMultipleBodyPartsWithVaryingTypes() {
  298. // Given
  299. let multipartFormData = MultipartFormData()
  300. let loremData = "Lorem ipsum.".data(using: .utf8, allowLossyConversion: false)!
  301. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  302. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  303. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  304. let rainbowStream = InputStream(url: rainbowImageURL)!
  305. multipartFormData.append(loremData, withName: "lorem")
  306. multipartFormData.append(unicornImageURL, withName: "unicorn")
  307. multipartFormData.append(
  308. rainbowStream,
  309. withLength: rainbowDataLength,
  310. name: "rainbow",
  311. fileName: "rainbow.jpg",
  312. mimeType: "image/jpeg"
  313. )
  314. var encodedData: Data?
  315. // When
  316. do {
  317. encodedData = try multipartFormData.encode()
  318. } catch {
  319. // No-op
  320. }
  321. // Then
  322. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  323. if let encodedData = encodedData {
  324. let boundary = multipartFormData.boundary
  325. var expectedData = Data()
  326. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  327. expectedData.append((
  328. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)"
  329. ).data(using: .utf8, allowLossyConversion: false)!
  330. )
  331. expectedData.append(loremData)
  332. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  333. expectedData.append((
  334. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  335. "Content-Type: image/png\(crlf)\(crlf)"
  336. ).data(using: .utf8, allowLossyConversion: false)!
  337. )
  338. expectedData.append(try! Data(contentsOf: unicornImageURL))
  339. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  340. expectedData.append((
  341. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  342. "Content-Type: image/jpeg\(crlf)\(crlf)"
  343. ).data(using: .utf8, allowLossyConversion: false)!
  344. )
  345. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  346. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  347. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  348. }
  349. }
  350. }
  351. // MARK: -
  352. class MultipartFormDataWriteEncodedDataToDiskTestCase: BaseTestCase {
  353. let crlf = EncodingCharacters.crlf
  354. func testWritingEncodedDataBodyPartToDisk() {
  355. // Given
  356. let fileURL = temporaryFileURL()
  357. let multipartFormData = MultipartFormData()
  358. let data = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  359. multipartFormData.append(data, withName: "data")
  360. var encodingError: Error?
  361. // When
  362. do {
  363. try multipartFormData.writeEncodedData(to: fileURL)
  364. } catch {
  365. encodingError = error
  366. }
  367. // Then
  368. XCTAssertNil(encodingError, "encoding error should be nil")
  369. if let fileData = try? Data(contentsOf: fileURL) {
  370. let boundary = multipartFormData.boundary
  371. let expectedFileData = (
  372. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  373. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  374. "Lorem ipsum dolor sit amet." +
  375. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  376. ).data(using: .utf8, allowLossyConversion: false)!
  377. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  378. } else {
  379. XCTFail("file data should not be nil")
  380. }
  381. }
  382. func testWritingMultipleEncodedDataBodyPartsToDisk() {
  383. // Given
  384. let fileURL = temporaryFileURL()
  385. let multipartFormData = MultipartFormData()
  386. let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
  387. let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!
  388. let emojiData = "😃👍🏻🍻🎉".data(using: .utf8, allowLossyConversion: false)!
  389. multipartFormData.append(frenchData, withName: "french")
  390. multipartFormData.append(japaneseData, withName: "japanese")
  391. multipartFormData.append(emojiData, withName: "emoji")
  392. var encodingError: Error?
  393. // When
  394. do {
  395. try multipartFormData.writeEncodedData(to: fileURL)
  396. } catch {
  397. encodingError = error
  398. }
  399. // Then
  400. XCTAssertNil(encodingError, "encoding error should be nil")
  401. if let fileData = try? Data(contentsOf: fileURL) {
  402. let boundary = multipartFormData.boundary
  403. let expectedFileData = (
  404. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  405. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  406. "français" +
  407. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  408. "Content-Disposition: form-data; name=\"japanese\"\(crlf)\(crlf)" +
  409. "日本語" +
  410. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  411. "Content-Disposition: form-data; name=\"emoji\"\(crlf)\(crlf)" +
  412. "😃👍🏻🍻🎉" +
  413. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  414. ).data(using: .utf8, allowLossyConversion: false)!
  415. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  416. } else {
  417. XCTFail("file data should not be nil")
  418. }
  419. }
  420. func testWritingEncodedFileBodyPartToDisk() {
  421. // Given
  422. let fileURL = temporaryFileURL()
  423. let multipartFormData = MultipartFormData()
  424. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  425. multipartFormData.append(unicornImageURL, withName: "unicorn")
  426. var encodingError: Error?
  427. // When
  428. do {
  429. try multipartFormData.writeEncodedData(to: fileURL)
  430. } catch {
  431. encodingError = error
  432. }
  433. // Then
  434. XCTAssertNil(encodingError, "encoding error should be nil")
  435. if let fileData = try? Data(contentsOf: fileURL) {
  436. let boundary = multipartFormData.boundary
  437. var expectedFileData = Data()
  438. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  439. expectedFileData.append((
  440. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  441. "Content-Type: image/png\(crlf)\(crlf)"
  442. ).data(using: .utf8, allowLossyConversion: false)!
  443. )
  444. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  445. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  446. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  447. } else {
  448. XCTFail("file data should not be nil")
  449. }
  450. }
  451. func testWritingMultipleEncodedFileBodyPartsToDisk() {
  452. // Given
  453. let fileURL = temporaryFileURL()
  454. let multipartFormData = MultipartFormData()
  455. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  456. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  457. multipartFormData.append(unicornImageURL, withName: "unicorn")
  458. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  459. var encodingError: Error?
  460. // When
  461. do {
  462. try multipartFormData.writeEncodedData(to: fileURL)
  463. } catch {
  464. encodingError = error
  465. }
  466. // Then
  467. XCTAssertNil(encodingError, "encoding error should be nil")
  468. if let fileData = try? Data(contentsOf: fileURL) {
  469. let boundary = multipartFormData.boundary
  470. var expectedFileData = Data()
  471. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  472. expectedFileData.append((
  473. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  474. "Content-Type: image/png\(crlf)\(crlf)"
  475. ).data(using: .utf8, allowLossyConversion: false)!
  476. )
  477. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  478. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  479. expectedFileData.append((
  480. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  481. "Content-Type: image/jpeg\(crlf)\(crlf)"
  482. ).data(using: .utf8, allowLossyConversion: false)!
  483. )
  484. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  485. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  486. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  487. } else {
  488. XCTFail("file data should not be nil")
  489. }
  490. }
  491. func testWritingEncodedStreamBodyPartToDisk() {
  492. // Given
  493. let fileURL = temporaryFileURL()
  494. let multipartFormData = MultipartFormData()
  495. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  496. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  497. let unicornStream = InputStream(url: unicornImageURL)!
  498. multipartFormData.append(
  499. unicornStream,
  500. withLength: unicornDataLength,
  501. name: "unicorn",
  502. fileName: "unicorn.png",
  503. mimeType: "image/png"
  504. )
  505. var encodingError: Error?
  506. // When
  507. do {
  508. try multipartFormData.writeEncodedData(to: fileURL)
  509. } catch {
  510. encodingError = error
  511. }
  512. // Then
  513. XCTAssertNil(encodingError, "encoding error should be nil")
  514. if let fileData = try? Data(contentsOf: fileURL) {
  515. let boundary = multipartFormData.boundary
  516. var expectedFileData = Data()
  517. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  518. expectedFileData.append((
  519. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  520. "Content-Type: image/png\(crlf)\(crlf)"
  521. ).data(using: .utf8, allowLossyConversion: false)!
  522. )
  523. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  524. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  525. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  526. } else {
  527. XCTFail("file data should not be nil")
  528. }
  529. }
  530. func testWritingMultipleEncodedStreamBodyPartsToDisk() {
  531. // Given
  532. let fileURL = temporaryFileURL()
  533. let multipartFormData = MultipartFormData()
  534. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  535. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  536. let unicornStream = InputStream(url: unicornImageURL)!
  537. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  538. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  539. let rainbowStream = InputStream(url: rainbowImageURL)!
  540. multipartFormData.append(
  541. unicornStream,
  542. withLength: unicornDataLength,
  543. name: "unicorn",
  544. fileName: "unicorn.png",
  545. mimeType: "image/png"
  546. )
  547. multipartFormData.append(
  548. rainbowStream,
  549. withLength: rainbowDataLength,
  550. name: "rainbow",
  551. fileName: "rainbow.jpg",
  552. mimeType: "image/jpeg"
  553. )
  554. var encodingError: Error?
  555. // When
  556. do {
  557. try multipartFormData.writeEncodedData(to: fileURL)
  558. } catch {
  559. encodingError = error
  560. }
  561. // Then
  562. XCTAssertNil(encodingError, "encoding error should be nil")
  563. if let fileData = try? Data(contentsOf: fileURL) {
  564. let boundary = multipartFormData.boundary
  565. var expectedFileData = Data()
  566. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  567. expectedFileData.append((
  568. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  569. "Content-Type: image/png\(crlf)\(crlf)"
  570. ).data(using: .utf8, allowLossyConversion: false)!
  571. )
  572. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  573. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  574. expectedFileData.append((
  575. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  576. "Content-Type: image/jpeg\(crlf)\(crlf)"
  577. ).data(using: .utf8, allowLossyConversion: false)!
  578. )
  579. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  580. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  581. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  582. } else {
  583. XCTFail("file data should not be nil")
  584. }
  585. }
  586. func testWritingMultipleEncodedBodyPartsWithVaryingTypesToDisk() {
  587. // Given
  588. let fileURL = temporaryFileURL()
  589. let multipartFormData = MultipartFormData()
  590. let loremData = "Lorem ipsum.".data(using: .utf8, allowLossyConversion: false)!
  591. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  592. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  593. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  594. let rainbowStream = InputStream(url: rainbowImageURL)!
  595. multipartFormData.append(loremData, withName: "lorem")
  596. multipartFormData.append(unicornImageURL, withName: "unicorn")
  597. multipartFormData.append(
  598. rainbowStream,
  599. withLength: rainbowDataLength,
  600. name: "rainbow",
  601. fileName: "rainbow.jpg",
  602. mimeType: "image/jpeg"
  603. )
  604. var encodingError: Error?
  605. // When
  606. do {
  607. try multipartFormData.writeEncodedData(to: fileURL)
  608. } catch {
  609. encodingError = error
  610. }
  611. // Then
  612. XCTAssertNil(encodingError, "encoding error should be nil")
  613. if let fileData = try? Data(contentsOf: fileURL) {
  614. let boundary = multipartFormData.boundary
  615. var expectedFileData = Data()
  616. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  617. expectedFileData.append((
  618. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)"
  619. ).data(using: .utf8, allowLossyConversion: false)!
  620. )
  621. expectedFileData.append(loremData)
  622. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  623. expectedFileData.append((
  624. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  625. "Content-Type: image/png\(crlf)\(crlf)"
  626. ).data(using: .utf8, allowLossyConversion: false)!
  627. )
  628. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  629. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  630. expectedFileData.append((
  631. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  632. "Content-Type: image/jpeg\(crlf)\(crlf)"
  633. ).data(using: .utf8, allowLossyConversion: false)!
  634. )
  635. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  636. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  637. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  638. } else {
  639. XCTFail("file data should not be nil")
  640. }
  641. }
  642. }
  643. // MARK: -
  644. class MultipartFormDataFailureTestCase: BaseTestCase {
  645. func testThatAppendingFileBodyPartWithInvalidLastPathComponentReturnsError() {
  646. // Given
  647. let fileURL = NSURL(string: "")! as URL
  648. let multipartFormData = MultipartFormData()
  649. multipartFormData.append(fileURL, withName: "empty_data")
  650. var encodingError: Error?
  651. // When
  652. do {
  653. _ = try multipartFormData.encode()
  654. } catch {
  655. encodingError = error
  656. }
  657. // Then
  658. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  659. if let error = encodingError as? AFError {
  660. XCTAssertTrue(error.isBodyPartFilenameInvalid)
  661. let expectedFailureReason = "The URL provided does not have a valid filename: \(fileURL)"
  662. XCTAssertEqual(error.localizedDescription, expectedFailureReason, "failure reason does not match expected value")
  663. } else {
  664. XCTFail("Error should be AFError.")
  665. }
  666. }
  667. func testThatAppendingFileBodyPartThatIsNotFileURLReturnsError() {
  668. // Given
  669. let fileURL = URL(string: "https://example.com/image.jpg")!
  670. let multipartFormData = MultipartFormData()
  671. multipartFormData.append(fileURL, withName: "empty_data")
  672. var encodingError: Error?
  673. // When
  674. do {
  675. _ = try multipartFormData.encode()
  676. } catch {
  677. encodingError = error
  678. }
  679. // Then
  680. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  681. if let error = encodingError as? AFError {
  682. XCTAssertTrue(error.isBodyPartURLInvalid)
  683. let expectedFailureReason = "The URL provided is not a file URL: \(fileURL)"
  684. XCTAssertEqual(error.localizedDescription, expectedFailureReason, "error failure reason does not match expected value")
  685. } else {
  686. XCTFail("Error should be AFError.")
  687. }
  688. }
  689. func testThatAppendingFileBodyPartThatIsNotReachableReturnsError() {
  690. // Given
  691. let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent("does_not_exist.jpg")
  692. let fileURL = URL(fileURLWithPath: filePath)
  693. let multipartFormData = MultipartFormData()
  694. multipartFormData.append(fileURL, withName: "empty_data")
  695. var encodingError: Error?
  696. // When
  697. do {
  698. _ = try multipartFormData.encode()
  699. } catch {
  700. encodingError = error
  701. }
  702. // Then
  703. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  704. if let error = encodingError as? AFError {
  705. XCTAssertTrue(error.isBodyPartFileNotReachableWithError)
  706. } else {
  707. XCTFail("Error should be AFError.")
  708. }
  709. }
  710. func testThatAppendingFileBodyPartThatIsDirectoryReturnsError() {
  711. // Given
  712. let directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
  713. let multipartFormData = MultipartFormData()
  714. multipartFormData.append(directoryURL, withName: "empty_data", fileName: "empty", mimeType: "application/octet")
  715. var encodingError: Error?
  716. // When
  717. do {
  718. _ = try multipartFormData.encode()
  719. } catch {
  720. encodingError = error
  721. }
  722. // Then
  723. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  724. if let error = encodingError as? AFError {
  725. XCTAssertTrue(error.isBodyPartFileIsDirectory)
  726. let expectedFailureReason = "The URL provided is a directory: \(directoryURL)"
  727. XCTAssertEqual(error.localizedDescription, expectedFailureReason, "error failure reason does not match expected value")
  728. } else {
  729. XCTFail("Error should be AFError.")
  730. }
  731. }
  732. func testThatWritingEncodedDataToExistingFileURLFails() {
  733. // Given
  734. let fileURL = temporaryFileURL()
  735. var writerError: Error?
  736. do {
  737. try "dummy data".write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
  738. } catch {
  739. writerError = error
  740. }
  741. let multipartFormData = MultipartFormData()
  742. let data = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  743. multipartFormData.append(data, withName: "data")
  744. var encodingError: Error?
  745. // When
  746. do {
  747. try multipartFormData.writeEncodedData(to: fileURL)
  748. } catch {
  749. encodingError = error
  750. }
  751. // Then
  752. XCTAssertNil(writerError, "writer error should be nil")
  753. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  754. if let encodingError = encodingError as? AFError {
  755. XCTAssertTrue(encodingError.isOutputStreamFileAlreadyExists)
  756. }
  757. }
  758. func testThatWritingEncodedDataToBadURLFails() {
  759. // Given
  760. let fileURL = URL(string: "/this/is/not/a/valid/url")!
  761. let multipartFormData = MultipartFormData()
  762. let data = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  763. multipartFormData.append(data, withName: "data")
  764. var encodingError: Error?
  765. // When
  766. do {
  767. try multipartFormData.writeEncodedData(to: fileURL)
  768. } catch {
  769. encodingError = error
  770. }
  771. // Then
  772. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  773. if let encodingError = encodingError as? AFError {
  774. XCTAssertTrue(encodingError.isOutputStreamURLInvalid)
  775. }
  776. }
  777. }