StorageAsyncAwait.swift 17 KB

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