StorageIntegration.swift 21 KB

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