ConfigDBManagerOrigTest.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. // Copyright 2024 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. @testable import FirebaseRemoteConfig
  15. import XCTest
  16. class ConfigDBManagerOrigTest: XCTestCase {
  17. private var dbPath: String!
  18. private var dbManager: ConfigDBManager!
  19. private let expectationTimeout: TimeInterval = 10.0
  20. override func setUp() {
  21. super.setUp()
  22. dbPath = Self.remoteConfigPath(forTestDatabase: databaseName)
  23. dbManager = ConfigDBManager(dbPath: dbPath)
  24. }
  25. override func tearDown() {
  26. dbManager.removeDatabase(path: dbPath)
  27. super.tearDown()
  28. }
  29. func testV1NamespaceMigrationToV2Namespace() {
  30. let namespace = "testNamespace"
  31. let bundleIdentifier = Bundle.main.bundleIdentifier!
  32. let expectation = expectation(description: "test v1 namespace migration to v2 namespace")
  33. var count = 0
  34. for i in 0 ... 100 {
  35. let value = "value\(i)"
  36. let key = "key\(i)"
  37. let values: [Any] = [bundleIdentifier, namespace, key, value.data(using: .utf8)!]
  38. dbManager.insertMainTable(withValues: values, fromSource: .fetched) { success, _ in
  39. XCTAssertTrue(success)
  40. count += 1
  41. if count == 101 {
  42. self.dbManager.createOrOpenDatabase()
  43. self.dbManager.loadMain(withBundleIdentifier: bundleIdentifier) {
  44. success, fetched, _, _, _ in
  45. XCTAssertTrue(success)
  46. let fullyQualifiedNamespace = "\(namespace):__FIRAPP_DEFAULT"
  47. XCTAssertNotNil(fetched[fullyQualifiedNamespace])
  48. XCTAssertEqual(fetched[fullyQualifiedNamespace]?.count, 101)
  49. XCTAssertNil(fetched[namespace])
  50. expectation.fulfill()
  51. }
  52. }
  53. }
  54. }
  55. waitForExpectations(timeout: expectationTimeout)
  56. }
  57. func testWriteAndLoadMainTableResult() {
  58. let namespace = "namespace_1"
  59. let bundleIdentifier = Bundle.main.bundleIdentifier!
  60. let expectation = expectation(description: "Write and read metadata in database serially")
  61. var count = 0
  62. for i in 0 ... 100 {
  63. let value = "value\(i)"
  64. let key = "key\(i)"
  65. let values: [Any] = [bundleIdentifier, namespace, key, value.data(using: .utf8)!]
  66. dbManager.insertMainTable(withValues: values, fromSource: .fetched) { success, _ in
  67. XCTAssertTrue(success)
  68. count += 1
  69. if count == 101 {
  70. self.dbManager.loadMain(withBundleIdentifier: bundleIdentifier) {
  71. success, fetchedConfig, _, _, _ in
  72. XCTAssertTrue(success)
  73. let configValue = fetchedConfig[namespace]?["key100"]
  74. XCTAssertEqual(configValue?.stringValue, "value100")
  75. expectation.fulfill()
  76. }
  77. }
  78. }
  79. }
  80. waitForExpectations(timeout: expectationTimeout)
  81. }
  82. func testWriteAndLoadMetadataResult() {
  83. let expectation = expectation(description: "Write and load metadata successfully")
  84. let bundleIdentifier = Bundle.main.bundleIdentifier!
  85. let namespace = "test_namespace"
  86. let lastFetchTimestamp = Date().timeIntervalSince1970
  87. let now = Date().timeIntervalSince1970
  88. let deviceContext: [String: String] = [
  89. "app_version": "1.0.1",
  90. "app_build": "1.0.1.11",
  91. "os_version": "iOS9.1",
  92. ]
  93. let customVariables: [String: Sendable] = ["user_level": 15, "user_experiences": "2468"]
  94. let successFetchTimes: [TimeInterval] = []
  95. let failureFetchTimes: [TimeInterval] = [now - 200, now]
  96. let columnNameToValue: [String: Any] = [
  97. RCNKeyBundleIdentifier: bundleIdentifier,
  98. RCNKeyNamespace: namespace,
  99. RCNKeyFetchTime: lastFetchTimestamp,
  100. RCNKeyDigestPerNamespace: try! JSONSerialization
  101. .data(withJSONObject: [:], options: .prettyPrinted), // Empty dictionary
  102. RCNKeyDeviceContext: try! JSONSerialization
  103. .data(withJSONObject: deviceContext, options: .prettyPrinted),
  104. RCNKeyAppContext: try! JSONSerialization
  105. .data(withJSONObject: customVariables, options: .prettyPrinted),
  106. RCNKeySuccessFetchTime: try! JSONSerialization
  107. .data(withJSONObject: successFetchTimes, options: .prettyPrinted),
  108. RCNKeyFailureFetchTime: try! JSONSerialization
  109. .data(withJSONObject: failureFetchTimes, options: .prettyPrinted),
  110. RCNKeyLastFetchStatus: RemoteConfigFetchStatus.success.rawValue,
  111. RCNKeyLastFetchError: RemoteConfigError.unknown.rawValue,
  112. RCNKeyLastApplyTime: now - 100,
  113. RCNKeyLastSetDefaultsTime: now - 200,
  114. ]
  115. dbManager.insertMetadataTable(withValues: columnNameToValue) { success, _ in
  116. XCTAssertTrue(success)
  117. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  118. namespace: namespace) { result in
  119. XCTAssertEqual(result[RCNKeyBundleIdentifier] as? String, bundleIdentifier)
  120. XCTAssertEqual(result[RCNKeyFetchTime] as? TimeInterval, lastFetchTimestamp)
  121. XCTAssertEqual(result[RCNKeyDigestPerNamespace] as? [String: String], [:])
  122. XCTAssertEqual(result[RCNKeyDeviceContext] as? [String: String], deviceContext)
  123. let loadedCustomVariables = result[RCNKeyAppContext] as? [String: Sendable]
  124. XCTAssertEqual(loadedCustomVariables?["user_level"] as? Int, 15)
  125. XCTAssertEqual(loadedCustomVariables?["user_experiences"] as? String, "2468")
  126. XCTAssertEqual(result[RCNKeyLastApplyTime] as? Double, now - 100)
  127. XCTAssertEqual(result[RCNKeyLastSetDefaultsTime] as? Double, now - 200)
  128. expectation.fulfill()
  129. }
  130. }
  131. waitForExpectations(timeout: expectationTimeout)
  132. }
  133. func testWriteAndLoadMetadataForMultipleNamespaces() {
  134. let expectation1 = expectation(
  135. description: "Metadata is stored and read based on namespace1"
  136. )
  137. let expectation2 = expectation(
  138. description: "Metadata is stored and read based on namespace2"
  139. )
  140. let bundleIdentifier = Bundle.main.bundleIdentifier!
  141. let deviceContext: [String: String] = [:] // Empty dictionary
  142. let customVariables: [String: Any] = [:] // Empty dictionary
  143. let namespace1 = "test_namespace"
  144. let namespace2 = "test_namespace_2"
  145. let lastApplyTime1 = 100.0
  146. let lastSetDefaultsTime1 = 200.0
  147. let lastApplyTime2 = 300.0
  148. let lastSetDefaultsTime2 = 400.0
  149. let serializedAppContext = try! JSONSerialization
  150. .data(withJSONObject: customVariables, options: .prettyPrinted)
  151. let serializedDeviceContext = try! JSONSerialization
  152. .data(withJSONObject: deviceContext, options: .prettyPrinted)
  153. let serializedDigestPerNamespace = try! JSONSerialization
  154. .data(withJSONObject: [:], options: .prettyPrinted) // Empty dictionary
  155. let serializedSuccessTime = try! JSONSerialization
  156. .data(withJSONObject: [], options: []) // Empty
  157. let serializedFailureTime = try! JSONSerialization
  158. .data(withJSONObject: [], options: []) // Empty
  159. let valuesForNamespace1: [String: Any] = [
  160. RCNKeyBundleIdentifier: bundleIdentifier,
  161. RCNKeyNamespace: namespace1,
  162. RCNKeyFetchTime: 0, // Or appropriate initial value
  163. RCNKeyDigestPerNamespace: serializedDigestPerNamespace,
  164. RCNKeyDeviceContext: serializedDeviceContext,
  165. RCNKeyAppContext: serializedAppContext,
  166. RCNKeySuccessFetchTime: serializedSuccessTime,
  167. RCNKeyFailureFetchTime: serializedFailureTime,
  168. RCNKeyLastFetchStatus: RemoteConfigFetchStatus.success.rawValue,
  169. RCNKeyLastFetchError: RemoteConfigError.unknown.rawValue,
  170. RCNKeyLastApplyTime: lastApplyTime1,
  171. RCNKeyLastSetDefaultsTime: lastSetDefaultsTime1,
  172. ]
  173. let valuesForNamespace2: [String: Any] = [
  174. RCNKeyBundleIdentifier: bundleIdentifier,
  175. RCNKeyNamespace: namespace2,
  176. RCNKeyFetchTime: 0,
  177. RCNKeyDigestPerNamespace: serializedDigestPerNamespace,
  178. RCNKeyDeviceContext: serializedDeviceContext,
  179. RCNKeyAppContext: serializedAppContext,
  180. RCNKeySuccessFetchTime: serializedSuccessTime,
  181. RCNKeyFailureFetchTime: serializedFailureTime,
  182. RCNKeyLastFetchStatus: RemoteConfigFetchStatus.success.rawValue,
  183. RCNKeyLastFetchError: RemoteConfigError.unknown.rawValue,
  184. RCNKeyLastApplyTime: lastApplyTime2,
  185. RCNKeyLastSetDefaultsTime: lastSetDefaultsTime2,
  186. ]
  187. dbManager.insertMetadataTable(withValues: valuesForNamespace1) { success, _ in
  188. XCTAssertTrue(success)
  189. self.dbManager.insertMetadataTable(withValues: valuesForNamespace2) { success, _ in
  190. XCTAssertTrue(success)
  191. // Load and verify namespace 1:
  192. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  193. namespace: namespace1) { result in
  194. XCTAssertEqual(result[RCNKeyLastApplyTime] as? Double, lastApplyTime1)
  195. XCTAssertEqual(result[RCNKeyLastSetDefaultsTime] as? Double, lastSetDefaultsTime1)
  196. expectation1.fulfill()
  197. }
  198. // Load and verify namespace 2:
  199. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  200. namespace: namespace2) { result in
  201. XCTAssertEqual(result[RCNKeyLastApplyTime] as? Double, lastApplyTime2)
  202. XCTAssertEqual(result[RCNKeyLastSetDefaultsTime] as? Double, lastSetDefaultsTime2)
  203. expectation2.fulfill()
  204. }
  205. }
  206. }
  207. waitForExpectations(timeout: expectationTimeout)
  208. }
  209. // Create a key each for two namespaces, delete it from one namespace, read both namespaces.
  210. func testDeleteParamAndLoadMainTable() {
  211. let namespaceToDelete = "namespace_delete"
  212. let namespaceToKeep = "namespace_keep"
  213. let bundleIdentifier = "testBundleID"
  214. let deleteExpectation =
  215. expectation(description: "Contents of 'namespace_delete' should be deleted.")
  216. let keepExpectation =
  217. expectation(description: "Write a key to namespace_keep and read back again.")
  218. let keyToDelete = "keyToDelete"
  219. let valueToDelete = "valueToDelete"
  220. let keyToRetain = "keyToRetain"
  221. let valueToRetain = "valueToRetain"
  222. let itemsToDelete: [Any] = [
  223. bundleIdentifier,
  224. namespaceToDelete,
  225. keyToDelete,
  226. valueToDelete.data(using: .utf8)!,
  227. ]
  228. let itemsToRetain: [Any] = [
  229. bundleIdentifier,
  230. namespaceToKeep,
  231. keyToRetain,
  232. valueToRetain.data(using: .utf8)!,
  233. ]
  234. // First write the data to both namespaces, then delete.
  235. dbManager.insertMainTable(withValues: itemsToDelete, fromSource: .active) { success, _ in
  236. XCTAssertTrue(success)
  237. self.dbManager
  238. .insertMainTable(withValues: itemsToRetain, fromSource: .active) { success, _ in
  239. XCTAssertTrue(success)
  240. self.dbManager.deleteRecord(fromMainTableWithNamespace: namespaceToDelete,
  241. bundleIdentifier: bundleIdentifier,
  242. fromSource: .active)
  243. self.dbManager.loadMain(withBundleIdentifier: bundleIdentifier) {
  244. success, _, active, _, _ in
  245. XCTAssertTrue(success)
  246. XCTAssertNil(active[namespaceToDelete]?[keyToDelete])
  247. XCTAssertEqual(active[namespaceToKeep]?[keyToRetain]?.stringValue, valueToRetain)
  248. deleteExpectation.fulfill()
  249. }
  250. }
  251. }
  252. dbManager.loadMain(withBundleIdentifier: bundleIdentifier) { success, _, active, _, _ in
  253. XCTAssertTrue(success)
  254. XCTAssertEqual(active[namespaceToKeep]?[keyToRetain]?.stringValue, valueToRetain)
  255. keepExpectation.fulfill()
  256. }
  257. waitForExpectations(timeout: expectationTimeout)
  258. }
  259. func testWriteAndLoadExperiments() {
  260. let expectation =
  261. expectation(description: "Update and load experiment in database successfully")
  262. let payload1 = Data() // Empty Data
  263. let payload2 = try! JSONSerialization.data(
  264. withJSONObject: ["ab", "cd"],
  265. options: .prettyPrinted
  266. )
  267. let payload3 = try! JSONSerialization.data(
  268. withJSONObject: ["experiment_ID": "35667", "experiment_activate_name": "activate_game"],
  269. options: .prettyPrinted
  270. )
  271. let payloads = [payload2, payload3, payload1] as [Any] // Mixed types require Any
  272. // Insert payloads asynchronously
  273. dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyPayload,
  274. value: payload1) { _, _ in
  275. self.dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyPayload,
  276. value: payload2) { _, _ in
  277. self.dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyPayload,
  278. value: payload3) { _, _ in
  279. let metadata: [String: Any] = [
  280. "last_known_start_time": -11,
  281. "experiment_new_metadata": "wonderful",
  282. ]
  283. let serializedMetadata = try! JSONSerialization
  284. .data(withJSONObject: metadata, options: .prettyPrinted)
  285. self.dbManager.insertExperimentTable(
  286. withKey: ConfigConstants.experimentTableKeyMetadata,
  287. value: serializedMetadata
  288. ) { success, _ in
  289. XCTAssertTrue(success)
  290. self.dbManager.loadExperiment { success, experimentResults in
  291. XCTAssertTrue(success)
  292. guard let results = experimentResults else {
  293. XCTFail("Expected experiment results")
  294. return
  295. }
  296. XCTAssertNotNil(results[ConfigConstants.experimentTableKeyPayload])
  297. // Sort to avoid flaky tests due to array order
  298. let loadedPayloads = (results[ConfigConstants.experimentTableKeyPayload] as! [Data])
  299. .sorted { $0.hashValue < $1.hashValue }
  300. let sortedInput = (payloads as! [Data]).sorted { $0.hashValue < $1.hashValue }
  301. for (index, payload) in sortedInput.enumerated() {
  302. XCTAssertEqual(loadedPayloads[index], payload)
  303. }
  304. XCTAssertNotNil(results[ConfigConstants.experimentTableKeyMetadata])
  305. let loadedMetadata =
  306. results[ConfigConstants.experimentTableKeyMetadata] as! [String: Any]
  307. let startTime = loadedMetadata["last_known_start_time"] as! Double
  308. XCTAssertEqual(startTime, -11, accuracy: 1.0)
  309. XCTAssertEqual(loadedMetadata["experiment_new_metadata"] as? String, "wonderful")
  310. expectation.fulfill()
  311. }
  312. }
  313. }
  314. }
  315. }
  316. waitForExpectations(timeout: expectationTimeout)
  317. }
  318. func testWriteAndLoadActivatedExperiments() {
  319. let expectation = expectation(
  320. description: "Update and load activated experiments in database successfully"
  321. )
  322. let payload1 = Data() // Empty Data
  323. let payload2 = try! JSONSerialization.data(
  324. withJSONObject: ["ab", "cd"],
  325. options: .prettyPrinted
  326. )
  327. let payload3 = try! JSONSerialization.data(
  328. withJSONObject: ["experiment_ID": "35667", "experiment_activate_name": "activate_game"],
  329. options: .prettyPrinted
  330. )
  331. let payloads = [payload2, payload3, payload1]
  332. // Insert payloads using a loop and DispatchGroup for synchronization
  333. let dispatchGroup = DispatchGroup()
  334. for payload in payloads {
  335. dispatchGroup.enter()
  336. dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyActivePayload,
  337. value: payload) { success, _ in
  338. XCTAssertTrue(success)
  339. dispatchGroup.leave()
  340. }
  341. }
  342. // Wait for all inserts to complete before loading
  343. dispatchGroup.notify(queue: .global()) {
  344. self.dbManager.loadExperiment { success, experimentResults in
  345. XCTAssertTrue(success)
  346. guard let results = experimentResults else {
  347. XCTFail("Expected experiment results")
  348. return
  349. }
  350. XCTAssertNotNil(results[ConfigConstants.experimentTableKeyActivePayload])
  351. // Sort to prevent flaky tests due to array order.
  352. let loadedPayloads = (results[ConfigConstants.experimentTableKeyActivePayload] as! [Data])
  353. .sorted { $0.hashValue < $1.hashValue }
  354. let sortedInput = payloads.sorted { $0.hashValue < $1.hashValue }
  355. XCTAssertEqual(loadedPayloads, sortedInput)
  356. expectation.fulfill()
  357. }
  358. }
  359. waitForExpectations(timeout: expectationTimeout)
  360. }
  361. func testWriteAndLoadMetadataMultipleTimes() {
  362. let expectation = expectation(
  363. description: "Update and load experiment metadata in database successfully"
  364. )
  365. let metadata1: [String: Any] = [
  366. "last_known_start_time": -11,
  367. "experiment_new_metadata": "wonderful",
  368. ]
  369. let serializedMetadata1 = try! JSONSerialization.data(withJSONObject: metadata1,
  370. options: .prettyPrinted)
  371. let metadata2: [String: Any] = [
  372. "last_known_start_time": 12_345_678,
  373. "experiment_new_metadata": "wonderful",
  374. ]
  375. let serializedMetadata2 = try! JSONSerialization.data(withJSONObject: metadata2,
  376. options: .prettyPrinted)
  377. // Insert the first metadata
  378. dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyMetadata,
  379. value: serializedMetadata1) { success, _ in
  380. XCTAssertTrue(success)
  381. // Insert the updated metadata. This should replace the previous entry.
  382. self.dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyMetadata,
  383. value: serializedMetadata2) { success, _ in
  384. XCTAssertTrue(success)
  385. self.dbManager.loadExperiment { success, experimentResults in
  386. XCTAssertTrue(success)
  387. guard let results = experimentResults else {
  388. XCTFail("Expected experiment results")
  389. return
  390. }
  391. XCTAssertNotNil(results[ConfigConstants.experimentTableKeyMetadata])
  392. let loadedMetadata =
  393. results[ConfigConstants.experimentTableKeyMetadata] as! [String: Any]
  394. XCTAssertEqual(loadedMetadata["last_known_start_time"] as! Double, 12_345_678.0,
  395. accuracy: 1.0)
  396. XCTAssertEqual(loadedMetadata["experiment_new_metadata"] as? String, "wonderful")
  397. expectation.fulfill()
  398. }
  399. }
  400. }
  401. waitForExpectations(timeout: expectationTimeout)
  402. }
  403. func testWriteAndLoadFetchedAndActiveRollout() {
  404. let expectation = expectation(description: "Write and load rollout in database successfully")
  405. let bundleIdentifier = Bundle.main.bundleIdentifier!
  406. let fetchedRollout = [
  407. [
  408. "rollout_id": "1",
  409. "variant_id": "B",
  410. "affected_parameter_keys": ["key_1", "key_2"],
  411. ],
  412. [
  413. "rollout_id": "2",
  414. "variant_id": "1",
  415. "affected_parameter_keys": ["key_1", "key_3"],
  416. ],
  417. ]
  418. let activeRollout = [
  419. [
  420. "rollout_id": "1",
  421. "variant_id": "B",
  422. "affected_parameter_keys": ["key_1", "key_2"],
  423. ],
  424. [
  425. "rollout_id": "3",
  426. "variant_id": "a",
  427. "affected_parameter_keys": ["key_1", "key_3"],
  428. ],
  429. ]
  430. dbManager.insertOrUpdateRolloutTable(withKey: ConfigConstants.rolloutTableKeyFetchedMetadata,
  431. value: fetchedRollout) {
  432. success,
  433. _ in
  434. XCTAssertTrue(success)
  435. self.dbManager.insertOrUpdateRolloutTable(
  436. withKey: ConfigConstants.rolloutTableKeyActiveMetadata,
  437. value: activeRollout
  438. ) {
  439. success,
  440. _ in
  441. XCTAssertTrue(success)
  442. self.dbManager.loadMain(withBundleIdentifier: bundleIdentifier) {
  443. success,
  444. _,
  445. _,
  446. _,
  447. rolloutMetadata in
  448. XCTAssertTrue(success)
  449. let loadedFetchedRollout = rolloutMetadata[ConfigConstants.rolloutTableKeyFetchedMetadata]
  450. let loadedFetchedID = loadedFetchedRollout?[1]["fetched_id"] as? String
  451. let fetchedID = fetchedRollout[1]["fetched_id"] as? String
  452. XCTAssertEqual(loadedFetchedID, fetchedID)
  453. let loadedActiveRollout = rolloutMetadata[ConfigConstants.rolloutTableKeyActiveMetadata]
  454. let loadedParameters = loadedActiveRollout?[0]["affected_parameter_keys"] as? [String]
  455. let parameters = fetchedRollout[0]["affected_parameter_keys"] as? [String]
  456. XCTAssertEqual(loadedParameters, parameters)
  457. expectation.fulfill()
  458. }
  459. }
  460. }
  461. waitForExpectations(timeout: expectationTimeout)
  462. }
  463. func testUpdateAndLoadRollout() {
  464. let expectation = expectation(description: "Update and load rollout in database successfully")
  465. let bundleIdentifier = Bundle.main.bundleIdentifier!
  466. let initialFetchedRollout: [[String: Any]] = [
  467. [
  468. "rollout_id": "1",
  469. "variant_id": "B",
  470. "affected_parameter_keys": ["key_1", "key_2"],
  471. ],
  472. ]
  473. let updatedFetchedRollout: [[String: Any]] = [
  474. [
  475. "rollout_id": "1",
  476. "variant_id": "B",
  477. "affected_parameter_keys": ["key_1", "key_2"],
  478. ],
  479. [
  480. "rollout_id": "2",
  481. "variant_id": "1",
  482. "affected_parameter_keys": ["key_1", "key_3"],
  483. ],
  484. ]
  485. dbManager.insertOrUpdateRolloutTable(withKey: ConfigConstants.rolloutTableKeyFetchedMetadata,
  486. value: initialFetchedRollout) { success, _ in
  487. XCTAssertTrue(success)
  488. self.dbManager.insertOrUpdateRolloutTable(
  489. withKey: ConfigConstants.rolloutTableKeyFetchedMetadata,
  490. value: updatedFetchedRollout
  491. ) { success, _ in
  492. XCTAssertTrue(success)
  493. self.dbManager.loadMain(withBundleIdentifier: bundleIdentifier) {
  494. success, _, _, _, rolloutMetadata in
  495. XCTAssertTrue(success)
  496. let loadedFetchedRollout = rolloutMetadata[ConfigConstants.rolloutTableKeyFetchedMetadata]
  497. let loadedFetchedID = loadedFetchedRollout?[0]["variant_id"] as? String
  498. let fetchedID = updatedFetchedRollout[0]["variant_id"] as? String
  499. XCTAssertEqual(loadedFetchedID, fetchedID)
  500. let loadedParameters = loadedFetchedRollout?[1]["affected_parameter_keys"] as? [String]
  501. let parameters = updatedFetchedRollout[1]["affected_parameter_keys"] as? [String]
  502. XCTAssertEqual(loadedParameters, parameters)
  503. expectation.fulfill()
  504. }
  505. }
  506. }
  507. waitForExpectations(timeout: expectationTimeout)
  508. }
  509. func testLoadEmptyRollout() {
  510. let expectation = expectation(description: "Load empty rollout in database successfully")
  511. let bundleIdentifier = Bundle.main.bundleIdentifier!
  512. dbManager
  513. .loadMain(withBundleIdentifier: bundleIdentifier) { success, _, _, _, rolloutMetadata in
  514. XCTAssertTrue(success)
  515. let loadedFetchedRollout = rolloutMetadata[ConfigConstants.rolloutTableKeyFetchedMetadata]
  516. let loadedActiveRollout = rolloutMetadata[ConfigConstants.rolloutTableKeyActiveMetadata]
  517. XCTAssertEqual(loadedFetchedRollout?.count, 0) // Assert against empty array
  518. XCTAssertEqual(loadedActiveRollout?.count, 0) // Assert against empty array
  519. expectation.fulfill()
  520. }
  521. waitForExpectations(timeout: expectationTimeout)
  522. }
  523. func testUpdateAndloadLastFetchStatus() {
  524. let expectation = expectation(
  525. description: "Update and load last fetch status in database successfully."
  526. )
  527. let bundleIdentifier = Bundle.main.bundleIdentifier!
  528. let namespace = "test_namespace"
  529. let sampleMetadata = createSampleMetadata()
  530. dbManager.insertMetadataTable(withValues: sampleMetadata) { success, _ in
  531. XCTAssertTrue(success)
  532. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  533. namespace: namespace) { result in
  534. XCTAssertEqual(result[RCNKeyLastFetchStatus] as? Int,
  535. RemoteConfigFetchStatus.success.rawValue)
  536. XCTAssertEqual(result[RCNKeyLastFetchError] as? Int, RemoteConfigError.unknown.rawValue)
  537. let updatedValues: [Any] = [
  538. RemoteConfigFetchStatus.throttled.rawValue,
  539. RemoteConfigError.throttled.rawValue,
  540. ]
  541. self.dbManager.updateMetadata(withOption: .fetchStatus,
  542. namespace: namespace,
  543. values: updatedValues) { success, _ in
  544. XCTAssertTrue(success)
  545. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  546. namespace: namespace) { result in
  547. XCTAssertEqual(result[RCNKeyLastFetchStatus] as? Int,
  548. RemoteConfigFetchStatus.throttled.rawValue)
  549. XCTAssertEqual(result[RCNKeyLastFetchError] as? Int,
  550. RemoteConfigError.throttled.rawValue)
  551. expectation.fulfill()
  552. }
  553. }
  554. }
  555. }
  556. waitForExpectations(timeout: expectationTimeout)
  557. }
  558. /// Tests that we can insert values in the database and can update them.
  559. func testInsertAndUpdateApplyTime() {
  560. let expectation = expectation(description: "Update and load apply time successfully")
  561. let bundleIdentifier = Bundle.main.bundleIdentifier!
  562. let namespace = "test_namespace"
  563. let lastApplyTimestamp = Date().timeIntervalSince1970
  564. let sampleMetadata = createSampleMetadata()
  565. dbManager.insertMetadataTable(withValues: sampleMetadata) { success, _ in
  566. XCTAssertTrue(success)
  567. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  568. namespace: namespace) { result in
  569. XCTAssertEqual(result[RCNKeyLastApplyTime] as? Double, 100) // Original value
  570. self.dbManager.updateMetadata(withOption: .applyTime,
  571. namespace: namespace,
  572. values: [lastApplyTimestamp]) { success, _ in
  573. XCTAssertTrue(success)
  574. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  575. namespace: namespace) { result in
  576. XCTAssertEqual(result[RCNKeyLastApplyTime] as? Double, lastApplyTimestamp)
  577. expectation.fulfill()
  578. }
  579. }
  580. }
  581. }
  582. waitForExpectations(timeout: expectationTimeout)
  583. }
  584. func testUpdateAndLoadSetDefaultsTime() {
  585. let expectation = expectation(
  586. description: "Update and load set defaults time in database successfully."
  587. )
  588. let bundleIdentifier = Bundle.main.bundleIdentifier!
  589. let namespace = "test_namespace"
  590. let lastSetDefaultsTimestamp = Date().timeIntervalSince1970
  591. let sampleMetadata = createSampleMetadata()
  592. dbManager.insertMetadataTable(withValues: sampleMetadata) { success, _ in
  593. XCTAssertTrue(success)
  594. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  595. namespace: namespace) { result in
  596. XCTAssertEqual(result[RCNKeyLastSetDefaultsTime] as? Double, 200)
  597. self.dbManager.updateMetadata(withOption: .defaultTime,
  598. namespace: namespace,
  599. values: [lastSetDefaultsTimestamp]) { success, _ in
  600. XCTAssertTrue(success)
  601. self.dbManager.loadMetadata(withBundleIdentifier: bundleIdentifier,
  602. namespace: namespace) { result in
  603. XCTAssertEqual(result[RCNKeyLastSetDefaultsTime] as? Double, lastSetDefaultsTimestamp)
  604. expectation.fulfill()
  605. }
  606. }
  607. }
  608. }
  609. waitForExpectations(timeout: expectationTimeout)
  610. }
  611. private func createSampleMetadata() -> [String: Any] {
  612. let bundleIdentifier = Bundle.main.bundleIdentifier!
  613. let namespace = "test_namespace"
  614. let deviceContext = [String: String]() // Empty dictionary
  615. let customVariables = [String: String]() // Empty dictionary
  616. let successFetchTimes = [TimeInterval]() // Empty array
  617. let failureFetchTimes = [TimeInterval]() // Empty array
  618. return [
  619. RCNKeyBundleIdentifier: bundleIdentifier,
  620. RCNKeyNamespace: namespace,
  621. RCNKeyFetchTime: 0, // Or appropriate initial value
  622. RCNKeyDigestPerNamespace: try! JSONSerialization
  623. .data(withJSONObject: [:], options: []), // Empty dictionary literal
  624. RCNKeyDeviceContext: try! JSONSerialization.data(withJSONObject: deviceContext, options: []),
  625. RCNKeyAppContext: try! JSONSerialization.data(withJSONObject: customVariables, options: []),
  626. RCNKeySuccessFetchTime: try! JSONSerialization
  627. .data(withJSONObject: successFetchTimes, options: []),
  628. RCNKeyFailureFetchTime: try! JSONSerialization.data(
  629. withJSONObject: failureFetchTimes,
  630. options: []
  631. ),
  632. RCNKeyLastFetchStatus: RemoteConfigFetchStatus.success.rawValue,
  633. RCNKeyLastFetchError: RemoteConfigError.unknown.rawValue,
  634. RCNKeyLastApplyTime: 100.0, // Or appropriate value
  635. RCNKeyLastSetDefaultsTime: 200.0, // Or appropriate value
  636. ]
  637. }
  638. static func remoteConfigPath(forTestDatabase databaseName: String) -> String {
  639. #if os(tvOS)
  640. let dirPaths = NSSearchPathForDirectoriesInDomains(.cachesDirectory,
  641. .userDomainMask, true)
  642. #else
  643. let dirPaths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory,
  644. .userDomainMask, true)
  645. #endif
  646. let storageDirPath = dirPaths[0]
  647. let dbPath = URL(fileURLWithPath: storageDirPath)
  648. .appendingPathComponent("Google/RemoteConfig")
  649. .appendingPathComponent(databaseName).path
  650. return dbPath
  651. }
  652. private let databaseName = "RC_Test.sqlite3"
  653. }