MultipartFormDataTests.swift 40 KB

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