MultipartFormDataTests.swift 37 KB

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