MultipartFormDataTests.swift 41 KB

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