StorageAsyncAwait.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. // Copyright 2021 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 XCTest
  18. @available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)
  19. class StorageAsyncAwait: StorageIntegrationCommon {
  20. func testGetMetadata() async throws {
  21. let ref = storage.reference().child("ios/public/1mb2")
  22. let result = try await ref.getMetadata()
  23. XCTAssertNotNil(result)
  24. }
  25. func testUpdateMetadata() async throws {
  26. let meta = StorageMetadata()
  27. meta.contentType = "lol/custom"
  28. meta.customMetadata = ["lol": "custom metadata is neat",
  29. "ちかてつ": "🚇",
  30. "shinkansen": "新幹線"]
  31. let ref = storage.reference(withPath: "ios/public/1mb2")
  32. let metadata = try await ref.updateMetadata(meta)
  33. XCTAssertEqual(meta.contentType, metadata.contentType)
  34. XCTAssertEqual(meta.customMetadata!["lol"], metadata.customMetadata!["lol"])
  35. XCTAssertEqual(meta.customMetadata!["ちかてつ"], metadata.customMetadata!["ちかてつ"])
  36. XCTAssertEqual(meta.customMetadata!["shinkansen"],
  37. metadata.customMetadata!["shinkansen"])
  38. }
  39. func testDelete() async throws {
  40. let objectLocation = "ios/public/fileToDelete"
  41. let ref = storage.reference(withPath: objectLocation)
  42. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  43. let result = try await ref.putDataAsync(data)
  44. XCTAssertNotNil(result)
  45. _ = try await ref.delete()
  46. // Next delete should fail and verify the first delete succeeded.
  47. var caughtError = false
  48. do {
  49. _ = try await ref.delete()
  50. } catch {
  51. caughtError = true
  52. let nsError = error as NSError
  53. XCTAssertEqual(nsError.code, StorageErrorCode.objectNotFound.rawValue)
  54. XCTAssertEqual(nsError.userInfo["ResponseErrorCode"] as? Int, 404)
  55. let underlyingError = try XCTUnwrap(nsError.userInfo[NSUnderlyingErrorKey] as? NSError)
  56. XCTAssertEqual(underlyingError.code, 404)
  57. XCTAssertEqual(underlyingError.domain, "com.google.HTTPStatus")
  58. }
  59. XCTAssertTrue(caughtError)
  60. }
  61. func testDeleteAfterPut() async throws {
  62. let ref = storage.reference(withPath: "ios/public/fileToDelete")
  63. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  64. let result = try await ref.putDataAsync(data)
  65. XCTAssertNotNil(result)
  66. let result2: Void = try await ref.delete()
  67. XCTAssertNotNil(result2)
  68. }
  69. func testSimplePutData() async throws {
  70. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  71. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  72. let result = try await ref.putDataAsync(data)
  73. XCTAssertNotNil(result)
  74. }
  75. func testSimplePutSpecialCharacter() async throws {
  76. let ref = storage.reference(withPath: "ios/public/-._~!$'()*,=:@&+;")
  77. let data = try XCTUnwrap("Hello Swift World-._~!$'()*,=:@&+;".data(using: .utf8),
  78. "Data construction failed")
  79. let result = try await ref.putDataAsync(data)
  80. XCTAssertNotNil(result)
  81. }
  82. func testSimplePutDataInBackgroundQueue() async throws {
  83. actor Background {
  84. func uploadData(_ ref: StorageReference) async throws -> StorageMetadata {
  85. let data = try XCTUnwrap(
  86. "Hello Swift World".data(using: .utf8),
  87. "Data construction failed"
  88. )
  89. XCTAssertFalse(Thread.isMainThread)
  90. return try await ref.putDataAsync(data)
  91. }
  92. }
  93. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  94. let result = try await Background().uploadData(ref)
  95. XCTAssertNotNil(result)
  96. }
  97. func testSimplePutEmptyData() async throws {
  98. let ref = storage.reference(withPath: "ios/public/testSimplePutEmptyData")
  99. let data = Data()
  100. let result = try await ref.putDataAsync(data)
  101. XCTAssertNotNil(result)
  102. }
  103. func testSimplePutDataUnauthorized() async throws {
  104. let objectLocation = "ios/private/secretfile.txt"
  105. let ref = storage.reference(withPath: objectLocation)
  106. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  107. do {
  108. _ = try await ref.putDataAsync(data)
  109. XCTFail("Unexpected success from unauthorized putData")
  110. } catch let StorageError.unauthorized(bucket, object, _) {
  111. XCTAssertEqual(bucket, "ios-opensource-samples.appspot.com")
  112. XCTAssertEqual(object, objectLocation)
  113. } catch {
  114. XCTFail("error failed to convert to StorageError.unauthorized \(error)")
  115. }
  116. }
  117. func testSimplePutDataUnauthorizedWithNSError() async throws {
  118. let objectLocation = "ios/private/secretfile.txt"
  119. let ref = storage.reference(withPath: objectLocation)
  120. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  121. do {
  122. _ = try await ref.putDataAsync(data)
  123. XCTFail("Unexpected success from unauthorized putData")
  124. } catch {
  125. let e = error as NSError
  126. XCTAssertEqual(e.domain, StorageErrorDomain)
  127. XCTAssertEqual(e.code, StorageErrorCode.unauthorized.rawValue)
  128. XCTAssertEqual(e.localizedDescription, "User does not have permission to access" +
  129. " gs://ios-opensource-samples.appspot.com/ios/private/secretfile.txt.")
  130. XCTAssertEqual(e.userInfo["ResponseErrorCode"] as? Int, 403)
  131. print(e)
  132. }
  133. }
  134. func testAttemptToUploadDirectoryShouldFail() async throws {
  135. // This `.numbers` file is actually a directory.
  136. let fileName = "HomeImprovement.numbers"
  137. let bundle = Bundle(for: StorageIntegrationCommon.self)
  138. let fileURL = try XCTUnwrap(bundle.url(forResource: fileName, withExtension: ""),
  139. "Failed to get filePath")
  140. let ref = storage.reference(withPath: "ios/public/" + fileName)
  141. do {
  142. _ = try await ref.putFileAsync(from: fileURL)
  143. XCTFail("Unexpected success from putFile of a directory")
  144. } catch let StorageError.unknown(reason, _) {
  145. XCTAssertTrue(reason.starts(with: "File at URL:"))
  146. XCTAssertTrue(reason.hasSuffix(
  147. "is not reachable. Ensure file URL is not a directory, symbolic link, or invalid url."
  148. ))
  149. } catch {
  150. XCTFail("error failed to convert to StorageError.unknown \(error)")
  151. }
  152. }
  153. func testPutFileWithSpecialCharacters() async throws {
  154. let fileName = "hello&+@_ .txt"
  155. let ref = storage.reference(withPath: "ios/public/" + fileName)
  156. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  157. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  158. let fileURL = tmpDirURL.appendingPathComponent(#function + "hello.txt")
  159. try data.write(to: fileURL, options: .atomicWrite)
  160. let metadata = try await ref.putFileAsync(from: fileURL)
  161. XCTAssertEqual(fileName, metadata.name)
  162. let result = try await ref.getMetadata()
  163. XCTAssertNotNil(result)
  164. }
  165. func testSimplePutDataNoMetadata() async throws {
  166. let ref = storage.reference(withPath: "ios/public/testSimplePutDataNoMetadata")
  167. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  168. let result = try await ref.putDataAsync(data)
  169. XCTAssertNotNil(result)
  170. }
  171. func testSimplePutFileNoMetadata() async throws {
  172. let fileName = "hello&+@_ .txt"
  173. let ref = storage.reference(withPath: "ios/public/" + fileName)
  174. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  175. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  176. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  177. try data.write(to: fileURL, options: .atomicWrite)
  178. let result = try await ref.putFileAsync(from: fileURL)
  179. XCTAssertNotNil(result)
  180. }
  181. func testSimplePutFileWithAsyncProgress() async throws {
  182. var checkedProgress = false
  183. let ref = storage.reference(withPath: "ios/public/testSimplePutFile")
  184. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  185. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  186. let fileURL = tmpDirURL.appendingPathComponent(#function + "hello.txt")
  187. try data.write(to: fileURL, options: .atomicWrite)
  188. var uploadedBytes: Int64 = -1
  189. let successMetadata = try await ref.putFileAsync(from: fileURL) { progress in
  190. if let completed = progress?.completedUnitCount {
  191. checkedProgress = true
  192. XCTAssertGreaterThanOrEqual(completed, uploadedBytes)
  193. uploadedBytes = completed
  194. }
  195. }
  196. XCTAssertEqual(successMetadata.size, 17)
  197. XCTAssertTrue(checkedProgress)
  198. }
  199. func testSimpleGetData() async throws {
  200. let ref = storage.reference(withPath: "ios/public/1mb2")
  201. let result = try await ref.data(maxSize: 1024 * 1024)
  202. XCTAssertNotNil(result)
  203. }
  204. func testSimpleGetDataWithTask() async throws {
  205. let ref = storage.reference(withPath: "ios/public/1mb2")
  206. let result = try await ref.data(maxSize: 1024 * 1024)
  207. XCTAssertNotNil(result)
  208. }
  209. func testSimpleGetDataInBackgroundQueue() async throws {
  210. actor Background {
  211. func data(from ref: StorageReference) async throws -> Data {
  212. XCTAssertFalse(Thread.isMainThread)
  213. return try await ref.data(maxSize: 1024 * 1024)
  214. }
  215. }
  216. let ref = storage.reference(withPath: "ios/public/1mb2")
  217. let result = try await Background().data(from: ref)
  218. XCTAssertNotNil(result)
  219. }
  220. func testSimpleGetDataTooSmall() async {
  221. let ref = storage.reference(withPath: "ios/public/1mb2")
  222. let max: Int64 = 1024
  223. do {
  224. _ = try await ref.data(maxSize: max)
  225. XCTFail("Unexpected success from getData too small")
  226. } catch let StorageError.downloadSizeExceeded(total, maxSize) {
  227. XCTAssertEqual(total, 1_048_576)
  228. XCTAssertEqual(maxSize, max)
  229. } catch {
  230. XCTFail("error failed to convert to StorageError.downloadSizeExceeded")
  231. }
  232. }
  233. func testSimpleGetDownloadURL() async throws {
  234. let ref = storage.reference(withPath: "ios/public/1mb2")
  235. // Download URL format is
  236. // "https://firebasestorage.googleapis.com:443/v0/b/{bucket}/o/{path}?alt=media&token={token}"
  237. let downloadURLPattern =
  238. "^https:\\/\\/firebasestorage.googleapis.com:443\\/v0\\/b\\/[^\\/]*\\/o\\/" +
  239. "ios%2Fpublic%2F1mb2\\?alt=media&token=[a-z0-9-]*$"
  240. let downloadURL = try await ref.downloadURL()
  241. let testRegex = try NSRegularExpression(pattern: downloadURLPattern)
  242. let urlString = downloadURL.absoluteString
  243. let range = NSRange(location: 0, length: urlString.count)
  244. XCTAssertNotNil(testRegex.firstMatch(in: urlString, options: [], range: range))
  245. }
  246. func testAsyncWrite() async throws {
  247. let ref = storage.reference(withPath: "ios/public/helloworld" + #function)
  248. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  249. let fileURL = tmpDirURL.appendingPathComponent(#function + "hello.txt")
  250. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  251. _ = try await ref.putDataAsync(data)
  252. let url = try await ref.writeAsync(toFile: fileURL)
  253. XCTAssertEqual(url.lastPathComponent, #function + "hello.txt")
  254. }
  255. func testSimpleGetFile() throws {
  256. let expectation = self.expectation(description: #function)
  257. let ref = storage.reference(withPath: "ios/public/helloworld" + #function)
  258. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  259. let fileURL = tmpDirURL.appendingPathComponent(#function + "hello.txt")
  260. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  261. Task {
  262. _ = try await ref.putDataAsync(data)
  263. let task = ref.write(toFile: fileURL)
  264. task.observe(StorageTaskStatus.success) { snapshot in
  265. do {
  266. let stringData = try String(contentsOf: fileURL, encoding: .utf8)
  267. XCTAssertEqual(stringData, "Hello Swift World")
  268. XCTAssertEqual(snapshot.description, "<State: Success>")
  269. } catch {
  270. XCTFail("Error processing success snapshot")
  271. }
  272. expectation.fulfill()
  273. }
  274. task.observe(StorageTaskStatus.progress) { snapshot in
  275. XCTAssertNil(snapshot.error, "Error should be nil")
  276. guard snapshot.progress != nil else {
  277. XCTFail("Missing progress")
  278. return
  279. }
  280. }
  281. task.observe(StorageTaskStatus.failure) { snapshot in
  282. XCTAssertNil(snapshot.error, "Error should be nil")
  283. }
  284. }
  285. waitForExpectations()
  286. }
  287. func testSimpleGetFileWithAsyncProgressCallbackAPI() async throws {
  288. var checkedProgress = false
  289. let ref = storage.reference().child("ios/public/1mb")
  290. let url = URL(fileURLWithPath: "\(NSTemporaryDirectory())/hello.txt")
  291. let fileURL = url
  292. var downloadedBytes: Int64 = 0
  293. var resumeAtBytes = 256 * 1024
  294. let successURL = try await ref.writeAsync(toFile: fileURL) { progress in
  295. if let completed = progress?.completedUnitCount {
  296. checkedProgress = true
  297. XCTAssertGreaterThanOrEqual(completed, downloadedBytes)
  298. downloadedBytes = completed
  299. if completed > resumeAtBytes {
  300. resumeAtBytes = Int.max
  301. }
  302. }
  303. }
  304. XCTAssertTrue(checkedProgress)
  305. XCTAssertEqual(successURL, url)
  306. XCTAssertEqual(resumeAtBytes, Int.max)
  307. }
  308. private func assertMetadata(actualMetadata: StorageMetadata,
  309. expectedContentType: String,
  310. expectedCustomMetadata: [String: String]) {
  311. XCTAssertEqual(actualMetadata.cacheControl, "cache-control")
  312. XCTAssertEqual(actualMetadata.contentDisposition, "content-disposition")
  313. XCTAssertEqual(actualMetadata.contentEncoding, "gzip")
  314. XCTAssertEqual(actualMetadata.contentLanguage, "de")
  315. XCTAssertEqual(actualMetadata.contentType, expectedContentType)
  316. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  317. for (key, value) in expectedCustomMetadata {
  318. XCTAssertEqual(actualMetadata.customMetadata![key], value)
  319. }
  320. }
  321. private func assertMetadataNil(actualMetadata: StorageMetadata) {
  322. XCTAssertNil(actualMetadata.cacheControl)
  323. XCTAssertNil(actualMetadata.contentDisposition)
  324. XCTAssertEqual(actualMetadata.contentEncoding, "identity")
  325. XCTAssertNil(actualMetadata.contentLanguage)
  326. XCTAssertNil(actualMetadata.contentType)
  327. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  328. XCTAssertNil(actualMetadata.customMetadata)
  329. }
  330. func testUpdateMetadata2() async throws {
  331. let ref = storage.reference(withPath: "ios/public/1mb2")
  332. let metadata = StorageMetadata()
  333. metadata.cacheControl = "cache-control"
  334. metadata.contentDisposition = "content-disposition"
  335. metadata.contentEncoding = "gzip"
  336. metadata.contentLanguage = "de"
  337. metadata.contentType = "content-type-a"
  338. metadata.customMetadata = ["a": "b"]
  339. let updatedMetadata = try await ref.updateMetadata(metadata)
  340. assertMetadata(actualMetadata: updatedMetadata,
  341. expectedContentType: "content-type-a",
  342. expectedCustomMetadata: ["a": "b"])
  343. let metadata2 = updatedMetadata
  344. metadata2.contentType = "content-type-b"
  345. metadata2.customMetadata = ["a": "b", "c": "d"]
  346. let metadata3 = try await ref.updateMetadata(metadata2)
  347. assertMetadata(actualMetadata: metadata3,
  348. expectedContentType: "content-type-b",
  349. expectedCustomMetadata: ["a": "b", "c": "d"])
  350. metadata.cacheControl = nil
  351. metadata.contentDisposition = nil
  352. metadata.contentEncoding = nil
  353. metadata.contentLanguage = nil
  354. metadata.contentType = nil
  355. metadata.customMetadata = nil
  356. let metadata4 = try await ref.updateMetadata(metadata)
  357. XCTAssertNotNil(metadata4)
  358. }
  359. func testPagedListFiles() async throws {
  360. let ref = storage.reference(withPath: "ios/public/list")
  361. let listResult = try await ref.list(maxResults: 2)
  362. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  363. XCTAssertEqual(listResult.prefixes, [])
  364. let pageToken = try XCTUnwrap(listResult.pageToken)
  365. let listResult2 = try await ref.list(maxResults: 2, pageToken: pageToken)
  366. XCTAssertEqual(listResult2.items, [])
  367. XCTAssertEqual(listResult2.prefixes, [ref.child("prefix")])
  368. XCTAssertNil(listResult2.pageToken, "pageToken should be nil")
  369. }
  370. func testPagedListFilesError() async throws {
  371. let ref = storage.reference(withPath: "ios/public/list")
  372. do {
  373. let _: StorageListResult = try await ref.list(maxResults: 22222)
  374. XCTFail("Unexpected success from ref.list")
  375. } catch let StorageError.invalidArgument(message) {
  376. XCTAssertEqual(message, "Argument 'maxResults' must be between 1 and 1000 inclusive.")
  377. } catch {
  378. XCTFail("Unexpected error")
  379. }
  380. }
  381. func testListAllFiles() async throws {
  382. let ref = storage.reference(withPath: "ios/public/list")
  383. let listResult = try await ref.listAll()
  384. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  385. XCTAssertEqual(listResult.prefixes, [ref.child("prefix")])
  386. XCTAssertNil(listResult.pageToken, "pageToken should be nil")
  387. }
  388. private func waitForExpectations() {
  389. let kTestTimeout = 60.0
  390. waitForExpectations(timeout: kTestTimeout,
  391. handler: { error in
  392. if let error {
  393. print(error)
  394. }
  395. })
  396. }
  397. }