StorageIntegration.swift 20 KB

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