MultipartFormDataTests.swift 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. //
  2. // MultipartFormDataTests.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Alamofire
  25. import Foundation
  26. import XCTest
  27. 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 Data(BoundaryGenerator.boundary(forBoundaryType: boundaryType,
  48. boundaryKey: boundaryKey).utf8)
  49. }
  50. }
  51. private func temporaryFileURL() -> URL { return BaseTestCase.testDirectoryURL.appendingPathComponent(UUID().uuidString) }
  52. // MARK: -
  53. class MultipartFormDataPropertiesTestCase: BaseTestCase {
  54. func testThatContentTypeContainsBoundary() {
  55. // Given
  56. let multipartFormData = MultipartFormData()
  57. // When
  58. let boundary = multipartFormData.boundary
  59. // Then
  60. let expectedContentType = "multipart/form-data; boundary=\(boundary)"
  61. XCTAssertEqual(multipartFormData.contentType, expectedContentType, "contentType should match expected value")
  62. }
  63. func testThatContentLengthMatchesTotalBodyPartSize() {
  64. // Given
  65. let multipartFormData = MultipartFormData()
  66. let data1 = Data("Lorem ipsum dolor sit amet.".utf8)
  67. let data2 = Data("Vim at integre alterum.".utf8)
  68. // When
  69. multipartFormData.append(data1, withName: "data1")
  70. multipartFormData.append(data2, withName: "data2")
  71. // Then
  72. let expectedContentLength = UInt64(data1.count + data2.count)
  73. XCTAssertEqual(multipartFormData.contentLength, expectedContentLength, "content length should match expected value")
  74. }
  75. }
  76. // MARK: -
  77. class MultipartFormDataEncodingTestCase: BaseTestCase {
  78. let crlf = EncodingCharacters.crlf
  79. func testEncodingDataBodyPart() {
  80. // Given
  81. let multipartFormData = MultipartFormData()
  82. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  83. multipartFormData.append(data, withName: "data")
  84. var encodedData: Data?
  85. // When
  86. do {
  87. encodedData = try multipartFormData.encode()
  88. } catch {
  89. // No-op
  90. }
  91. // Then
  92. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  93. if let encodedData = encodedData {
  94. let boundary = multipartFormData.boundary
  95. let expectedString = (
  96. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  97. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  98. "Lorem ipsum dolor sit amet." +
  99. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  100. )
  101. let expectedData = Data(expectedString.utf8)
  102. XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  103. }
  104. }
  105. func testEncodingMultipleDataBodyParts() {
  106. // Given
  107. let multipartFormData = MultipartFormData()
  108. let frenchData = Data("français".utf8)
  109. let japaneseData = Data("日本語".utf8)
  110. let emojiData = Data("😃👍🏻🍻🎉".utf8)
  111. multipartFormData.append(frenchData, withName: "french")
  112. multipartFormData.append(japaneseData, withName: "japanese", mimeType: "text/plain")
  113. multipartFormData.append(emojiData, withName: "emoji", mimeType: "text/plain")
  114. var encodedData: Data?
  115. // When
  116. do {
  117. encodedData = try multipartFormData.encode()
  118. } catch {
  119. // No-op
  120. }
  121. // Then
  122. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  123. if let encodedData = encodedData {
  124. let boundary = multipartFormData.boundary
  125. let expectedString = (
  126. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  127. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  128. "français" +
  129. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  130. "Content-Disposition: form-data; name=\"japanese\"\(crlf)" +
  131. "Content-Type: text/plain\(crlf)\(crlf)" +
  132. "日本語" +
  133. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  134. "Content-Disposition: form-data; name=\"emoji\"\(crlf)" +
  135. "Content-Type: text/plain\(crlf)\(crlf)" +
  136. "😃👍🏻🍻🎉" +
  137. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  138. )
  139. let expectedData = Data(expectedString.utf8)
  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(Data((
  162. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  163. "Content-Type: image/png\(crlf)\(crlf)").utf8
  164. )
  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(Data((
  192. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  193. "Content-Type: image/png\(crlf)\(crlf)").utf8
  194. )
  195. )
  196. expectedData.append(try! Data(contentsOf: unicornImageURL))
  197. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  198. expectedData.append(Data((
  199. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  200. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  201. )
  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(unicornStream,
  215. withLength: unicornDataLength,
  216. name: "unicorn",
  217. fileName: "unicorn.png",
  218. mimeType: "image/png")
  219. var encodedData: Data?
  220. // When
  221. do {
  222. encodedData = try multipartFormData.encode()
  223. } catch {
  224. // No-op
  225. }
  226. // Then
  227. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  228. if let encodedData = encodedData {
  229. let boundary = multipartFormData.boundary
  230. var expectedData = Data()
  231. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  232. expectedData.append(Data((
  233. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  234. "Content-Type: image/png\(crlf)\(crlf)").utf8
  235. )
  236. )
  237. expectedData.append(try! Data(contentsOf: unicornImageURL))
  238. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  239. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  240. }
  241. }
  242. func testEncodingMultipleStreamBodyParts() {
  243. // Given
  244. let multipartFormData = MultipartFormData()
  245. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  246. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  247. let unicornStream = InputStream(url: unicornImageURL)!
  248. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  249. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  250. let rainbowStream = InputStream(url: rainbowImageURL)!
  251. multipartFormData.append(unicornStream,
  252. withLength: unicornDataLength,
  253. name: "unicorn",
  254. fileName: "unicorn.png",
  255. mimeType: "image/png")
  256. multipartFormData.append(rainbowStream,
  257. withLength: rainbowDataLength,
  258. name: "rainbow",
  259. fileName: "rainbow.jpg",
  260. mimeType: "image/jpeg")
  261. var encodedData: Data?
  262. // When
  263. do {
  264. encodedData = try multipartFormData.encode()
  265. } catch {
  266. // No-op
  267. }
  268. // Then
  269. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  270. if let encodedData = encodedData {
  271. let boundary = multipartFormData.boundary
  272. var expectedData = Data()
  273. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  274. expectedData.append(Data((
  275. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  276. "Content-Type: image/png\(crlf)\(crlf)").utf8
  277. )
  278. )
  279. expectedData.append(try! Data(contentsOf: unicornImageURL))
  280. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  281. expectedData.append(Data((
  282. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  283. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  284. )
  285. )
  286. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  287. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  288. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  289. }
  290. }
  291. func testEncodingMultipleBodyPartsWithVaryingTypes() {
  292. // Given
  293. let multipartFormData = MultipartFormData()
  294. let loremData = Data("Lorem ipsum.".utf8)
  295. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  296. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  297. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  298. let rainbowStream = InputStream(url: rainbowImageURL)!
  299. multipartFormData.append(loremData, withName: "lorem")
  300. multipartFormData.append(unicornImageURL, withName: "unicorn")
  301. multipartFormData.append(rainbowStream,
  302. withLength: rainbowDataLength,
  303. name: "rainbow",
  304. fileName: "rainbow.jpg",
  305. mimeType: "image/jpeg")
  306. var encodedData: Data?
  307. // When
  308. do {
  309. encodedData = try multipartFormData.encode()
  310. } catch {
  311. // No-op
  312. }
  313. // Then
  314. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  315. if let encodedData = encodedData {
  316. let boundary = multipartFormData.boundary
  317. var expectedData = Data()
  318. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  319. expectedData.append(Data(
  320. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  321. )
  322. )
  323. expectedData.append(loremData)
  324. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  325. expectedData.append(Data((
  326. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  327. "Content-Type: image/png\(crlf)\(crlf)").utf8
  328. )
  329. )
  330. expectedData.append(try! Data(contentsOf: unicornImageURL))
  331. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  332. expectedData.append(Data((
  333. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  334. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  335. )
  336. )
  337. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  338. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  339. XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  340. }
  341. }
  342. }
  343. // MARK: -
  344. class MultipartFormDataWriteEncodedDataToDiskTestCase: BaseTestCase {
  345. let crlf = EncodingCharacters.crlf
  346. func testWritingEncodedDataBodyPartToDisk() {
  347. // Given
  348. let fileURL = temporaryFileURL()
  349. let multipartFormData = MultipartFormData()
  350. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  351. multipartFormData.append(data, withName: "data")
  352. var encodingError: Error?
  353. // When
  354. do {
  355. try multipartFormData.writeEncodedData(to: fileURL)
  356. } catch {
  357. encodingError = error
  358. }
  359. // Then
  360. XCTAssertNil(encodingError, "encoding error should be nil")
  361. if let fileData = try? Data(contentsOf: fileURL) {
  362. let boundary = multipartFormData.boundary
  363. let expectedString = BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  364. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  365. "Lorem ipsum dolor sit amet." +
  366. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  367. let expectedFileData = Data(expectedString.utf8)
  368. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  369. } else {
  370. XCTFail("file data should not be nil")
  371. }
  372. }
  373. func testWritingMultipleEncodedDataBodyPartsToDisk() {
  374. // Given
  375. let fileURL = temporaryFileURL()
  376. let multipartFormData = MultipartFormData()
  377. let frenchData = Data("français".utf8)
  378. let japaneseData = Data("日本語".utf8)
  379. let emojiData = Data("😃👍🏻🍻🎉".utf8)
  380. multipartFormData.append(frenchData, withName: "french")
  381. multipartFormData.append(japaneseData, withName: "japanese")
  382. multipartFormData.append(emojiData, withName: "emoji")
  383. var encodingError: Error?
  384. // When
  385. do {
  386. try multipartFormData.writeEncodedData(to: fileURL)
  387. } catch {
  388. encodingError = error
  389. }
  390. // Then
  391. XCTAssertNil(encodingError, "encoding error should be nil")
  392. if let fileData = try? Data(contentsOf: fileURL) {
  393. let boundary = multipartFormData.boundary
  394. let expectedString = (
  395. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  396. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  397. "français" +
  398. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  399. "Content-Disposition: form-data; name=\"japanese\"\(crlf)\(crlf)" +
  400. "日本語" +
  401. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  402. "Content-Disposition: form-data; name=\"emoji\"\(crlf)\(crlf)" +
  403. "😃👍🏻🍻🎉" +
  404. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  405. )
  406. let expectedFileData = Data(expectedString.utf8)
  407. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  408. } else {
  409. XCTFail("file data should not be nil")
  410. }
  411. }
  412. func testWritingEncodedFileBodyPartToDisk() {
  413. // Given
  414. let fileURL = temporaryFileURL()
  415. let multipartFormData = MultipartFormData()
  416. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  417. multipartFormData.append(unicornImageURL, withName: "unicorn")
  418. var encodingError: Error?
  419. // When
  420. do {
  421. try multipartFormData.writeEncodedData(to: fileURL)
  422. } catch {
  423. encodingError = error
  424. }
  425. // Then
  426. XCTAssertNil(encodingError, "encoding error should be nil")
  427. if let fileData = try? Data(contentsOf: fileURL) {
  428. let boundary = multipartFormData.boundary
  429. var expectedFileData = Data()
  430. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  431. expectedFileData.append(Data((
  432. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  433. "Content-Type: image/png\(crlf)\(crlf)").utf8
  434. )
  435. )
  436. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  437. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  438. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  439. } else {
  440. XCTFail("file data should not be nil")
  441. }
  442. }
  443. func testWritingMultipleEncodedFileBodyPartsToDisk() {
  444. // Given
  445. let fileURL = temporaryFileURL()
  446. let multipartFormData = MultipartFormData()
  447. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  448. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  449. multipartFormData.append(unicornImageURL, withName: "unicorn")
  450. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  451. var encodingError: Error?
  452. // When
  453. do {
  454. try multipartFormData.writeEncodedData(to: fileURL)
  455. } catch {
  456. encodingError = error
  457. }
  458. // Then
  459. XCTAssertNil(encodingError, "encoding error should be nil")
  460. if let fileData = try? Data(contentsOf: fileURL) {
  461. let boundary = multipartFormData.boundary
  462. var expectedFileData = Data()
  463. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  464. expectedFileData.append(Data((
  465. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  466. "Content-Type: image/png\(crlf)\(crlf)").utf8
  467. )
  468. )
  469. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  470. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  471. expectedFileData.append(Data((
  472. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  473. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  474. )
  475. )
  476. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  477. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  478. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  479. } else {
  480. XCTFail("file data should not be nil")
  481. }
  482. }
  483. func testWritingEncodedStreamBodyPartToDisk() {
  484. // Given
  485. let fileURL = temporaryFileURL()
  486. let multipartFormData = MultipartFormData()
  487. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  488. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  489. let unicornStream = InputStream(url: unicornImageURL)!
  490. multipartFormData.append(unicornStream,
  491. withLength: unicornDataLength,
  492. name: "unicorn",
  493. fileName: "unicorn.png",
  494. mimeType: "image/png")
  495. var encodingError: Error?
  496. // When
  497. do {
  498. try multipartFormData.writeEncodedData(to: fileURL)
  499. } catch {
  500. encodingError = error
  501. }
  502. // Then
  503. XCTAssertNil(encodingError, "encoding error should be nil")
  504. if let fileData = try? Data(contentsOf: fileURL) {
  505. let boundary = multipartFormData.boundary
  506. var expectedFileData = Data()
  507. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  508. expectedFileData.append(Data((
  509. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  510. "Content-Type: image/png\(crlf)\(crlf)").utf8
  511. )
  512. )
  513. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  514. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  515. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  516. } else {
  517. XCTFail("file data should not be nil")
  518. }
  519. }
  520. func testWritingMultipleEncodedStreamBodyPartsToDisk() {
  521. // Given
  522. let fileURL = temporaryFileURL()
  523. let multipartFormData = MultipartFormData()
  524. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  525. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  526. let unicornStream = InputStream(url: unicornImageURL)!
  527. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  528. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  529. let rainbowStream = InputStream(url: rainbowImageURL)!
  530. multipartFormData.append(unicornStream,
  531. withLength: unicornDataLength,
  532. name: "unicorn",
  533. fileName: "unicorn.png",
  534. mimeType: "image/png")
  535. multipartFormData.append(rainbowStream,
  536. withLength: rainbowDataLength,
  537. name: "rainbow",
  538. fileName: "rainbow.jpg",
  539. mimeType: "image/jpeg")
  540. var encodingError: Error?
  541. // When
  542. do {
  543. try multipartFormData.writeEncodedData(to: fileURL)
  544. } catch {
  545. encodingError = error
  546. }
  547. // Then
  548. XCTAssertNil(encodingError, "encoding error should be nil")
  549. if let fileData = try? Data(contentsOf: fileURL) {
  550. let boundary = multipartFormData.boundary
  551. var expectedFileData = Data()
  552. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  553. expectedFileData.append(Data((
  554. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  555. "Content-Type: image/png\(crlf)\(crlf)").utf8
  556. )
  557. )
  558. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  559. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  560. expectedFileData.append(Data((
  561. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  562. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  563. )
  564. )
  565. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  566. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  567. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  568. } else {
  569. XCTFail("file data should not be nil")
  570. }
  571. }
  572. func testWritingMultipleEncodedBodyPartsWithVaryingTypesToDisk() {
  573. // Given
  574. let fileURL = temporaryFileURL()
  575. let multipartFormData = MultipartFormData()
  576. let loremData = Data("Lorem ipsum.".utf8)
  577. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  578. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  579. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  580. let rainbowStream = InputStream(url: rainbowImageURL)!
  581. multipartFormData.append(loremData, withName: "lorem")
  582. multipartFormData.append(unicornImageURL, withName: "unicorn")
  583. multipartFormData.append(rainbowStream,
  584. withLength: rainbowDataLength,
  585. name: "rainbow",
  586. fileName: "rainbow.jpg",
  587. mimeType: "image/jpeg")
  588. var encodingError: Error?
  589. // When
  590. do {
  591. try multipartFormData.writeEncodedData(to: fileURL)
  592. } catch {
  593. encodingError = error
  594. }
  595. // Then
  596. XCTAssertNil(encodingError, "encoding error should be nil")
  597. if let fileData = try? Data(contentsOf: fileURL) {
  598. let boundary = multipartFormData.boundary
  599. var expectedFileData = Data()
  600. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  601. expectedFileData.append(Data(
  602. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  603. )
  604. )
  605. expectedFileData.append(loremData)
  606. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  607. expectedFileData.append(Data((
  608. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)" +
  609. "Content-Type: image/png\(crlf)\(crlf)").utf8
  610. )
  611. )
  612. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  613. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  614. expectedFileData.append(Data((
  615. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)" +
  616. "Content-Type: image/jpeg\(crlf)\(crlf)").utf8
  617. )
  618. )
  619. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  620. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  621. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  622. } else {
  623. XCTFail("file data should not be nil")
  624. }
  625. }
  626. }
  627. // MARK: -
  628. class MultipartFormDataFailureTestCase: BaseTestCase {
  629. func testThatAppendingFileBodyPartWithInvalidLastPathComponentReturnsError() {
  630. // Given
  631. let fileURL = NSURL(string: "")! as URL
  632. let multipartFormData = MultipartFormData()
  633. multipartFormData.append(fileURL, withName: "empty_data")
  634. var encodingError: Error?
  635. // When
  636. do {
  637. _ = try multipartFormData.encode()
  638. } catch {
  639. encodingError = error
  640. }
  641. // Then
  642. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  643. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFilenameInvalid, true)
  644. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  645. }
  646. func testThatAppendingFileBodyPartThatIsNotFileURLReturnsError() {
  647. // Given
  648. let fileURL = URL(string: "https://example.com/image.jpg")!
  649. let multipartFormData = MultipartFormData()
  650. multipartFormData.append(fileURL, withName: "empty_data")
  651. var encodingError: Error?
  652. // When
  653. do {
  654. _ = try multipartFormData.encode()
  655. } catch {
  656. encodingError = error
  657. }
  658. // Then
  659. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  660. XCTAssertEqual(encodingError?.asAFError?.isBodyPartURLInvalid, true)
  661. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  662. }
  663. func testThatAppendingFileBodyPartThatIsNotReachableReturnsError() {
  664. // Given
  665. let filePath = (NSTemporaryDirectory() as NSString).appendingPathComponent("does_not_exist.jpg")
  666. let fileURL = URL(fileURLWithPath: filePath)
  667. let multipartFormData = MultipartFormData()
  668. multipartFormData.append(fileURL, withName: "empty_data")
  669. var encodingError: Error?
  670. // When
  671. do {
  672. _ = try multipartFormData.encode()
  673. } catch {
  674. encodingError = error
  675. }
  676. // Then
  677. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  678. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFileNotReachableWithError, true)
  679. XCTAssertEqual(encodingError?.asAFError?.url, fileURL)
  680. }
  681. func testThatAppendingFileBodyPartThatIsDirectoryReturnsError() {
  682. // Given
  683. let directoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
  684. let multipartFormData = MultipartFormData()
  685. multipartFormData.append(directoryURL, withName: "empty_data", fileName: "empty", mimeType: "application/octet")
  686. var encodingError: Error?
  687. // When
  688. do {
  689. _ = try multipartFormData.encode()
  690. } catch {
  691. encodingError = error
  692. }
  693. // Then
  694. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  695. XCTAssertEqual(encodingError?.asAFError?.isBodyPartFileIsDirectory, true)
  696. XCTAssertEqual(encodingError?.asAFError?.url, directoryURL)
  697. }
  698. func testThatWritingEncodedDataToExistingFileURLFails() {
  699. // Given
  700. let fileURL = temporaryFileURL()
  701. var writerError: Error?
  702. do {
  703. try "dummy data".write(to: fileURL, atomically: true, encoding: .utf8)
  704. } catch {
  705. writerError = error
  706. }
  707. let multipartFormData = MultipartFormData()
  708. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  709. multipartFormData.append(data, withName: "data")
  710. var encodingError: Error?
  711. // When
  712. do {
  713. try multipartFormData.writeEncodedData(to: fileURL)
  714. } catch {
  715. encodingError = error
  716. }
  717. // Then
  718. XCTAssertNil(writerError, "writer error should be nil")
  719. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  720. XCTAssertEqual(encodingError?.asAFError?.isOutputStreamFileAlreadyExists, true)
  721. }
  722. func testThatWritingEncodedDataToBadURLFails() {
  723. // Given
  724. let fileURL = URL(string: "/this/is/not/a/valid/url")!
  725. let multipartFormData = MultipartFormData()
  726. let data = Data("Lorem ipsum dolor sit amet.".utf8)
  727. multipartFormData.append(data, withName: "data")
  728. var encodingError: Error?
  729. // When
  730. do {
  731. try multipartFormData.writeEncodedData(to: fileURL)
  732. } catch {
  733. encodingError = error
  734. }
  735. // Then
  736. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  737. XCTAssertEqual(encodingError?.asAFError?.isOutputStreamURLInvalid, true)
  738. }
  739. }