StorageAsyncAwait.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. // TODO: update this test to use Swift error codes.
  41. func testDelete() async throws {
  42. let objectLocation = "ios/public/fileToDelete"
  43. let ref = storage.reference(withPath: objectLocation)
  44. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  45. let result = try await ref.putDataAsync(data)
  46. XCTAssertNotNil(result)
  47. _ = try await ref.delete()
  48. // Next delete should fail and verify the first delete succeeded.
  49. do {
  50. _ = try await ref.delete()
  51. } catch {
  52. XCTAssertEqual((error as NSError).code, StorageErrorCode.objectNotFound.rawValue)
  53. }
  54. }
  55. func testDeleteAfterPut() async throws {
  56. let ref = storage.reference(withPath: "ios/public/fileToDelete")
  57. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  58. let result = try await ref.putDataAsync(data)
  59. XCTAssertNotNil(result)
  60. let result2: Void = try await ref.delete()
  61. XCTAssertNotNil(result2)
  62. }
  63. func testSimplePutData() async throws {
  64. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  65. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  66. let result = try await ref.putDataAsync(data)
  67. XCTAssertNotNil(result)
  68. }
  69. func testSimplePutSpecialCharacter() async throws {
  70. let ref = storage.reference(withPath: "ios/public/-._~!$'()*,=:@&+;")
  71. let data = try XCTUnwrap("Hello Swift World-._~!$'()*,=:@&+;".data(using: .utf8),
  72. "Data construction failed")
  73. let result = try await ref.putDataAsync(data)
  74. XCTAssertNotNil(result)
  75. }
  76. func testSimplePutDataInBackgroundQueue() async throws {
  77. actor Background {
  78. func uploadData(_ ref: StorageReference) async throws -> StorageMetadata {
  79. let data = try XCTUnwrap(
  80. "Hello Swift World".data(using: .utf8),
  81. "Data construction failed"
  82. )
  83. XCTAssertFalse(Thread.isMainThread)
  84. return try await ref.putDataAsync(data)
  85. }
  86. }
  87. let ref = storage.reference(withPath: "ios/public/testBytesUpload")
  88. let result = try await Background().uploadData(ref)
  89. XCTAssertNotNil(result)
  90. }
  91. func testSimplePutEmptyData() async throws {
  92. let ref = storage.reference(withPath: "ios/public/testSimplePutEmptyData")
  93. let data = Data()
  94. let result = try await ref.putDataAsync(data)
  95. XCTAssertNotNil(result)
  96. }
  97. func testSimplePutDataUnauthorized() async throws {
  98. let objectLocation = "ios/private/secretfile.txt"
  99. let ref = storage.reference(withPath: objectLocation)
  100. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  101. do {
  102. _ = try await ref.putDataAsync(data)
  103. XCTFail("Unexpected success from unauthorized putData")
  104. } catch let StorageError.unauthorized(bucket, object) {
  105. XCTAssertEqual(bucket, "ios-opensource-samples.appspot.com")
  106. XCTAssertEqual(object, objectLocation)
  107. } catch {
  108. XCTFail("error failed to convert to StorageError.unauthorized")
  109. }
  110. }
  111. // TODO: Update this function when the task handle APIs are updated for the new Swift Concurrency.
  112. func testSimplePutFile() throws {}
  113. func testAttemptToUploadDirectoryShouldFail() async throws {
  114. // This `.numbers` file is actually a directory.
  115. let fileName = "HomeImprovement.numbers"
  116. let bundle = Bundle(for: StorageIntegrationCommon.self)
  117. let fileURL = try XCTUnwrap(bundle.url(forResource: fileName, withExtension: ""),
  118. "Failed to get filePath")
  119. let ref = storage.reference(withPath: "ios/public/" + fileName)
  120. do {
  121. _ = try await ref.putFileAsync(from: fileURL)
  122. XCTFail("Unexpected success from putFile of a directory")
  123. } catch StorageError.unknown {
  124. XCTAssertTrue(true)
  125. } catch {
  126. XCTFail("error failed to convert to StorageError.unknown")
  127. }
  128. }
  129. func testPutFileWithSpecialCharacters() async throws {
  130. let fileName = "hello&+@_ .txt"
  131. let ref = storage.reference(withPath: "ios/public/" + fileName)
  132. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  133. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  134. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  135. try data.write(to: fileURL, options: .atomicWrite)
  136. let metadata = try await ref.putFileAsync(from: fileURL)
  137. XCTAssertEqual(fileName, metadata.name)
  138. let result = try await ref.getMetadata()
  139. XCTAssertNotNil(result)
  140. }
  141. func testSimplePutDataNoMetadata() async throws {
  142. let ref = storage.reference(withPath: "ios/public/testSimplePutDataNoMetadata")
  143. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  144. let result = try await ref.putDataAsync(data)
  145. XCTAssertNotNil(result)
  146. }
  147. func testSimplePutFileNoMetadata() async throws {
  148. let fileName = "hello&+@_ .txt"
  149. let ref = storage.reference(withPath: "ios/public/" + fileName)
  150. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  151. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  152. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  153. try data.write(to: fileURL, options: .atomicWrite)
  154. let result = try await ref.putFileAsync(from: fileURL)
  155. XCTAssertNotNil(result)
  156. }
  157. func testSimpleGetData() async throws {
  158. let ref = storage.reference(withPath: "ios/public/1mb2")
  159. let result = try await ref.data(maxSize: 1024 * 1024)
  160. XCTAssertNotNil(result)
  161. }
  162. func testSimpleGetDataWithTask() async throws {
  163. let ref = storage.reference(withPath: "ios/public/1mb2")
  164. let result = try await ref.data(maxSize: 1024 * 1024)
  165. XCTAssertNotNil(result)
  166. }
  167. func testSimpleGetDataInBackgroundQueue() async throws {
  168. actor Background {
  169. func data(from ref: StorageReference) async throws -> Data {
  170. XCTAssertFalse(Thread.isMainThread)
  171. return try await ref.data(maxSize: 1024 * 1024)
  172. }
  173. }
  174. let ref = storage.reference(withPath: "ios/public/1mb2")
  175. let result = try await Background().data(from: ref)
  176. XCTAssertNotNil(result)
  177. }
  178. func testSimpleGetDataTooSmall() async {
  179. let ref = storage.reference(withPath: "ios/public/1mb2")
  180. let max: Int64 = 1024
  181. do {
  182. _ = try await ref.data(maxSize: max)
  183. XCTFail("Unexpected success from getData too small")
  184. } catch let StorageError.downloadSizeExceeded(total, maxSize) {
  185. XCTAssertEqual(total, 1_048_576)
  186. XCTAssertEqual(maxSize, max)
  187. } catch {
  188. XCTFail("error failed to convert to StorageError.downloadSizeExceeded")
  189. }
  190. }
  191. func testSimpleGetDownloadURL() async throws {
  192. let ref = storage.reference(withPath: "ios/public/1mb2")
  193. // Download URL format is
  194. // "https://firebasestorage.googleapis.com:443/v0/b/{bucket}/o/{path}?alt=media&token={token}"
  195. let downloadURLPattern =
  196. "^https:\\/\\/firebasestorage.googleapis.com:443\\/v0\\/b\\/[^\\/]*\\/o\\/" +
  197. "ios%2Fpublic%2F1mb2\\?alt=media&token=[a-z0-9-]*$"
  198. let downloadURL = try await ref.downloadURL()
  199. let testRegex = try NSRegularExpression(pattern: downloadURLPattern)
  200. let urlString = downloadURL.absoluteString
  201. let range = NSRange(location: 0, length: urlString.count)
  202. XCTAssertNotNil(testRegex.firstMatch(in: urlString, options: [], range: range))
  203. }
  204. func testAsyncWrite() async throws {
  205. let ref = storage.reference(withPath: "ios/public/helloworld")
  206. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  207. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  208. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  209. _ = try await ref.putDataAsync(data)
  210. let url = try await ref.writeAsync(toFile: fileURL)
  211. XCTAssertEqual(url.lastPathComponent, "hello.txt")
  212. }
  213. func testSimpleGetFile() throws {
  214. let expectation = self.expectation(description: #function)
  215. let ref = storage.reference(withPath: "ios/public/helloworld")
  216. let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
  217. let fileURL = tmpDirURL.appendingPathComponent("hello.txt")
  218. let data = try XCTUnwrap("Hello Swift World".data(using: .utf8), "Data construction failed")
  219. Task {
  220. _ = try await ref.putDataAsync(data)
  221. let task = ref.write(toFile: fileURL)
  222. // TODO: Update to use Swift Tasks
  223. task.observe(StorageTaskStatus.success) { snapshot in
  224. do {
  225. let stringData = try String(contentsOf: fileURL, encoding: .utf8)
  226. XCTAssertEqual(stringData, "Hello Swift World")
  227. XCTAssertEqual(snapshot.description, "<State: Success>")
  228. } catch {
  229. XCTFail("Error processing success snapshot")
  230. }
  231. expectation.fulfill()
  232. }
  233. task.observe(StorageTaskStatus.progress) { snapshot in
  234. XCTAssertNil(snapshot.error, "Error should be nil")
  235. guard let progress = snapshot.progress else {
  236. XCTFail("Missing progress")
  237. return
  238. }
  239. print("\(progress.completedUnitCount) of \(progress.totalUnitCount)")
  240. }
  241. task.observe(StorageTaskStatus.failure) { snapshot in
  242. XCTAssertNil(snapshot.error, "Error should be nil")
  243. }
  244. }
  245. waitForExpectations()
  246. }
  247. private func assertMetadata(actualMetadata: StorageMetadata,
  248. expectedContentType: String,
  249. expectedCustomMetadata: [String: String]) {
  250. XCTAssertEqual(actualMetadata.cacheControl, "cache-control")
  251. XCTAssertEqual(actualMetadata.contentDisposition, "content-disposition")
  252. XCTAssertEqual(actualMetadata.contentEncoding, "gzip")
  253. XCTAssertEqual(actualMetadata.contentLanguage, "de")
  254. XCTAssertEqual(actualMetadata.contentType, expectedContentType)
  255. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  256. for (key, value) in expectedCustomMetadata {
  257. XCTAssertEqual(actualMetadata.customMetadata![key], value)
  258. }
  259. }
  260. private func assertMetadataNil(actualMetadata: StorageMetadata) {
  261. XCTAssertNil(actualMetadata.cacheControl)
  262. XCTAssertNil(actualMetadata.contentDisposition)
  263. XCTAssertEqual(actualMetadata.contentEncoding, "identity")
  264. XCTAssertNil(actualMetadata.contentLanguage)
  265. XCTAssertNil(actualMetadata.contentType)
  266. XCTAssertEqual(actualMetadata.md5Hash?.count, 24)
  267. XCTAssertNil(actualMetadata.customMetadata)
  268. }
  269. func testUpdateMetadata2() async throws {
  270. let ref = storage.reference(withPath: "ios/public/1mb2")
  271. let metadata = StorageMetadata()
  272. metadata.cacheControl = "cache-control"
  273. metadata.contentDisposition = "content-disposition"
  274. metadata.contentEncoding = "gzip"
  275. metadata.contentLanguage = "de"
  276. metadata.contentType = "content-type-a"
  277. metadata.customMetadata = ["a": "b"]
  278. let updatedMetadata = try await ref.updateMetadata(metadata)
  279. assertMetadata(actualMetadata: updatedMetadata,
  280. expectedContentType: "content-type-a",
  281. expectedCustomMetadata: ["a": "b"])
  282. let metadata2 = updatedMetadata
  283. metadata2.contentType = "content-type-b"
  284. metadata2.customMetadata = ["a": "b", "c": "d"]
  285. let metadata3 = try await ref.updateMetadata(metadata2)
  286. assertMetadata(actualMetadata: metadata3,
  287. expectedContentType: "content-type-b",
  288. expectedCustomMetadata: ["a": "b", "c": "d"])
  289. metadata.cacheControl = nil
  290. metadata.contentDisposition = nil
  291. metadata.contentEncoding = nil
  292. metadata.contentLanguage = nil
  293. metadata.contentType = nil
  294. metadata.customMetadata = nil
  295. let metadata4 = try await ref.updateMetadata(metadata)
  296. XCTAssertNotNil(metadata4)
  297. }
  298. func testPagedListFiles() async throws {
  299. let ref = storage.reference(withPath: "ios/public/list")
  300. let listResult = try await ref.list(maxResults: 2)
  301. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  302. XCTAssertEqual(listResult.prefixes, [])
  303. let pageToken = try XCTUnwrap(listResult.pageToken)
  304. let listResult2 = try await ref.list(maxResults: 2, pageToken: pageToken)
  305. XCTAssertEqual(listResult2.items, [])
  306. XCTAssertEqual(listResult2.prefixes, [ref.child("prefix")])
  307. XCTAssertNil(listResult2.pageToken, "pageToken should be nil")
  308. }
  309. // TODO: Update to Swift error codes.
  310. func testPagedListFilesError() async throws {
  311. let ref = storage.reference(withPath: "ios/public/list")
  312. do {
  313. let _: StorageListResult = try await ref.list(maxResults: 22222)
  314. XCTFail("Unexpected success from ref.list")
  315. } catch let StorageError.invalidArgument(message) {
  316. XCTAssertEqual(message, "Argument 'maxResults' must be between 1 and 1000 inclusive.")
  317. } catch {
  318. XCTFail("Unexpected error")
  319. }
  320. }
  321. func testListAllFiles() async throws {
  322. let ref = storage.reference(withPath: "ios/public/list")
  323. let listResult = try await ref.listAll()
  324. XCTAssertEqual(listResult.items, [ref.child("a"), ref.child("b")])
  325. XCTAssertEqual(listResult.prefixes, [ref.child("prefix")])
  326. XCTAssertNil(listResult.pageToken, "pageToken should be nil")
  327. }
  328. private func waitForExpectations() {
  329. let kTestTimeout = 60.0
  330. waitForExpectations(timeout: kTestTimeout,
  331. handler: { error in
  332. if let error = error {
  333. print(error)
  334. }
  335. })
  336. }
  337. }
  338. #endif