StorageIntegration.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import FirebaseCore
  15. import FirebaseStorage
  16. import FirebaseStorageSwift
  17. import XCTest
  18. class StorageIntegration: XCTestCase {
  19. var app: FirebaseApp!
  20. var storage: Storage!
  21. static var once = false
  22. override class func setUp() {
  23. FirebaseApp.configure()
  24. }
  25. override func setUp() {
  26. super.setUp()
  27. app = FirebaseApp.app()
  28. storage = Storage.storage(app: app!)
  29. if !StorageIntegration.once {
  30. StorageIntegration.once = true
  31. let setupExpectation = expectation(description: "setUp")
  32. let largeFiles = ["ios/public/1mb"]
  33. let emptyFiles =
  34. ["ios/public/empty", "ios/public/list/a", "ios/public/list/b", "ios/public/list/prefix/c"]
  35. setupExpectation.expectedFulfillmentCount = largeFiles.count + emptyFiles.count
  36. do {
  37. let bundle = Bundle(for: StorageIntegration.self)
  38. let filePath = try XCTUnwrap(bundle.path(forResource: "1mb", ofType: "dat"),
  39. "Failed to get filePath")
  40. let data = try XCTUnwrap(try Data(contentsOf: URL(fileURLWithPath: filePath)),
  41. "Failed to load file")
  42. for largeFile in largeFiles {
  43. let ref = storage.reference().child(largeFile)
  44. ref.putData(data) { result in
  45. self.assertResultSuccess(result)
  46. setupExpectation.fulfill()
  47. }
  48. }
  49. for emptyFile in emptyFiles {
  50. let ref = storage.reference().child(emptyFile)
  51. ref.putData(data) { result in
  52. self.assertResultSuccess(result)
  53. setupExpectation.fulfill()
  54. }
  55. }
  56. waitForExpectations()
  57. } catch {
  58. XCTFail("Error thrown setting up files in setUp")
  59. }
  60. }
  61. }
  62. override func tearDown() {
  63. app = nil
  64. storage = nil
  65. super.tearDown()
  66. }
  67. func testUnauthenticatedGetMetadata() {
  68. let expectation = self.expectation(description: "testUnauthenticatedGetMetadata")
  69. let ref = storage.reference().child("ios/public/1mb")
  70. ref.getMetadata { result in
  71. self.assertResultSuccess(result)
  72. expectation.fulfill()
  73. }
  74. waitForExpectations()
  75. }
  76. func testUnauthenticatedUpdateMetadata() {
  77. let expectation = self.expectation(description: #function)
  78. let meta = StorageMetadata()
  79. meta.contentType = "lol/custom"
  80. meta.customMetadata = ["lol": "custom metadata is neat",
  81. "ちかてつ": "🚇",
  82. "shinkansen": "新幹線"]
  83. let ref = storage.reference(withPath: "ios/public/1mb")
  84. ref.updateMetadata(meta) { result in
  85. switch result {
  86. case let .success(metadata):
  87. XCTAssertEqual(meta.contentType, metadata.contentType)
  88. XCTAssertEqual(meta.customMetadata!["lol"], metadata.customMetadata!["lol"])
  89. XCTAssertEqual(meta.customMetadata!["ちかてつ"], metadata.customMetadata!["ちかてつ"])
  90. XCTAssertEqual(meta.customMetadata!["shinkansen"],
  91. metadata.customMetadata!["shinkansen"])
  92. case let .failure(error):
  93. XCTFail("Unexpected error \(error) from updateMetadata")
  94. }
  95. expectation.fulfill()
  96. }
  97. waitForExpectations()
  98. }
  99. func testUnauthenticatedDelete() throws {
  100. let expectation = self.expectation(description: #function)
  101. let ref = storage.reference(withPath: "ios/public/fileToDelete")
  102. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  103. ref.putData(data) { result in
  104. self.assertResultSuccess(result)
  105. ref.delete { error in
  106. XCTAssertNil(error, "Error should be nil")
  107. }
  108. expectation.fulfill()
  109. }
  110. waitForExpectations()
  111. }
  112. func testDeleteWithNilCompletion() throws {
  113. let expectation = self.expectation(description: #function)
  114. let ref = storage.reference(withPath: "ios/public/fileToDelete")
  115. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  116. ref.putData(data) { result in
  117. self.assertResultSuccess(result)
  118. ref.delete(completion: nil)
  119. expectation.fulfill()
  120. }
  121. waitForExpectations()
  122. }
  123. func testUnauthenticatedSimplePutData() throws {
  124. let expectation = self.expectation(description: #function)
  125. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  126. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  127. ref.putData(data) { result in
  128. self.assertResultSuccess(result)
  129. expectation.fulfill()
  130. }
  131. waitForExpectations()
  132. }
  133. func testUnauthenticatedSimplePutSpecialCharacter() throws {
  134. let expectation = self.expectation(description: #function)
  135. let ref = storage.reference(withPath: "ios/public/-._~!$'()*,=:@&+;")
  136. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  137. ref.putData(data) { result in
  138. self.assertResultSuccess(result)
  139. expectation.fulfill()
  140. }
  141. waitForExpectations()
  142. }
  143. func testUnauthenticatedSimplePutDataInBackgroundQueue() throws {
  144. let expectation = self.expectation(description: #function)
  145. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  146. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  147. DispatchQueue.global(qos: .background).async {
  148. ref.putData(data) { result in
  149. self.assertResultSuccess(result)
  150. expectation.fulfill()
  151. }
  152. }
  153. waitForExpectations()
  154. }
  155. func testUnauthenticatedSimplePutEmptyData() {
  156. let expectation = self.expectation(description: #function)
  157. let ref = storage.reference(withPath: "ios/public/testUnauthenticatedSimplePutEmptyData")
  158. let data = Data()
  159. ref.putData(data) { result in
  160. self.assertResultSuccess(result)
  161. expectation.fulfill()
  162. }
  163. waitForExpectations()
  164. }
  165. func testUnauthenticatedSimplePutDataUnauthorized() throws {
  166. let expectation = self.expectation(description: #function)
  167. let ref = storage.reference(withPath: "ios/private/secretfile.txt")
  168. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  169. ref.putData(data) { result in
  170. switch result {
  171. case .success:
  172. XCTFail("Unexpected success from unauthorized putData")
  173. case let .failure(error as NSError):
  174. XCTAssertEqual(error.code, StorageErrorCode.unauthorized.rawValue)
  175. expectation.fulfill()
  176. }
  177. }
  178. waitForExpectations()
  179. }
  180. func testUnauthenticatedSimplePutDataUnauthorizedThrow() throws {
  181. let expectation = self.expectation(description: #function)
  182. let ref = storage.reference(withPath: "ios/private/secretfile.txt")
  183. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  184. ref.putData(data) { result in
  185. do {
  186. try _ = result.get() // .failure will throw
  187. } catch {
  188. expectation.fulfill()
  189. return
  190. }
  191. XCTFail("Unexpected success from unauthorized putData")
  192. expectation.fulfill()
  193. }
  194. waitForExpectations()
  195. }
  196. func testUnauthenticatedSimplePutFile() throws {
  197. let expectation = self.expectation(description: #function)
  198. let putFileExpectation = self.expectation(description: "putFile")
  199. let ref = storage.reference(withPath: "ios/public/testUnauthenticatedSimplePutFile")
  200. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  201. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  202. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  203. try data.write(to: fileURL, options: .atomicWrite)
  204. let task = ref.putFile(from: fileURL) { result in
  205. self.assertResultSuccess(result)
  206. putFileExpectation.fulfill()
  207. }
  208. task.observe(StorageTaskStatus.success) { snapshot in
  209. XCTAssertEqual(snapshot.description, "<State: Success>")
  210. expectation.fulfill()
  211. }
  212. var uploadedBytes: Int64 = -1
  213. task.observe(StorageTaskStatus.progress) { snapshot in
  214. XCTAssertTrue(snapshot.description.starts(with: "<State: Progress") ||
  215. snapshot.description.starts(with: "<State: Resume"))
  216. guard let progress = snapshot.progress else {
  217. XCTFail("Failed to get snapshot.progress")
  218. return
  219. }
  220. XCTAssertGreaterThanOrEqual(progress.completedUnitCount, uploadedBytes)
  221. uploadedBytes = progress.completedUnitCount
  222. }
  223. waitForExpectations()
  224. }
  225. func testPutFileWithSpecialCharacters() throws {
  226. let expectation = self.expectation(description: #function)
  227. let fileName = "hello&+@_ .txt"
  228. let ref = storage.reference(withPath: "ios/public/" + fileName)
  229. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  230. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  231. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  232. try data.write(to: fileURL, options: .atomicWrite)
  233. ref.putFile(from: fileURL) { result in
  234. switch result {
  235. case let .success(metadata):
  236. XCTAssertEqual(fileName, metadata.name)
  237. ref.getMetadata { result in
  238. self.assertResultSuccess(result)
  239. }
  240. case let .failure(error):
  241. XCTFail("Unexpected error \(error) from putFile")
  242. }
  243. expectation.fulfill()
  244. }
  245. waitForExpectations()
  246. }
  247. func testUnauthenticatedSimplePutDataNoMetadata() throws {
  248. let expectation = self.expectation(description: #function)
  249. let ref = storage.reference(withPath: "ios/public/testUnauthenticatedSimplePutDataNoMetadata")
  250. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  251. ref.putData(data) { result in
  252. self.assertResultSuccess(result)
  253. expectation.fulfill()
  254. }
  255. waitForExpectations()
  256. }
  257. func testUnauthenticatedSimplePutFileNoMetadata() throws {
  258. let expectation = self.expectation(description: #function)
  259. let fileName = "hello&+@_ .txt"
  260. let ref = storage.reference(withPath: "ios/public/" + fileName)
  261. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  262. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  263. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  264. try data.write(to: fileURL, options: .atomicWrite)
  265. ref.putFile(from: fileURL) { result in
  266. self.assertResultSuccess(result)
  267. expectation.fulfill()
  268. }
  269. waitForExpectations()
  270. }
  271. func testUnauthenticatedSimpleGetData() {
  272. let expectation = self.expectation(description: #function)
  273. let ref = storage.reference(withPath: "ios/public/1mb")
  274. ref.getData(maxSize: 1024 * 1024) { result in
  275. self.assertResultSuccess(result)
  276. expectation.fulfill()
  277. }
  278. waitForExpectations()
  279. }
  280. func testUnauthenticatedSimpleGetDataInBackgroundQueue() {
  281. let expectation = self.expectation(description: #function)
  282. let ref = storage.reference(withPath: "ios/public/1mb")
  283. DispatchQueue.global(qos: .background).async {
  284. ref.getData(maxSize: 1024 * 1024) { result in
  285. self.assertResultSuccess(result)
  286. expectation.fulfill()
  287. }
  288. }
  289. waitForExpectations()
  290. }
  291. func testUnauthenticatedSimpleGetDataTooSmall() {
  292. let expectation = self.expectation(description: #function)
  293. let ref = storage.reference(withPath: "ios/public/1mb")
  294. ref.getData(maxSize: 1024) { result in
  295. switch result {
  296. case .success:
  297. XCTFail("Unexpected success from getData too small")
  298. case let .failure(error as NSError):
  299. XCTAssertEqual(error.code, StorageErrorCode.downloadSizeExceeded.rawValue)
  300. }
  301. expectation.fulfill()
  302. }
  303. waitForExpectations()
  304. }
  305. func testUnauthenticatedSimpleGetDownloadURL() {
  306. let expectation = self.expectation(description: #function)
  307. let ref = storage.reference(withPath: "ios/public/1mb")
  308. // Download URL format is
  309. // "https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{path}?alt=media&token={token}"
  310. let downloadURLPattern =
  311. "^https:\\/\\/firebasestorage.googleapis.com\\/v0\\/b\\/[^\\/]*\\/o\\/" +
  312. "ios%2Fpublic%2F1mb\\?alt=media&token=[a-z0-9-]*$"
  313. ref.downloadURL { result in
  314. switch result {
  315. case let .success(downloadURL):
  316. do {
  317. let testRegex = try NSRegularExpression(pattern: downloadURLPattern)
  318. let urlString = downloadURL.absoluteString
  319. XCTAssertEqual(testRegex.numberOfMatches(in: urlString,
  320. range: NSRange(location: 0,
  321. length: urlString.count)), 1)
  322. } catch {
  323. XCTFail("Throw in downloadURL completion block")
  324. }
  325. case let .failure(error):
  326. XCTFail("Unexpected error \(error) from downloadURL")
  327. }
  328. expectation.fulfill()
  329. }
  330. waitForExpectations()
  331. }
  332. func testUnauthenticatedSimpleGetFile() throws {
  333. let expectation = self.expectation(description: #function)
  334. let ref = storage.reference(withPath: "ios/public/helloworld")
  335. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  336. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  337. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  338. ref.putData(data) { result in
  339. switch result {
  340. case .success:
  341. let task = ref.write(toFile: fileURL)
  342. task.observe(StorageTaskStatus.success) { snapshot in
  343. do {
  344. let stringData = try String(contentsOf: fileURL, encoding: .utf8)
  345. XCTAssertEqual(stringData, "Hello Swift World")
  346. XCTAssertEqual(snapshot.description, "<State: Success>")
  347. } catch {
  348. XCTFail("Error processing success snapshot")
  349. }
  350. expectation.fulfill()
  351. }
  352. task.observe(StorageTaskStatus.progress) { snapshot in
  353. XCTAssertNil(snapshot.error, "Error should be nil")
  354. guard let progress = snapshot.progress else {
  355. XCTFail("Missing progress")
  356. return
  357. }
  358. print("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
  359. }
  360. task.observe(StorageTaskStatus.failure) { snapshot in
  361. XCTAssertNil(snapshot.error, "Error should be nil")
  362. }
  363. case let .failure(error):
  364. XCTFail("Unexpected error \(error) from putData")
  365. expectation.fulfill()
  366. }
  367. }
  368. waitForExpectations()
  369. }
  370. private func assertMetadata(actualMetadata: StorageMetadata,
  371. expectedContentType: String,
  372. expectedCustomMetadata: [String: String]) {
  373. XCTAssertEqual(actualMetadata.cacheControl, "cache-control")
  374. XCTAssertEqual(actualMetadata.contentDisposition, "content-disposition")
  375. XCTAssertEqual(actualMetadata.contentEncoding, "gzip")
  376. XCTAssertEqual(actualMetadata.contentLanguage, "de")
  377. XCTAssertEqual(actualMetadata.contentType, expectedContentType)
  378. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  379. for (key, value) in expectedCustomMetadata {
  380. XCTAssertEqual(actualMetadata.customMetadata![key], value)
  381. }
  382. }
  383. private func assertMetadataNil(actualMetadata: StorageMetadata) {
  384. XCTAssertNil(actualMetadata.cacheControl)
  385. XCTAssertNil(actualMetadata.contentDisposition)
  386. XCTAssertEqual(actualMetadata.contentEncoding, "identity")
  387. XCTAssertNil(actualMetadata.contentLanguage)
  388. XCTAssertNil(actualMetadata.contentType)
  389. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  390. XCTAssertNil(actualMetadata.customMetadata)
  391. }
  392. func testUpdateMetadata() {
  393. let expectation = self.expectation(description: #function)
  394. let ref = storage.reference(withPath: "ios/public/1mb")
  395. let metadata = StorageMetadata()
  396. metadata.cacheControl = "cache-control"
  397. metadata.contentDisposition = "content-disposition"
  398. metadata.contentEncoding = "gzip"
  399. metadata.contentLanguage = "de"
  400. metadata.contentType = "content-type-a"
  401. metadata.customMetadata = ["a": "b"]
  402. ref.updateMetadata(metadata) { updatedMetadata, error in
  403. XCTAssertNil(error, "Error should be nil")
  404. guard let updatedMetadata = updatedMetadata else {
  405. XCTFail("Metadata is nil")
  406. expectation.fulfill()
  407. return
  408. }
  409. self.assertMetadata(actualMetadata: updatedMetadata,
  410. expectedContentType: "content-type-a",
  411. expectedCustomMetadata: ["a": "b"])
  412. let metadata = updatedMetadata
  413. metadata.contentType = "content-type-b"
  414. metadata.customMetadata = ["a": "b", "c": "d"]
  415. ref.updateMetadata(metadata) { result in
  416. switch result {
  417. case let .success(updatedMetadata):
  418. self.assertMetadata(actualMetadata: updatedMetadata,
  419. expectedContentType: "content-type-b",
  420. expectedCustomMetadata: ["a": "b", "c": "d"])
  421. metadata.cacheControl = nil
  422. metadata.contentDisposition = nil
  423. metadata.contentEncoding = nil
  424. metadata.contentLanguage = nil
  425. metadata.contentType = nil
  426. metadata.customMetadata = nil
  427. ref.updateMetadata(metadata) { result in
  428. self.assertResultSuccess(result)
  429. expectation.fulfill()
  430. }
  431. case let .failure(error):
  432. XCTFail("Unexpected error \(error) from updateMetadata")
  433. expectation.fulfill()
  434. }
  435. }
  436. }
  437. waitForExpectations()
  438. }
  439. func testPagedListFiles() {
  440. let expectation = self.expectation(description: #function)
  441. let ref = storage.reference(withPath: "ios/public/list")
  442. ref.list(withMaxResults: 2) { result in
  443. switch result {
  444. case let .success(listResult):
  445. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  446. XCTAssertEqual(listResult.prefixes, [])
  447. guard let pageToken = listResult.pageToken else {
  448. XCTFail("pageToken should not be nil")
  449. expectation.fulfill()
  450. return
  451. }
  452. ref.list(withMaxResults: 2, pageToken: pageToken) { result in
  453. switch result {
  454. case let .success(listResult):
  455. XCTAssertEqual(listResult.items, [])
  456. XCTAssertEqual(listResult.prefixes, [ref.child("prefix")])
  457. XCTAssertNil(listResult.pageToken, "pageToken should be nil")
  458. case let .failure(error):
  459. XCTFail("Unexpected error \(error) from list")
  460. }
  461. expectation.fulfill()
  462. }
  463. case let .failure(error):
  464. XCTFail("Unexpected error \(error) from list")
  465. expectation.fulfill()
  466. }
  467. }
  468. waitForExpectations()
  469. }
  470. func testListAllFiles() {
  471. let expectation = self.expectation(description: #function)
  472. let ref = storage.reference(withPath: "ios/public/list")
  473. ref.listAll { result in
  474. switch result {
  475. case let .success(listResult):
  476. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  477. XCTAssertEqual(listResult.prefixes, [ref.child("prefix")])
  478. XCTAssertNil(listResult.pageToken, "pageToken should be nil")
  479. case let .failure(error):
  480. XCTFail("Unexpected error \(error) from list")
  481. }
  482. expectation.fulfill()
  483. }
  484. waitForExpectations()
  485. }
  486. private func waitForExpectations() {
  487. let kFIRStorageIntegrationTestTimeout = 60.0
  488. waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout,
  489. handler: { (error) -> Void in
  490. if let error = error {
  491. print(error)
  492. }
  493. })
  494. }
  495. private func assertResultSuccess<T>(
  496. _ result: Result<T, Error>,
  497. file: StaticString = #file, line: UInt = #line
  498. ) {
  499. switch result {
  500. case let .success(value):
  501. XCTAssertNotNil(value, file: file, line: line)
  502. case let .failure(error):
  503. XCTFail("Unexpected error \(error)")
  504. }
  505. }
  506. }