StorageIntegration.swift 21 KB

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