MultipartFormDataTests.swift 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. //
  2. // MultipartFormDataTests.swift
  3. //
  4. // Copyright (c) 2014 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 = Data("français".utf8)
  110. let japaneseData = Data("日本語".utf8)
  111. let emojiData = Data("😃👍🏻🍻🎉".utf8)
  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 expectedString = (
  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-Type: text/plain\(crlf)" +
  132. "Content-Disposition: form-data; name=\"japanese\"\(crlf)\(crlf)" +
  133. "日本語" +
  134. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  135. "Content-Type: text/plain\(crlf)" +
  136. "Content-Disposition: form-data; name=\"emoji\"\(crlf)\(crlf)" +
  137. "😃👍🏻🍻🎉" +
  138. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  139. )
  140. let expectedData = Data(expectedString.utf8)
  141. // XCTAssertEqual(encodedData, expectedData, "encoded data should match expected data")
  142. XCTAssertEqual(encodedData.count, expectedData.count)
  143. }
  144. }
  145. func testEncodingFileBodyPart() {
  146. // Given
  147. let multipartFormData = MultipartFormData()
  148. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  149. multipartFormData.append(unicornImageURL, withName: "unicorn")
  150. var encodedData: Data?
  151. // When
  152. do {
  153. encodedData = try multipartFormData.encode()
  154. } catch {
  155. // No-op
  156. }
  157. // Then
  158. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  159. if let encodedData = encodedData {
  160. let boundary = multipartFormData.boundary
  161. var expectedData = Data()
  162. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  163. expectedData.append(Data((
  164. "Content-Type: image/png\(crlf)" +
  165. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  166. )
  167. )
  168. expectedData.append(try! Data(contentsOf: unicornImageURL))
  169. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  170. // XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  171. XCTAssertEqual(encodedData.count, expectedData.count)
  172. }
  173. }
  174. func testEncodingMultipleFileBodyParts() {
  175. // Given
  176. let multipartFormData = MultipartFormData()
  177. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  178. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  179. multipartFormData.append(unicornImageURL, withName: "unicorn")
  180. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  181. var encodedData: Data?
  182. // When
  183. do {
  184. encodedData = try multipartFormData.encode()
  185. } catch {
  186. // No-op
  187. }
  188. // Then
  189. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  190. if let encodedData = encodedData {
  191. let boundary = multipartFormData.boundary
  192. var expectedData = Data()
  193. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  194. expectedData.append(Data((
  195. "Content-Type: image/png\(crlf)" +
  196. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  197. )
  198. )
  199. expectedData.append(try! Data(contentsOf: unicornImageURL))
  200. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  201. expectedData.append(Data((
  202. "Content-Type: image/jpeg\(crlf)" +
  203. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  204. )
  205. )
  206. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  207. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  208. // XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  209. XCTAssertEqual(encodedData.count, expectedData.count)
  210. }
  211. }
  212. func testEncodingStreamBodyPart() {
  213. // Given
  214. let multipartFormData = MultipartFormData()
  215. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  216. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  217. let unicornStream = InputStream(url: unicornImageURL)!
  218. multipartFormData.append(
  219. unicornStream,
  220. withLength: unicornDataLength,
  221. name: "unicorn",
  222. fileName: "unicorn.png",
  223. mimeType: "image/png"
  224. )
  225. var encodedData: Data?
  226. // When
  227. do {
  228. encodedData = try multipartFormData.encode()
  229. } catch {
  230. // No-op
  231. }
  232. // Then
  233. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  234. if let encodedData = encodedData {
  235. let boundary = multipartFormData.boundary
  236. var expectedData = Data()
  237. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  238. expectedData.append(Data((
  239. "Content-Type: image/png\(crlf)" +
  240. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  241. )
  242. )
  243. expectedData.append(try! Data(contentsOf: unicornImageURL))
  244. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  245. // XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  246. XCTAssertEqual(encodedData.count, expectedData.count)
  247. }
  248. }
  249. func testEncodingMultipleStreamBodyParts() {
  250. // Given
  251. let multipartFormData = MultipartFormData()
  252. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  253. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  254. let unicornStream = InputStream(url: unicornImageURL)!
  255. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  256. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  257. let rainbowStream = InputStream(url: rainbowImageURL)!
  258. multipartFormData.append(
  259. unicornStream,
  260. withLength: unicornDataLength,
  261. name: "unicorn",
  262. fileName: "unicorn.png",
  263. mimeType: "image/png"
  264. )
  265. multipartFormData.append(
  266. rainbowStream,
  267. withLength: rainbowDataLength,
  268. name: "rainbow",
  269. fileName: "rainbow.jpg",
  270. mimeType: "image/jpeg"
  271. )
  272. var encodedData: Data?
  273. // When
  274. do {
  275. encodedData = try multipartFormData.encode()
  276. } catch {
  277. // No-op
  278. }
  279. // Then
  280. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  281. if let encodedData = encodedData {
  282. let boundary = multipartFormData.boundary
  283. var expectedData = Data()
  284. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  285. expectedData.append(Data((
  286. "Content-Type: image/png\(crlf)" +
  287. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  288. )
  289. )
  290. expectedData.append(try! Data(contentsOf: unicornImageURL))
  291. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  292. expectedData.append(Data((
  293. "Content-Type: image/jpeg\(crlf)" +
  294. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  295. )
  296. )
  297. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  298. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  299. // XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  300. XCTAssertEqual(encodedData.count, expectedData.count)
  301. }
  302. }
  303. func testEncodingMultipleBodyPartsWithVaryingTypes() {
  304. // Given
  305. let multipartFormData = MultipartFormData()
  306. let loremData = Data("Lorem ipsum.".utf8)
  307. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  308. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  309. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  310. let rainbowStream = InputStream(url: rainbowImageURL)!
  311. multipartFormData.append(loremData, withName: "lorem")
  312. multipartFormData.append(unicornImageURL, withName: "unicorn")
  313. multipartFormData.append(
  314. rainbowStream,
  315. withLength: rainbowDataLength,
  316. name: "rainbow",
  317. fileName: "rainbow.jpg",
  318. mimeType: "image/jpeg"
  319. )
  320. var encodedData: Data?
  321. // When
  322. do {
  323. encodedData = try multipartFormData.encode()
  324. } catch {
  325. // No-op
  326. }
  327. // Then
  328. XCTAssertNotNil(encodedData, "encoded data should not be nil")
  329. if let encodedData = encodedData {
  330. let boundary = multipartFormData.boundary
  331. var expectedData = Data()
  332. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  333. expectedData.append(Data(
  334. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  335. )
  336. )
  337. expectedData.append(loremData)
  338. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  339. expectedData.append(Data((
  340. "Content-Type: image/png\(crlf)" +
  341. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  342. )
  343. )
  344. expectedData.append(try! Data(contentsOf: unicornImageURL))
  345. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  346. expectedData.append(Data((
  347. "Content-Type: image/jpeg\(crlf)" +
  348. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  349. )
  350. )
  351. expectedData.append(try! Data(contentsOf: rainbowImageURL))
  352. expectedData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  353. // XCTAssertEqual(encodedData, expectedData, "data should match expected data")
  354. XCTAssertEqual(encodedData.count, expectedData.count)
  355. }
  356. }
  357. }
  358. // MARK: -
  359. class MultipartFormDataWriteEncodedDataToDiskTestCase: BaseTestCase {
  360. let crlf = EncodingCharacters.crlf
  361. func testWritingEncodedDataBodyPartToDisk() {
  362. // Given
  363. let fileURL = temporaryFileURL()
  364. let multipartFormData = MultipartFormData()
  365. let data = "Lorem ipsum dolor sit amet.".data(using: .utf8, allowLossyConversion: false)!
  366. multipartFormData.append(data, withName: "data")
  367. var encodingError: Error?
  368. // When
  369. do {
  370. try multipartFormData.writeEncodedData(to: fileURL)
  371. } catch {
  372. encodingError = error
  373. }
  374. // Then
  375. XCTAssertNil(encodingError, "encoding error should be nil")
  376. if let fileData = try? Data(contentsOf: fileURL) {
  377. let boundary = multipartFormData.boundary
  378. let expectedFileData = (
  379. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  380. "Content-Disposition: form-data; name=\"data\"\(crlf)\(crlf)" +
  381. "Lorem ipsum dolor sit amet." +
  382. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  383. ).data(using: .utf8, allowLossyConversion: false)!
  384. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  385. } else {
  386. XCTFail("file data should not be nil")
  387. }
  388. }
  389. func testWritingMultipleEncodedDataBodyPartsToDisk() {
  390. // Given
  391. let fileURL = temporaryFileURL()
  392. let multipartFormData = MultipartFormData()
  393. let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
  394. let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!
  395. let emojiData = "😃👍🏻🍻🎉".data(using: .utf8, allowLossyConversion: false)!
  396. multipartFormData.append(frenchData, withName: "french")
  397. multipartFormData.append(japaneseData, withName: "japanese")
  398. multipartFormData.append(emojiData, withName: "emoji")
  399. var encodingError: Error?
  400. // When
  401. do {
  402. try multipartFormData.writeEncodedData(to: fileURL)
  403. } catch {
  404. encodingError = error
  405. }
  406. // Then
  407. XCTAssertNil(encodingError, "encoding error should be nil")
  408. if let fileData = try? Data(contentsOf: fileURL) {
  409. let boundary = multipartFormData.boundary
  410. let expectedFileData = (
  411. BoundaryGenerator.boundary(forBoundaryType: .initial, boundaryKey: boundary) +
  412. "Content-Disposition: form-data; name=\"french\"\(crlf)\(crlf)" +
  413. "français" +
  414. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  415. "Content-Disposition: form-data; name=\"japanese\"\(crlf)\(crlf)" +
  416. "日本語" +
  417. BoundaryGenerator.boundary(forBoundaryType: .encapsulated, boundaryKey: boundary) +
  418. "Content-Disposition: form-data; name=\"emoji\"\(crlf)\(crlf)" +
  419. "😃👍🏻🍻🎉" +
  420. BoundaryGenerator.boundary(forBoundaryType: .final, boundaryKey: boundary)
  421. ).data(using: .utf8, allowLossyConversion: false)!
  422. XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  423. } else {
  424. XCTFail("file data should not be nil")
  425. }
  426. }
  427. func testWritingEncodedFileBodyPartToDisk() {
  428. // Given
  429. let fileURL = temporaryFileURL()
  430. let multipartFormData = MultipartFormData()
  431. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  432. multipartFormData.append(unicornImageURL, withName: "unicorn")
  433. var encodingError: Error?
  434. // When
  435. do {
  436. try multipartFormData.writeEncodedData(to: fileURL)
  437. } catch {
  438. encodingError = error
  439. }
  440. // Then
  441. XCTAssertNil(encodingError, "encoding error should be nil")
  442. if let fileData = try? Data(contentsOf: fileURL) {
  443. let boundary = multipartFormData.boundary
  444. var expectedFileData = Data()
  445. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  446. expectedFileData.append(Data((
  447. "Content-Type: image/png\(crlf)" +
  448. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  449. )
  450. )
  451. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  452. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  453. // XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  454. XCTAssertEqual(fileData.count, expectedFileData.count)
  455. } else {
  456. XCTFail("file data should not be nil")
  457. }
  458. }
  459. func testWritingMultipleEncodedFileBodyPartsToDisk() {
  460. // Given
  461. let fileURL = temporaryFileURL()
  462. let multipartFormData = MultipartFormData()
  463. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  464. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  465. multipartFormData.append(unicornImageURL, withName: "unicorn")
  466. multipartFormData.append(rainbowImageURL, withName: "rainbow")
  467. var encodingError: Error?
  468. // When
  469. do {
  470. try multipartFormData.writeEncodedData(to: fileURL)
  471. } catch {
  472. encodingError = error
  473. }
  474. // Then
  475. XCTAssertNil(encodingError, "encoding error should be nil")
  476. if let fileData = try? Data(contentsOf: fileURL) {
  477. let boundary = multipartFormData.boundary
  478. var expectedFileData = Data()
  479. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  480. expectedFileData.append(Data((
  481. "Content-Type: image/png\(crlf)" +
  482. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  483. )
  484. )
  485. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  486. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  487. expectedFileData.append(Data((
  488. "Content-Type: image/jpeg\(crlf)" +
  489. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  490. )
  491. )
  492. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  493. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  494. // XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  495. XCTAssertEqual(fileData.count, expectedFileData.count)
  496. } else {
  497. XCTFail("file data should not be nil")
  498. }
  499. }
  500. func testWritingEncodedStreamBodyPartToDisk() {
  501. // Given
  502. let fileURL = temporaryFileURL()
  503. let multipartFormData = MultipartFormData()
  504. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  505. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  506. let unicornStream = InputStream(url: unicornImageURL)!
  507. multipartFormData.append(
  508. unicornStream,
  509. withLength: unicornDataLength,
  510. name: "unicorn",
  511. fileName: "unicorn.png",
  512. mimeType: "image/png"
  513. )
  514. var encodingError: Error?
  515. // When
  516. do {
  517. try multipartFormData.writeEncodedData(to: fileURL)
  518. } catch {
  519. encodingError = error
  520. }
  521. // Then
  522. XCTAssertNil(encodingError, "encoding error should be nil")
  523. if let fileData = try? Data(contentsOf: fileURL) {
  524. let boundary = multipartFormData.boundary
  525. var expectedFileData = Data()
  526. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  527. expectedFileData.append(Data((
  528. "Content-Type: image/png\(crlf)" +
  529. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  530. )
  531. )
  532. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  533. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  534. // XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  535. XCTAssertEqual(fileData.count, expectedFileData.count)
  536. } else {
  537. XCTFail("file data should not be nil")
  538. }
  539. }
  540. func testWritingMultipleEncodedStreamBodyPartsToDisk() {
  541. // Given
  542. let fileURL = temporaryFileURL()
  543. let multipartFormData = MultipartFormData()
  544. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  545. let unicornDataLength = UInt64((try! Data(contentsOf: unicornImageURL)).count)
  546. let unicornStream = InputStream(url: unicornImageURL)!
  547. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  548. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  549. let rainbowStream = InputStream(url: rainbowImageURL)!
  550. multipartFormData.append(
  551. unicornStream,
  552. withLength: unicornDataLength,
  553. name: "unicorn",
  554. fileName: "unicorn.png",
  555. mimeType: "image/png"
  556. )
  557. multipartFormData.append(
  558. rainbowStream,
  559. withLength: rainbowDataLength,
  560. name: "rainbow",
  561. fileName: "rainbow.jpg",
  562. mimeType: "image/jpeg"
  563. )
  564. var encodingError: Error?
  565. // When
  566. do {
  567. try multipartFormData.writeEncodedData(to: fileURL)
  568. } catch {
  569. encodingError = error
  570. }
  571. // Then
  572. XCTAssertNil(encodingError, "encoding error should be nil")
  573. if let fileData = try? Data(contentsOf: fileURL) {
  574. let boundary = multipartFormData.boundary
  575. var expectedFileData = Data()
  576. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  577. expectedFileData.append(Data((
  578. "Content-Type: image/png\(crlf)" +
  579. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  580. )
  581. )
  582. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  583. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  584. expectedFileData.append(Data((
  585. "Content-Type: image/jpeg\(crlf)" +
  586. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  587. )
  588. )
  589. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  590. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  591. // XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  592. XCTAssertEqual(fileData.count, expectedFileData.count)
  593. } else {
  594. XCTFail("file data should not be nil")
  595. }
  596. }
  597. func testWritingMultipleEncodedBodyPartsWithVaryingTypesToDisk() {
  598. // Given
  599. let fileURL = temporaryFileURL()
  600. let multipartFormData = MultipartFormData()
  601. let loremData = Data("Lorem ipsum.".utf8)
  602. let unicornImageURL = url(forResource: "unicorn", withExtension: "png")
  603. let rainbowImageURL = url(forResource: "rainbow", withExtension: "jpg")
  604. let rainbowDataLength = UInt64((try! Data(contentsOf: rainbowImageURL)).count)
  605. let rainbowStream = InputStream(url: rainbowImageURL)!
  606. multipartFormData.append(loremData, withName: "lorem")
  607. multipartFormData.append(unicornImageURL, withName: "unicorn")
  608. multipartFormData.append(
  609. rainbowStream,
  610. withLength: rainbowDataLength,
  611. name: "rainbow",
  612. fileName: "rainbow.jpg",
  613. mimeType: "image/jpeg"
  614. )
  615. var encodingError: Error?
  616. // When
  617. do {
  618. try multipartFormData.writeEncodedData(to: fileURL)
  619. } catch {
  620. encodingError = error
  621. }
  622. // Then
  623. XCTAssertNil(encodingError, "encoding error should be nil")
  624. if let fileData = try? Data(contentsOf: fileURL) {
  625. let boundary = multipartFormData.boundary
  626. var expectedFileData = Data()
  627. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .initial, boundaryKey: boundary))
  628. expectedFileData.append(Data(
  629. "Content-Disposition: form-data; name=\"lorem\"\(crlf)\(crlf)".utf8
  630. )
  631. )
  632. expectedFileData.append(loremData)
  633. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  634. expectedFileData.append(Data((
  635. "Content-Type: image/png\(crlf)" +
  636. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(crlf)\(crlf)").utf8
  637. )
  638. )
  639. expectedFileData.append(try! Data(contentsOf: unicornImageURL))
  640. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .encapsulated, boundaryKey: boundary))
  641. expectedFileData.append(Data((
  642. "Content-Type: image/jpeg\(crlf)" +
  643. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(crlf)\(crlf)").utf8
  644. )
  645. )
  646. expectedFileData.append(try! Data(contentsOf: rainbowImageURL))
  647. expectedFileData.append(BoundaryGenerator.boundaryData(boundaryType: .final, boundaryKey: boundary))
  648. // XCTAssertEqual(fileData, expectedFileData, "file data should match expected file data")
  649. XCTAssertEqual(fileData.count, expectedFileData.count)
  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: .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: .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. }