StorageIntegration.swift 21 KB

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