ConfigDBManagerTest.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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. import Dispatch
  15. @testable import FirebaseRemoteConfig
  16. import XCTest
  17. class ConfigDBManagerTest: XCTestCase {
  18. let dbManager = ConfigDBManager()
  19. let bundleID = Bundle.main.bundleIdentifier!
  20. let namespace = "namespace"
  21. var filePath: String { ConfigDBManager.remoteConfigPathForDatabase() }
  22. override func setUp() {
  23. super.setUp()
  24. removeDatabase()
  25. createOrOpenDatabaseBlock()
  26. }
  27. override func tearDown() {
  28. removeDatabase()
  29. super.tearDown()
  30. }
  31. func testCreateOrOpenDBSuccess() {
  32. XCTAssertTrue(FileManager.default.fileExists(atPath: filePath))
  33. }
  34. // TODO: fix race condition in testIsNewDatabase
  35. func SKIPtestIsNewDatabase() async throws {
  36. // For a newly created DB, isNewDatabase should be true
  37. let isNew = dbManager.isNewDatabase
  38. XCTAssertTrue(isNew)
  39. }
  40. func testLoadMainTableWithBundleIdentifier() throws {
  41. let config = [
  42. "key1": "value1",
  43. "key2": "value2",
  44. ]
  45. let data = try JSONSerialization.data(withJSONObject: config, options: [])
  46. let rcValue = RemoteConfigValue(data: data, source: .remote)
  47. let namespace = "namespace"
  48. let exp = expectation(description: #function)
  49. dbManager.insertMainTable(withValues: [bundleID, namespace, "key", rcValue.dataValue],
  50. fromSource: .fetched) { success, _ in
  51. XCTAssertTrue(success)
  52. self.dbManager.loadMain(withBundleIdentifier: self.bundleID) { success, fetched, _, _, _ in
  53. XCTAssert(success)
  54. XCTAssertTrue(
  55. fetched[namespace]?["key"]?.stringValue == "{\"key1\":\"value1\",\"key2\":\"value2\"}" ||
  56. fetched[namespace]?["key"]?.stringValue == "{\"key2\":\"value2\",\"key1\":\"value1\"}"
  57. )
  58. exp.fulfill()
  59. }
  60. }
  61. waitForExpectations()
  62. }
  63. func testLoadMainTableWithBundleIdentifier_noData() {
  64. let exp = expectation(description: #function)
  65. dbManager.loadMain(withBundleIdentifier: bundleID) { success, fetched, _, _, _ in
  66. XCTAssert(success) // success should still be true.
  67. XCTAssertEqual(fetched, [:])
  68. exp.fulfill()
  69. }
  70. waitForExpectations()
  71. }
  72. func testLoadMetadataTableWithBundleIdentifier() throws {
  73. let exp = expectation(description: #function)
  74. let deviceContext = ["device": "info"]
  75. let appContext = ["app": "info"]
  76. let digestPerNamespace = ["digest": "info"]
  77. let deviceContextData = try JSONSerialization.data(withJSONObject: deviceContext, options: [])
  78. let appContextData = try JSONSerialization.data(withJSONObject: appContext, options: [])
  79. let digestPerNamespaceData = try JSONSerialization.data(withJSONObject: digestPerNamespace,
  80. options: [])
  81. let columnNameToValue = try [
  82. RCNKeyBundleIdentifier: bundleID,
  83. RCNKeyNamespace: namespace,
  84. RCNKeyFetchTime: Date().timeIntervalSince1970,
  85. RCNKeyDigestPerNamespace: digestPerNamespaceData,
  86. RCNKeyDeviceContext: deviceContextData,
  87. RCNKeyAppContext: appContextData,
  88. RCNKeySuccessFetchTime: JSONSerialization.data(withJSONObject: [], options: []),
  89. RCNKeyFailureFetchTime: JSONSerialization.data(withJSONObject: [], options: []),
  90. RCNKeyLastFetchStatus: 0,
  91. RCNKeyLastFetchError: 0,
  92. RCNKeyLastApplyTime: Date().timeIntervalSince1970,
  93. RCNKeyLastSetDefaultsTime: Date().timeIntervalSince1970,
  94. ] as [String: Any]
  95. dbManager.insertMetadataTable(withValues: columnNameToValue) { success, _ in
  96. XCTAssertTrue(success)
  97. self.dbManager.loadMetadata(withBundleIdentifier: self.bundleID,
  98. namespace: self.namespace) { result in
  99. XCTAssertEqual(result[RCNKeyBundleIdentifier] as? String, self.bundleID)
  100. exp.fulfill()
  101. }
  102. }
  103. waitForExpectations()
  104. }
  105. func testLoadMetadataTableWithBundleIdentifierAndNamespace_noData() {
  106. let exp = expectation(description: #function)
  107. dbManager.loadMetadata(withBundleIdentifier: bundleID, namespace: namespace) { result in
  108. XCTAssertTrue(result.isEmpty)
  109. exp.fulfill()
  110. }
  111. waitForExpectations()
  112. }
  113. func testUpdateMetadataTable() async throws {
  114. try await insertMetadata()
  115. let values: [Any] = [1, 1] // Fetch Failure status.
  116. let success = await dbManager.databaseActor.updateMetadataTable(withOption: .fetchStatus,
  117. namespace: namespace,
  118. values: values)
  119. XCTAssertTrue(success)
  120. let result = await dbManager.databaseActor.loadMetadataTable(
  121. withBundleIdentifier: bundleID,
  122. namespace: namespace
  123. )
  124. XCTAssertEqual(result[RCNKeyLastFetchStatus] as? Int, 1)
  125. XCTAssertEqual(result[RCNKeyLastFetchError] as? Int, 1)
  126. }
  127. func testInsertInternalMetadataTable() {
  128. let exp = expectation(description: #function)
  129. let values: [Any] = [
  130. "\(bundleID):\(namespace):fetch_timeout",
  131. "100.2".data(using: .utf8)!,
  132. ]
  133. dbManager.insertInternalMetadataTable(withValues: values) { success, _ in
  134. XCTAssert(success)
  135. self.dbManager.loadInternalMetadataTable { result in
  136. XCTAssertEqual(result.count, 1)
  137. XCTAssertEqual(String(data: result[values[0] as! String]!, encoding: .utf8), "100.2")
  138. exp.fulfill()
  139. }
  140. }
  141. waitForExpectations()
  142. }
  143. func testLoadInternalMetadataTable_noData() {
  144. let exp = expectation(description: #function)
  145. dbManager.loadInternalMetadataTable { result in
  146. XCTAssertTrue(result.isEmpty)
  147. exp.fulfill()
  148. }
  149. waitForExpectations()
  150. }
  151. func testDeleteRecordsFromMainTableForNamespace() async throws {
  152. try await insertMainTableValue()
  153. await dbManager.databaseActor.deleteRecord(fromMainTableWithNamespace: namespace,
  154. bundleIdentifier: bundleID,
  155. fromSource: .fetched)
  156. let fetched = await dbManager.databaseActor.loadMainTable(
  157. withBundleIdentifier: bundleID,
  158. fromSource: .fetched
  159. )
  160. XCTAssertNil(fetched[namespace]?["key"])
  161. }
  162. func testDeleteAllRecordsFromMetadataTable() async throws {
  163. try await insertMetadata()
  164. await dbManager.databaseActor.deleteRecord(withBundleIdentifier: bundleID, namespace: namespace)
  165. let result = await dbManager.databaseActor.loadMetadataTable(withBundleIdentifier: bundleID,
  166. namespace: namespace)
  167. XCTAssertTrue(result.isEmpty)
  168. }
  169. func testDeleteAllRecordsFromMainTable() async throws {
  170. try await insertMainTableValue()
  171. await dbManager.databaseActor.deleteAllRecords(fromTableWithSource: .fetched)
  172. let fetched = await dbManager.databaseActor.loadMainTable(
  173. withBundleIdentifier: bundleID,
  174. fromSource: .fetched
  175. )
  176. XCTAssertTrue(fetched.isEmpty)
  177. }
  178. func testInsertExperimentTable() throws {
  179. let exp = expectation(description: #function)
  180. let experiment: [String: Any?] = [
  181. "experimentId": "experiment",
  182. "variantId": "variant",
  183. "triggerEvent": "fetch",
  184. "triggerTimeoutMillis": 15000,
  185. "timeToLiveMillis": 900_000,
  186. "setRolloutId": "id",
  187. "activateEvent": "activate",
  188. "assignmentTimeoutMillis": 45000,
  189. "clearEvent": nil,
  190. ]
  191. let experimentData = try JSONSerialization.data(withJSONObject: experiment, options: [])
  192. dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyPayload,
  193. value: experimentData) { success, _ in
  194. XCTAssertTrue(success)
  195. self.dbManager.loadExperiment { success, result in
  196. let payloads = result?[ConfigConstants.experimentTableKeyPayload] as? [Data]
  197. XCTAssertEqual(payloads?.count, 1)
  198. let loadedExperiment = try! JSONSerialization.jsonObject(with: payloads![0], options: [])
  199. as! [String: Any]
  200. XCTAssertEqual(loadedExperiment["experimentId"] as? String, "experiment")
  201. XCTAssertEqual(loadedExperiment["timeToLiveMillis"] as? Int, 900_000)
  202. XCTAssertEqual(loadedExperiment["clearEvent"] as? NSNull, NSNull())
  203. exp.fulfill()
  204. }
  205. }
  206. waitForExpectations()
  207. }
  208. func testUpdateExperimentMetadata() throws {
  209. let exp = expectation(description: #function)
  210. let metadata = ["lastStartTime": 123]
  211. let metadataData = try JSONSerialization.data(withJSONObject: metadata, options: [])
  212. dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyMetadata,
  213. value: metadataData) { success, _ in
  214. XCTAssertTrue(success)
  215. let newMetadata = ["lastStartTime": 456]
  216. let newMetadataData = try! JSONSerialization.data(withJSONObject: newMetadata, options: [])
  217. self.dbManager.insertExperimentTable(withKey: ConfigConstants.experimentTableKeyMetadata,
  218. value: newMetadataData) { success, _ in
  219. XCTAssertTrue(success)
  220. self.dbManager.loadExperiment { success, result in
  221. let experimentMetadata =
  222. result?[ConfigConstants.experimentTableKeyMetadata] as? [String: Int]
  223. XCTAssertEqual(experimentMetadata?["lastStartTime"], 456)
  224. exp.fulfill()
  225. }
  226. }
  227. }
  228. waitForExpectations()
  229. }
  230. func testLoadExperiment_noData() {
  231. let exp = expectation(description: #function)
  232. dbManager.loadExperiment { success, result in
  233. XCTAssertTrue(success) // success is still true
  234. let payloads = result?[ConfigConstants.experimentTableKeyPayload] as? [Data]
  235. XCTAssertEqual(payloads, [])
  236. let metadata = result?[ConfigConstants.experimentTableKeyMetadata] as? [String: Any]
  237. XCTAssertNotNil(metadata) // metadata is initialized even if DB is empty.
  238. exp.fulfill()
  239. }
  240. waitForExpectations()
  241. }
  242. func testDeleteExperimentTableForKey() async throws {
  243. try await insertExperiment()
  244. await dbManager.databaseActor
  245. .deleteExperimentTable(forKey: ConfigConstants.experimentTableKeyPayload)
  246. let result = await dbManager.databaseActor
  247. .loadExperimentTable(fromKey: ConfigConstants.experimentTableKeyPayload)
  248. XCTAssertEqual(result, [])
  249. }
  250. func testInsertPersonalizationTable() {
  251. let exp = expectation(description: #function)
  252. let personalization: [String: Any] = [
  253. "armKey": "value",
  254. ]
  255. dbManager.insertOrUpdatePersonalizationConfig(personalization, fromSource: .fetched)
  256. dbManager.loadPersonalization { success, fetchedPersonalization, _ in
  257. XCTAssertTrue(success)
  258. XCTAssertEqual(
  259. fetchedPersonalization as? [String: String],
  260. personalization as? [String: String]
  261. )
  262. exp.fulfill()
  263. }
  264. waitForExpectations()
  265. }
  266. func testLoadPersonalization_noData() {
  267. let exp = expectation(description: #function)
  268. dbManager.loadPersonalization { success, fetchedPersonalization, _ in
  269. XCTAssertTrue(success) // success is still true
  270. XCTAssertNotNil(fetchedPersonalization) // initialized even if DB is empty
  271. exp.fulfill()
  272. }
  273. waitForExpectations()
  274. }
  275. func testInsertRolloutTable() async {
  276. let rolloutMetadata: [[String: any Sendable]] = [[
  277. "rolloutId": "id",
  278. "variantId": "variant",
  279. "affectedParameterKeys": ["key1", "key2"],
  280. ]]
  281. let success = await dbManager.databaseActor.insertOrUpdateRolloutTable(
  282. withKey: ConfigConstants.rolloutTableKeyFetchedMetadata,
  283. value: rolloutMetadata
  284. )
  285. XCTAssertTrue(success)
  286. let activeMetadata =
  287. await dbManager.databaseActor.loadRolloutTable(
  288. fromKey: ConfigConstants.rolloutTableKeyFetchedMetadata
  289. )
  290. let metadata = activeMetadata[0]
  291. let rolloutId = metadata["rolloutId"] as? String
  292. XCTAssertEqual(rolloutId, "id")
  293. let param2 = (metadata["affectedParameterKeys"] as? [String])?[1]
  294. XCTAssertEqual(param2, "key2")
  295. }
  296. func testLoadRolloutMetadata_noData() async {
  297. let activeMetadata =
  298. await dbManager.databaseActor
  299. .loadRolloutTable(fromKey: ConfigConstants.rolloutTableKeyActiveMetadata)
  300. XCTAssertNotNil(activeMetadata) // initialized even if DB is empty
  301. let fetchedMetadata =
  302. await dbManager.databaseActor.loadRolloutTable(
  303. fromKey: ConfigConstants.rolloutTableKeyFetchedMetadata
  304. )
  305. XCTAssertNotNil(fetchedMetadata) // initialized even if DB is empty
  306. }
  307. // MARK: - Helpers
  308. func removeDatabase() {
  309. try? FileManager.default.removeItem(atPath: filePath)
  310. }
  311. func insertMetadata() async throws {
  312. let deviceContextData = try JSONSerialization.data(withJSONObject: [:], options: [])
  313. let appContextData = try JSONSerialization.data(withJSONObject: [:], options: [])
  314. let digestPerNamespaceData = try JSONSerialization.data(withJSONObject: [:], options: [])
  315. let columnNameToValue = try [
  316. RCNKeyBundleIdentifier: bundleID,
  317. RCNKeyNamespace: namespace,
  318. RCNKeyFetchTime: Date().timeIntervalSince1970,
  319. RCNKeyDigestPerNamespace: digestPerNamespaceData,
  320. RCNKeyDeviceContext: deviceContextData,
  321. RCNKeyAppContext: appContextData,
  322. RCNKeySuccessFetchTime: JSONSerialization.data(withJSONObject: [], options: []),
  323. RCNKeyFailureFetchTime: JSONSerialization.data(withJSONObject: [], options: []),
  324. RCNKeyLastFetchStatus: 0,
  325. RCNKeyLastFetchError: 0,
  326. RCNKeyLastApplyTime: Date().timeIntervalSince1970,
  327. RCNKeyLastSetDefaultsTime: Date().timeIntervalSince1970,
  328. ] as [String: Any]
  329. let success = await dbManager.databaseActor.insertMetadataTable(withValues: columnNameToValue)
  330. XCTAssertTrue(success)
  331. }
  332. private func insertMainTableValue() async throws {
  333. let config = [
  334. "key1": "value1",
  335. "key2": "value2",
  336. ]
  337. let data = try JSONSerialization.data(withJSONObject: config, options: [])
  338. let rcValue = RemoteConfigValue(data: data, source: .remote)
  339. let success = await dbManager.databaseActor.insertMainTable(
  340. withValues: [bundleID, namespace, "key", rcValue.dataValue],
  341. fromSource: .fetched
  342. )
  343. XCTAssertTrue(success)
  344. }
  345. private func insertExperiment() async throws {
  346. let experiment: [String: Any] = [
  347. "experimentId": "experiment",
  348. ]
  349. let experimentData = try JSONSerialization.data(withJSONObject: experiment, options: [])
  350. let success = await dbManager.databaseActor.insertExperimentTable(
  351. withKey: ConfigConstants.experimentTableKeyPayload,
  352. value: experimentData
  353. )
  354. XCTAssertTrue(success)
  355. }
  356. private func createOrOpenDatabaseBlock() {
  357. let semaphore = DispatchSemaphore(value: 0)
  358. Task {
  359. await dbManager.databaseActor.createOrOpenDatabase()
  360. semaphore.signal()
  361. }
  362. semaphore.wait()
  363. }
  364. private func waitForExpectations() {
  365. waitForExpectations(timeout: 5.0)
  366. }
  367. }