MultipartFormDataTests.swift 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  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(boundaryType 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: BoundaryType, boundaryKey: String) -> NSData {
  47. return BoundaryGenerator.boundary(
  48. boundaryType: boundaryType,
  49. boundaryKey: boundaryKey
  50. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  51. }
  52. }
  53. private func temporaryFileURL() -> NSURL {
  54. let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
  55. let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.test/multipart.form.data")
  56. let fileManager = NSFileManager.defaultManager()
  57. do {
  58. try fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil)
  59. } catch {
  60. // No-op - will cause tests to fail, not crash
  61. }
  62. let fileName = NSUUID().UUIDString
  63. let fileURL = directoryURL.URLByAppendingPathComponent(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.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  81. let data2 = "Vim at integre alterum.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  82. // When
  83. multipartFormData.appendBodyPart(data: data1, name: "data1")
  84. multipartFormData.appendBodyPart(data: data2, name: "data2")
  85. // Then
  86. let expectedContentLength = UInt64(data1.length + data2.length)
  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.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  97. multipartFormData.appendBodyPart(data: data, name: "data")
  98. var encodedData: NSData?
  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(boundaryType: .Initial, boundaryKey: boundary) +
  111. "Content-Disposition: form-data; name=\"data\"\(CRLF)\(CRLF)" +
  112. "Lorem ipsum dolor sit amet." +
  113. BoundaryGenerator.boundary(boundaryType: .Final, boundaryKey: boundary)
  114. ).dataUsingEncoding(NSUTF8StringEncoding, 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 french = "français".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  122. let japanese = "日本語".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  123. let emoji = "😃👍🏻🍻🎉".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  124. multipartFormData.appendBodyPart(data: french, name: "french")
  125. multipartFormData.appendBodyPart(data: japanese, name: "japanese", mimeType: "text/plain")
  126. multipartFormData.appendBodyPart(data: emoji, name: "emoji", mimeType: "text/plain")
  127. var encodedData: NSData?
  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(boundaryType: .Initial, boundaryKey: boundary) +
  140. "Content-Disposition: form-data; name=\"french\"\(CRLF)\(CRLF)" +
  141. "français" +
  142. BoundaryGenerator.boundary(boundaryType: .Encapsulated, boundaryKey: boundary) +
  143. "Content-Disposition: form-data; name=\"japanese\"\(CRLF)" +
  144. "Content-Type: text/plain\(CRLF)\(CRLF)" +
  145. "日本語" +
  146. BoundaryGenerator.boundary(boundaryType: .Encapsulated, boundaryKey: boundary) +
  147. "Content-Disposition: form-data; name=\"emoji\"\(CRLF)" +
  148. "Content-Type: text/plain\(CRLF)\(CRLF)" +
  149. "😃👍🏻🍻🎉" +
  150. BoundaryGenerator.boundary(boundaryType: .Final, boundaryKey: boundary)
  151. ).dataUsingEncoding(NSUTF8StringEncoding, 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 = URLForResource("unicorn", withExtension: "png")
  159. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  160. var encodedData: NSData?
  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. let expectedData = NSMutableData()
  172. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  173. expectedData.appendData((
  174. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  175. "Content-Type: image/png\(CRLF)\(CRLF)"
  176. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  177. )
  178. expectedData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  179. expectedData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  187. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  188. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  189. multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
  190. var encodedData: NSData?
  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. let expectedData = NSMutableData()
  202. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  203. expectedData.appendData((
  204. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  205. "Content-Type: image/png\(CRLF)\(CRLF)"
  206. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  207. )
  208. expectedData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  209. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  210. expectedData.appendData((
  211. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  212. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  213. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  214. )
  215. expectedData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  216. expectedData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  224. let unicornDataLength = UInt64(NSData(contentsOfURL: unicornImageURL)!.length)
  225. let unicornStream = NSInputStream(URL: unicornImageURL)!
  226. multipartFormData.appendBodyPart(
  227. stream: unicornStream,
  228. length: unicornDataLength,
  229. name: "unicorn",
  230. fileName: "unicorn.png",
  231. mimeType: "image/png"
  232. )
  233. var encodedData: NSData?
  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. let expectedData = NSMutableData()
  245. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  246. expectedData.appendData((
  247. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  248. "Content-Type: image/png\(CRLF)\(CRLF)"
  249. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  250. )
  251. expectedData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  252. expectedData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  260. let unicornDataLength = UInt64(NSData(contentsOfURL: unicornImageURL)!.length)
  261. let unicornStream = NSInputStream(URL: unicornImageURL)!
  262. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  263. let rainbowDataLength = UInt64(NSData(contentsOfURL: rainbowImageURL)!.length)
  264. let rainbowStream = NSInputStream(URL: rainbowImageURL)!
  265. multipartFormData.appendBodyPart(
  266. stream: unicornStream,
  267. length: unicornDataLength,
  268. name: "unicorn",
  269. fileName: "unicorn.png",
  270. mimeType: "image/png"
  271. )
  272. multipartFormData.appendBodyPart(
  273. stream: rainbowStream,
  274. length: rainbowDataLength,
  275. name: "rainbow",
  276. fileName: "rainbow.jpg",
  277. mimeType: "image/jpeg"
  278. )
  279. var encodedData: NSData?
  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. let expectedData = NSMutableData()
  291. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  292. expectedData.appendData((
  293. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  294. "Content-Type: image/png\(CRLF)\(CRLF)"
  295. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  296. )
  297. expectedData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  298. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  299. expectedData.appendData((
  300. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  301. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  302. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  303. )
  304. expectedData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  305. expectedData.appendData(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.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  313. let unicornImageURL = URLForResource("unicorn", withExtension: "png")
  314. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  315. let rainbowDataLength = UInt64(NSData(contentsOfURL: rainbowImageURL)!.length)
  316. let rainbowStream = NSInputStream(URL: rainbowImageURL)!
  317. multipartFormData.appendBodyPart(data: loremData, name: "lorem")
  318. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  319. multipartFormData.appendBodyPart(
  320. stream: rainbowStream,
  321. length: rainbowDataLength,
  322. name: "rainbow",
  323. fileName: "rainbow.jpg",
  324. mimeType: "image/jpeg"
  325. )
  326. var encodedData: NSData?
  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. let expectedData = NSMutableData()
  338. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  339. expectedData.appendData((
  340. "Content-Disposition: form-data; name=\"lorem\"\(CRLF)\(CRLF)"
  341. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  342. )
  343. expectedData.appendData(loremData)
  344. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  345. expectedData.appendData((
  346. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  347. "Content-Type: image/png\(CRLF)\(CRLF)"
  348. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  349. )
  350. expectedData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  351. expectedData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  352. expectedData.appendData((
  353. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  354. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  355. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  356. )
  357. expectedData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  358. expectedData.appendData(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.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  371. multipartFormData.appendBodyPart(data: data, name: "data")
  372. var encodingError: NSError?
  373. // When
  374. do {
  375. try multipartFormData.writeEncodedDataToDisk(fileURL)
  376. } catch {
  377. encodingError = error as NSError
  378. }
  379. // Then
  380. XCTAssertNil(encodingError, "encoding error should be nil")
  381. if let fileData = NSData(contentsOfURL: fileURL) {
  382. let boundary = multipartFormData.boundary
  383. let expectedFileData = (
  384. BoundaryGenerator.boundary(boundaryType: .Initial, boundaryKey: boundary) +
  385. "Content-Disposition: form-data; name=\"data\"\(CRLF)\(CRLF)" +
  386. "Lorem ipsum dolor sit amet." +
  387. BoundaryGenerator.boundary(boundaryType: .Final, boundaryKey: boundary)
  388. ).dataUsingEncoding(NSUTF8StringEncoding, 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 french = "français".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  399. let japanese = "日本語".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  400. let emoji = "😃👍🏻🍻🎉".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  401. multipartFormData.appendBodyPart(data: french, name: "french")
  402. multipartFormData.appendBodyPart(data: japanese, name: "japanese")
  403. multipartFormData.appendBodyPart(data: emoji, name: "emoji")
  404. var encodingError: NSError?
  405. // When
  406. do {
  407. try multipartFormData.writeEncodedDataToDisk(fileURL)
  408. } catch {
  409. encodingError = error as NSError
  410. }
  411. // Then
  412. XCTAssertNil(encodingError, "encoding error should be nil")
  413. if let fileData = NSData(contentsOfURL: fileURL) {
  414. let boundary = multipartFormData.boundary
  415. let expectedFileData = (
  416. BoundaryGenerator.boundary(boundaryType: .Initial, boundaryKey: boundary) +
  417. "Content-Disposition: form-data; name=\"french\"\(CRLF)\(CRLF)" +
  418. "français" +
  419. BoundaryGenerator.boundary(boundaryType: .Encapsulated, boundaryKey: boundary) +
  420. "Content-Disposition: form-data; name=\"japanese\"\(CRLF)\(CRLF)" +
  421. "日本語" +
  422. BoundaryGenerator.boundary(boundaryType: .Encapsulated, boundaryKey: boundary) +
  423. "Content-Disposition: form-data; name=\"emoji\"\(CRLF)\(CRLF)" +
  424. "😃👍🏻🍻🎉" +
  425. BoundaryGenerator.boundary(boundaryType: .Final, boundaryKey: boundary)
  426. ).dataUsingEncoding(NSUTF8StringEncoding, 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 = URLForResource("unicorn", withExtension: "png")
  437. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  438. var encodingError: NSError?
  439. // When
  440. do {
  441. try multipartFormData.writeEncodedDataToDisk(fileURL)
  442. } catch {
  443. encodingError = error as NSError
  444. }
  445. // Then
  446. XCTAssertNil(encodingError, "encoding error should be nil")
  447. if let fileData = NSData(contentsOfURL: fileURL) {
  448. let boundary = multipartFormData.boundary
  449. let expectedFileData = NSMutableData()
  450. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  451. expectedFileData.appendData((
  452. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  453. "Content-Type: image/png\(CRLF)\(CRLF)"
  454. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  455. )
  456. expectedFileData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  457. expectedFileData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  468. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  469. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  470. multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
  471. var encodingError: NSError?
  472. // When
  473. do {
  474. try multipartFormData.writeEncodedDataToDisk(fileURL)
  475. } catch {
  476. encodingError = error as NSError
  477. }
  478. // Then
  479. XCTAssertNil(encodingError, "encoding error should be nil")
  480. if let fileData = NSData(contentsOfURL: fileURL) {
  481. let boundary = multipartFormData.boundary
  482. let expectedFileData = NSMutableData()
  483. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  484. expectedFileData.appendData((
  485. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  486. "Content-Type: image/png\(CRLF)\(CRLF)"
  487. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  488. )
  489. expectedFileData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  490. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  491. expectedFileData.appendData((
  492. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  493. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  494. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  495. )
  496. expectedFileData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  497. expectedFileData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  508. let unicornDataLength = UInt64(NSData(contentsOfURL: unicornImageURL)!.length)
  509. let unicornStream = NSInputStream(URL: unicornImageURL)!
  510. multipartFormData.appendBodyPart(
  511. stream: unicornStream,
  512. length: unicornDataLength,
  513. name: "unicorn",
  514. fileName: "unicorn.png",
  515. mimeType: "image/png"
  516. )
  517. var encodingError: NSError?
  518. // When
  519. do {
  520. try multipartFormData.writeEncodedDataToDisk(fileURL)
  521. } catch {
  522. encodingError = error as NSError
  523. }
  524. // Then
  525. XCTAssertNil(encodingError, "encoding error should be nil")
  526. if let fileData = NSData(contentsOfURL: fileURL) {
  527. let boundary = multipartFormData.boundary
  528. let expectedFileData = NSMutableData()
  529. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  530. expectedFileData.appendData((
  531. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  532. "Content-Type: image/png\(CRLF)\(CRLF)"
  533. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  534. )
  535. expectedFileData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  536. expectedFileData.appendData(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 = URLForResource("unicorn", withExtension: "png")
  547. let unicornDataLength = UInt64(NSData(contentsOfURL: unicornImageURL)!.length)
  548. let unicornStream = NSInputStream(URL: unicornImageURL)!
  549. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  550. let rainbowDataLength = UInt64(NSData(contentsOfURL: rainbowImageURL)!.length)
  551. let rainbowStream = NSInputStream(URL: rainbowImageURL)!
  552. multipartFormData.appendBodyPart(
  553. stream: unicornStream,
  554. length: unicornDataLength,
  555. name: "unicorn",
  556. fileName: "unicorn.png",
  557. mimeType: "image/png"
  558. )
  559. multipartFormData.appendBodyPart(
  560. stream: rainbowStream,
  561. length: rainbowDataLength,
  562. name: "rainbow",
  563. fileName: "rainbow.jpg",
  564. mimeType: "image/jpeg"
  565. )
  566. var encodingError: NSError?
  567. // When
  568. do {
  569. try multipartFormData.writeEncodedDataToDisk(fileURL)
  570. } catch {
  571. encodingError = error as NSError
  572. }
  573. // Then
  574. XCTAssertNil(encodingError, "encoding error should be nil")
  575. if let fileData = NSData(contentsOfURL: fileURL) {
  576. let boundary = multipartFormData.boundary
  577. let expectedFileData = NSMutableData()
  578. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  579. expectedFileData.appendData((
  580. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  581. "Content-Type: image/png\(CRLF)\(CRLF)"
  582. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  583. )
  584. expectedFileData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  585. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  586. expectedFileData.appendData((
  587. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  588. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  589. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  590. )
  591. expectedFileData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  592. expectedFileData.appendData(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.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  603. let unicornImageURL = URLForResource("unicorn", withExtension: "png")
  604. let rainbowImageURL = URLForResource("rainbow", withExtension: "jpg")
  605. let rainbowDataLength = UInt64(NSData(contentsOfURL: rainbowImageURL)!.length)
  606. let rainbowStream = NSInputStream(URL: rainbowImageURL)!
  607. multipartFormData.appendBodyPart(data: loremData, name: "lorem")
  608. multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
  609. multipartFormData.appendBodyPart(
  610. stream: rainbowStream,
  611. length: rainbowDataLength,
  612. name: "rainbow",
  613. fileName: "rainbow.jpg",
  614. mimeType: "image/jpeg"
  615. )
  616. var encodingError: NSError?
  617. // When
  618. do {
  619. try multipartFormData.writeEncodedDataToDisk(fileURL)
  620. } catch {
  621. encodingError = error as NSError
  622. }
  623. // Then
  624. XCTAssertNil(encodingError, "encoding error should be nil")
  625. if let fileData = NSData(contentsOfURL: fileURL) {
  626. let boundary = multipartFormData.boundary
  627. let expectedFileData = NSMutableData()
  628. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Initial, boundaryKey: boundary))
  629. expectedFileData.appendData((
  630. "Content-Disposition: form-data; name=\"lorem\"\(CRLF)\(CRLF)"
  631. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  632. )
  633. expectedFileData.appendData(loremData)
  634. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  635. expectedFileData.appendData((
  636. "Content-Disposition: form-data; name=\"unicorn\"; filename=\"unicorn.png\"\(CRLF)" +
  637. "Content-Type: image/png\(CRLF)\(CRLF)"
  638. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  639. )
  640. expectedFileData.appendData(NSData(contentsOfURL: unicornImageURL)!)
  641. expectedFileData.appendData(BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundaryKey: boundary))
  642. expectedFileData.appendData((
  643. "Content-Disposition: form-data; name=\"rainbow\"; filename=\"rainbow.jpg\"\(CRLF)" +
  644. "Content-Type: image/jpeg\(CRLF)\(CRLF)"
  645. ).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  646. )
  647. expectedFileData.appendData(NSData(contentsOfURL: rainbowImageURL)!)
  648. expectedFileData.appendData(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: "")!
  660. let multipartFormData = MultipartFormData()
  661. multipartFormData.appendBodyPart(fileURL: fileURL, name: "empty_data")
  662. var encodingError: NSError?
  663. // When
  664. do {
  665. try multipartFormData.encode()
  666. } catch {
  667. encodingError = error as NSError
  668. }
  669. // Then
  670. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  671. if let error = encodingError {
  672. XCTAssertEqual(error.domain, NSURLErrorDomain, "error domain does not match expected value")
  673. XCTAssertEqual(error.code, NSURLErrorBadURL, "error code does not match expected value")
  674. if let failureReason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String {
  675. let expectedFailureReason = "Failed to extract the fileName of the provided URL: \(fileURL)"
  676. XCTAssertEqual(failureReason, expectedFailureReason, "failure reason does not match expected value")
  677. } else {
  678. XCTFail("failure reason should not be nil")
  679. }
  680. }
  681. }
  682. func testThatAppendingFileBodyPartThatIsNotFileURLReturnsError() {
  683. // Given
  684. let fileURL = NSURL(string: "https://example.com/image.jpg")!
  685. let multipartFormData = MultipartFormData()
  686. multipartFormData.appendBodyPart(fileURL: fileURL, name: "empty_data")
  687. var encodingError: NSError?
  688. // When
  689. do {
  690. try multipartFormData.encode()
  691. } catch {
  692. encodingError = error as NSError
  693. }
  694. // Then
  695. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  696. if let error = encodingError {
  697. XCTAssertEqual(error.domain, NSURLErrorDomain, "error domain does not match expected value")
  698. XCTAssertEqual(error.code, NSURLErrorBadURL, "error code does not match expected value")
  699. if let failureReason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String {
  700. let expectedFailureReason = "The file URL does not point to a file URL: \(fileURL)"
  701. XCTAssertEqual(failureReason, expectedFailureReason, "error failure reason does not match expected value")
  702. } else {
  703. XCTFail("failure reason should not be nil")
  704. }
  705. }
  706. }
  707. func testThatAppendingFileBodyPartThatIsNotReachableReturnsError() {
  708. // Given
  709. let filePath = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("does_not_exist.jpg")
  710. let fileURL = NSURL(fileURLWithPath: filePath)
  711. let multipartFormData = MultipartFormData()
  712. multipartFormData.appendBodyPart(fileURL: fileURL, name: "empty_data")
  713. var encodingError: NSError?
  714. // When
  715. do {
  716. try multipartFormData.encode()
  717. } catch {
  718. encodingError = error as NSError
  719. }
  720. // Then
  721. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  722. if let error = encodingError {
  723. XCTAssertEqual(error.domain, NSURLErrorDomain, "error domain does not match expected value")
  724. XCTAssertEqual(error.code, NSURLErrorBadURL, "error code does not match expected value")
  725. if let failureReason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String {
  726. let expectedFailureReason = "The file URL is not reachable: \(fileURL)"
  727. XCTAssertEqual(failureReason, expectedFailureReason, "error failure reason does not match expected value")
  728. } else {
  729. XCTFail("failure reason should not be nil")
  730. }
  731. }
  732. }
  733. func testThatAppendingFileBodyPartThatIsDirectoryReturnsError() {
  734. // Given
  735. let directoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
  736. let multipartFormData = MultipartFormData()
  737. multipartFormData.appendBodyPart(fileURL: directoryURL, name: "empty_data")
  738. var encodingError: NSError?
  739. // When
  740. do {
  741. try multipartFormData.encode()
  742. } catch {
  743. encodingError = error as NSError
  744. }
  745. // Then
  746. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  747. if let error = encodingError {
  748. XCTAssertEqual(error.domain, NSURLErrorDomain, "error domain does not match expected value")
  749. XCTAssertEqual(error.code, NSURLErrorBadURL, "error code does not match expected value")
  750. if let failureReason = error.userInfo[NSLocalizedFailureReasonErrorKey] as? String {
  751. let expectedFailureReason = "The file URL is a directory, not a file: \(directoryURL)"
  752. XCTAssertEqual(failureReason, expectedFailureReason, "error failure reason does not match expected value")
  753. } else {
  754. XCTFail("failure reason should not be nil")
  755. }
  756. }
  757. }
  758. func testThatWritingEncodedDataToExistingFileURLFails() {
  759. // Given
  760. let fileURL = temporaryFileURL()
  761. var writerError: NSError?
  762. do {
  763. try "dummy data".writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)
  764. } catch {
  765. writerError = error as NSError
  766. }
  767. let multipartFormData = MultipartFormData()
  768. let data = "Lorem ipsum dolor sit amet.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  769. multipartFormData.appendBodyPart(data: data, name: "data")
  770. var encodingError: NSError?
  771. // When
  772. do {
  773. try multipartFormData.writeEncodedDataToDisk(fileURL)
  774. } catch {
  775. encodingError = error as NSError
  776. }
  777. // Then
  778. XCTAssertNil(writerError, "writer error should be nil")
  779. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  780. if let encodingError = encodingError {
  781. XCTAssertEqual(encodingError.domain, NSURLErrorDomain, "encoding error domain does not match expected value")
  782. XCTAssertEqual(encodingError.code, NSURLErrorBadURL, "encoding error code does not match expected value")
  783. }
  784. }
  785. func testThatWritingEncodedDataToBadURLFails() {
  786. // Given
  787. let fileURL = NSURL(string: "/this/is/not/a/valid/url")!
  788. let multipartFormData = MultipartFormData()
  789. let data = "Lorem ipsum dolor sit amet.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  790. multipartFormData.appendBodyPart(data: data, name: "data")
  791. var encodingError: NSError?
  792. // When
  793. do {
  794. try multipartFormData.writeEncodedDataToDisk(fileURL)
  795. } catch {
  796. encodingError = error as NSError
  797. }
  798. // Then
  799. XCTAssertNotNil(encodingError, "encoding error should not be nil")
  800. if let encodingError = encodingError {
  801. XCTAssertEqual(encodingError.domain, NSURLErrorDomain, "encoding error domain does not match expected value")
  802. XCTAssertEqual(encodingError.code, NSURLErrorBadURL, "encoding error code does not match expected value")
  803. }
  804. }
  805. }