RCNConfigDBManagerTest.m 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import <sqlite3.h>
  19. @import FirebaseRemoteConfig;
  20. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  21. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  22. #define RCNExperimentTableKeyPayload "experiment_payload"
  23. #define RCNExperimentTableKeyMetadata "experiment_metadata"
  24. #define RCNExperimentTableKeyActivePayload "experiment_active_payload"
  25. #define RCNRolloutTableKeyActiveMetadata "active_rollout_metadata"
  26. #define RCNRolloutTableKeyFetchedMetadata "fetched_rollout_metadata"
  27. typedef void (^RCNDBCompletion)(BOOL success, NSDictionary *result);
  28. typedef void (^RCNDBDictCompletion)(NSDictionary *result);
  29. @interface RCNConfigDBManager (Test)
  30. - (void)insertExperimentTableWithKey:(NSString *)key
  31. value:(NSData *)serializedValue
  32. completionHandler:(RCNDBCompletion)handler;
  33. - (void)deleteExperimentTableForKey:(NSString *)key;
  34. - (void)createOrOpenDatabase;
  35. @end
  36. @interface RCNConfigDBManagerTest : XCTestCase {
  37. NSString *_DBPath;
  38. }
  39. @property(nonatomic, strong) RCNConfigDBManager *DBManager;
  40. @property(nonatomic, assign) NSTimeInterval expectionTimeout;
  41. @end
  42. @implementation RCNConfigDBManagerTest
  43. - (void)setUp {
  44. [super setUp];
  45. _DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
  46. _expectionTimeout = 10.0;
  47. _DBManager = [[RCNConfigDBManager alloc] initWithDbPath:_DBPath];
  48. }
  49. - (void)tearDown {
  50. [_DBManager removeDatabaseWithPath:_DBPath];
  51. }
  52. - (void)testV1NamespaceMigrationToV2Namespace {
  53. // Write v1 namespace.
  54. XCTestExpectation *loadConfigContentExpectation =
  55. [self expectationWithDescription:@"test v1 namespace migration to v2 namespace"];
  56. NSString *namespace_p = @"testNamespace";
  57. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  58. __block int count = 0;
  59. for (int i = 0; i <= 100; ++i) {
  60. // Check namespace is updated after database write is completed.
  61. RCNDBCompletion insertCompletion = ^void(BOOL success,
  62. NSDictionary<NSString *, NSString *> *result) {
  63. count++;
  64. XCTAssertTrue(success);
  65. if (count == 100) {
  66. // Migrate to the new namespace.
  67. [self->_DBManager createOrOpenDatabase];
  68. [self->_DBManager
  69. loadMainWithBundleIdentifier:bundleIdentifier
  70. completionHandler:^(
  71. BOOL loadSuccess,
  72. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *fetchedConfig,
  73. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *activeConfig,
  74. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *defaultConfig,
  75. NSDictionary *unusedRolloutMetadata) {
  76. XCTAssertTrue(loadSuccess);
  77. NSString *fullyQualifiedNamespace =
  78. [NSString stringWithFormat:@"%@:%@", namespace_p, kFIRDefaultAppName];
  79. XCTAssertNotNil(fetchedConfig[fullyQualifiedNamespace]);
  80. XCTAssertEqual([fetchedConfig[fullyQualifiedNamespace] count], 101U);
  81. XCTAssertEqual([fetchedConfig[namespace_p] count], 0);
  82. if (loadSuccess) {
  83. [loadConfigContentExpectation fulfill];
  84. }
  85. }];
  86. }
  87. };
  88. NSString *value = [NSString stringWithFormat:@"value%d", i];
  89. NSString *key = [NSString stringWithFormat:@"key%d", i];
  90. NSArray<id> *values =
  91. @[ bundleIdentifier, namespace_p, key, [value dataUsingEncoding:NSUTF8StringEncoding] ];
  92. [_DBManager insertMainTableWithValues:values
  93. fromSource:RCNDBSourceFetched
  94. completionHandler:insertCompletion];
  95. }
  96. [self waitForExpectationsWithTimeout:_expectionTimeout
  97. handler:^(NSError *error) {
  98. XCTAssertNil(error);
  99. }];
  100. }
  101. - (void)testWriteAndLoadMainTableResult {
  102. XCTestExpectation *loadConfigContentExpectation =
  103. [self expectationWithDescription:@"Write and read metadata in database serializedly"];
  104. NSString *namespace_p = @"namespace_1";
  105. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  106. __block int count = 0;
  107. for (int i = 0; i <= 100; ++i) {
  108. // check DB write correctly
  109. RCNDBCompletion insertCompletion = ^void(BOOL success, NSDictionary *result) {
  110. count++;
  111. XCTAssertTrue(success);
  112. if (count == 100) {
  113. // check DB read correctly
  114. [self->_DBManager
  115. loadMainWithBundleIdentifier:bundleIdentifier
  116. completionHandler:^(
  117. BOOL loadSuccess,
  118. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *fetchedConfig,
  119. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *activeConfig,
  120. NSDictionary<NSString *, NSDictionary<NSString *, id> *> *defaultConfig,
  121. NSDictionary *unusedRolloutMetadata) {
  122. NSMutableDictionary *res = [fetchedConfig mutableCopy];
  123. XCTAssertTrue(success);
  124. FIRRemoteConfigValue *value = res[namespace_p][@"key100"];
  125. XCTAssertEqualObjects(value.stringValue, @"value100");
  126. if (success) {
  127. [loadConfigContentExpectation fulfill];
  128. }
  129. }];
  130. }
  131. };
  132. NSString *value = [NSString stringWithFormat:@"value%d", i];
  133. NSString *key = [NSString stringWithFormat:@"key%d", i];
  134. NSArray *values =
  135. @[ bundleIdentifier, namespace_p, key, [value dataUsingEncoding:NSUTF8StringEncoding] ];
  136. [_DBManager insertMainTableWithValues:values
  137. fromSource:RCNDBSourceFetched
  138. completionHandler:insertCompletion];
  139. }
  140. [self waitForExpectationsWithTimeout:_expectionTimeout
  141. handler:^(NSError *error) {
  142. XCTAssertNil(error);
  143. }];
  144. }
  145. /// Column names in metadata table
  146. static NSString *const RCNKeyBundleIdentifier = @"bundle_identifier";
  147. static NSString *const RCNKeyNamespace = @"namespace";
  148. static NSString *const RCNKeyFetchTime = @"fetch_time";
  149. static NSString *const RCNKeyDigestPerNamespace = @"digest_per_ns";
  150. static NSString *const RCNKeyDeviceContext = @"device_context";
  151. static NSString *const RCNKeyAppContext = @"app_context";
  152. static NSString *const RCNKeySuccessFetchTime = @"success_fetch_time";
  153. static NSString *const RCNKeyFailureFetchTime = @"failure_fetch_time";
  154. static NSString *const RCNKeyLastFetchStatus = @"last_fetch_status";
  155. static NSString *const RCNKeyLastFetchError = @"last_fetch_error";
  156. static NSString *const RCNKeyLastApplyTime = @"last_apply_time";
  157. static NSString *const RCNKeyLastSetDefaultsTime = @"last_set_defaults_time";
  158. - (void)testWriteAndLoadMetadataResult {
  159. XCTestExpectation *writeAndLoadMetadataExpectation =
  160. [self expectationWithDescription:@"Write and load metadata in database successfully"];
  161. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  162. NSString *namespace = @"test_namespace";
  163. NSTimeInterval lastFetchTimestamp = [NSDate date].timeIntervalSince1970;
  164. NSDictionary *deviceContext =
  165. @{@"app_version" : @"1.0.1", @"app_build" : @"1.0.1.11", @"os_version" : @"iOS9.1"};
  166. NSDictionary *syncedDBCustomVariables = @{@"user_level" : @15, @"user_experiences" : @"2468"};
  167. NSArray *successFetchTimes = @[];
  168. NSTimeInterval now = [NSDate date].timeIntervalSince1970;
  169. NSArray *failureFetchTimes =
  170. @[ [NSNumber numberWithDouble:now - 200], [NSNumber numberWithDouble:now] ];
  171. // serialize objects
  172. NSError *error;
  173. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  174. options:NSJSONWritingPrettyPrinted
  175. error:&error];
  176. NSData *serializedDeviceContext =
  177. [NSJSONSerialization dataWithJSONObject:deviceContext
  178. options:NSJSONWritingPrettyPrinted
  179. error:&error];
  180. NSData *serializedDigestPerNamespace =
  181. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  182. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  183. options:NSJSONWritingPrettyPrinted
  184. error:&error];
  185. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  186. options:NSJSONWritingPrettyPrinted
  187. error:&error];
  188. NSDictionary *columnNameToValue = @{
  189. RCNKeyBundleIdentifier : bundleIdentifier,
  190. RCNKeyNamespace : namespace,
  191. RCNKeyFetchTime : @(lastFetchTimestamp),
  192. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  193. RCNKeyDeviceContext : serializedDeviceContext,
  194. RCNKeyAppContext : serializedAppContext,
  195. RCNKeySuccessFetchTime : serializedSuccessTime,
  196. RCNKeyFailureFetchTime : serializedFailureTime,
  197. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  198. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  199. RCNKeyLastApplyTime : @(now - 100),
  200. RCNKeyLastSetDefaultsTime : @(now - 200)
  201. };
  202. RCNDBCompletion completion = ^(BOOL success, NSDictionary *result1) {
  203. [self->_DBManager
  204. loadMetadataWithBundleIdentifier:bundleIdentifier
  205. namespace:namespace
  206. completionHandler:^(NSDictionary<NSString *, id> *_Nonnull result) {
  207. XCTAssertNotNil(result);
  208. XCTAssertEqualObjects(result[RCNKeyBundleIdentifier], bundleIdentifier);
  209. XCTAssertEqual([result[RCNKeyFetchTime] doubleValue], lastFetchTimestamp);
  210. XCTAssertEqualObjects([result[RCNKeyDigestPerNamespace] copy], @{});
  211. XCTAssertEqualObjects([result[RCNKeyDeviceContext] copy], deviceContext);
  212. XCTAssertEqualObjects([result[RCNKeyAppContext] copy],
  213. syncedDBCustomVariables);
  214. XCTAssertEqualObjects([result[RCNKeySuccessFetchTime] copy],
  215. successFetchTimes);
  216. XCTAssertTrue([[result[RCNKeyFailureFetchTime] copy]
  217. isEqualToArray:failureFetchTimes]);
  218. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  219. (int)FIRRemoteConfigFetchStatusSuccess);
  220. XCTAssertEqual([result[RCNKeyLastFetchError] intValue],
  221. (int)FIRRemoteConfigErrorUnknown);
  222. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], now - 100);
  223. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], now - 200);
  224. [writeAndLoadMetadataExpectation fulfill];
  225. }];
  226. };
  227. [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:completion];
  228. [self waitForExpectationsWithTimeout:_expectionTimeout
  229. handler:^(NSError *error) {
  230. XCTAssertNil(error);
  231. }];
  232. }
  233. - (void)testWriteAndLoadMetadataForMultipleNamespaces {
  234. XCTestExpectation *writeAndLoadMetadataForNamespace1Expectation =
  235. [self expectationWithDescription:@"Metadata is stored and read based on namespace1"];
  236. XCTestExpectation *writeAndLoadMetadataForNamespace2Expectation =
  237. [self expectationWithDescription:@"Metadata is stored and read based on namespace2"];
  238. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  239. NSDictionary *deviceContext = @{};
  240. NSDictionary *syncedDBCustomVariables = @{};
  241. NSError *error;
  242. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  243. options:NSJSONWritingPrettyPrinted
  244. error:&error];
  245. NSData *serializedDeviceContext =
  246. [NSJSONSerialization dataWithJSONObject:deviceContext
  247. options:NSJSONWritingPrettyPrinted
  248. error:&error];
  249. NSData *serializedDigestPerNamespace =
  250. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  251. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:@[]
  252. options:NSJSONWritingPrettyPrinted
  253. error:&error];
  254. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:@[]
  255. options:NSJSONWritingPrettyPrinted
  256. error:&error];
  257. // Metadata for first namespace
  258. NSString *namespace = @"test_namespace";
  259. double lastApplyTime = 100;
  260. double lastSetDefaultsTime = 200;
  261. NSDictionary *valuesForNamespace = @{
  262. RCNKeyBundleIdentifier : bundleIdentifier,
  263. RCNKeyNamespace : namespace,
  264. RCNKeyFetchTime : @(0),
  265. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  266. RCNKeyDeviceContext : serializedDeviceContext,
  267. RCNKeyAppContext : serializedAppContext,
  268. RCNKeySuccessFetchTime : serializedSuccessTime,
  269. RCNKeyFailureFetchTime : serializedFailureTime,
  270. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  271. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  272. RCNKeyLastApplyTime : @(lastApplyTime),
  273. RCNKeyLastSetDefaultsTime : @(lastSetDefaultsTime)
  274. };
  275. // Metadata for second namespace
  276. NSString *namespace2 = @"test_namespace_2";
  277. double lastApplyTime2 = 300;
  278. double lastSetDefaultsTime2 = 400;
  279. NSDictionary *valuesForNamespace2 = @{
  280. RCNKeyBundleIdentifier : bundleIdentifier,
  281. RCNKeyNamespace : namespace2,
  282. RCNKeyFetchTime : @(0),
  283. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  284. RCNKeyDeviceContext : serializedDeviceContext,
  285. RCNKeyAppContext : serializedAppContext,
  286. RCNKeySuccessFetchTime : serializedSuccessTime,
  287. RCNKeyFailureFetchTime : serializedFailureTime,
  288. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  289. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  290. RCNKeyLastApplyTime : @(lastApplyTime2),
  291. RCNKeyLastSetDefaultsTime : @(lastSetDefaultsTime2)
  292. };
  293. RCNDBCompletion insertMetadataCompletion = ^void(BOOL success, NSDictionary *result) {
  294. XCTAssertTrue(success);
  295. // Load metadata for both namespaces and verify they retain their separate values
  296. [self->_DBManager
  297. loadMetadataWithBundleIdentifier:bundleIdentifier
  298. namespace:namespace
  299. completionHandler:^(
  300. NSDictionary<NSString *, id> *_Nonnull resultForNamespace) {
  301. XCTAssertNotNil(resultForNamespace);
  302. XCTAssertEqual([resultForNamespace[RCNKeyLastApplyTime] doubleValue],
  303. lastApplyTime);
  304. XCTAssertEqual([resultForNamespace[RCNKeyLastSetDefaultsTime] doubleValue],
  305. lastSetDefaultsTime);
  306. [writeAndLoadMetadataForNamespace1Expectation fulfill];
  307. }];
  308. [self->_DBManager
  309. loadMetadataWithBundleIdentifier:bundleIdentifier
  310. namespace:namespace2
  311. completionHandler:^(
  312. NSDictionary<NSString *, id> *_Nonnull resultForNamespace2) {
  313. XCTAssertNotNil(resultForNamespace2);
  314. XCTAssertEqual([resultForNamespace2[RCNKeyLastApplyTime] doubleValue],
  315. lastApplyTime2);
  316. XCTAssertEqual(
  317. [resultForNamespace2[RCNKeyLastSetDefaultsTime] doubleValue],
  318. lastSetDefaultsTime2);
  319. [writeAndLoadMetadataForNamespace2Expectation fulfill];
  320. }];
  321. };
  322. // Write metadata for first namespace
  323. [_DBManager insertMetadataTableWithValues:valuesForNamespace
  324. completionHandler:^(BOOL success, NSDictionary *result1) {
  325. XCTAssertTrue(success);
  326. // Write metadata for second namespace
  327. [self->_DBManager
  328. insertMetadataTableWithValues:valuesForNamespace2
  329. completionHandler:insertMetadataCompletion];
  330. }];
  331. [self waitForExpectationsWithTimeout:_expectionTimeout
  332. handler:^(NSError *error) {
  333. XCTAssertNil(error);
  334. }];
  335. }
  336. // Create a key each for two namespaces, delete it from one namespace, read both namespaces.
  337. - (void)testDeleteParamAndLoadMainTable {
  338. XCTestExpectation *namespaceDeleteExpectation =
  339. [self expectationWithDescription:@"Contents of 'namespace_delete' should be deleted."];
  340. XCTestExpectation *namespaceKeepExpectation =
  341. [self expectationWithDescription:@"Write a key to namespace_keep and read back again."];
  342. NSString *namespaceToDelete = @"namespace_delete";
  343. NSString *namespaceToKeep = @"namespace_keep";
  344. NSString *bundleIdentifier = @"testBundleID";
  345. // Write something to the database for both namespaces.
  346. // Completion handler for the write to namespace_delete namespace.
  347. RCNDBCompletion insertNamespace1Completion = ^void(BOOL success, NSDictionary *result) {
  348. XCTAssertTrue(success);
  349. // Delete the key for given namespace.
  350. [self->_DBManager deleteRecordFromMainTableWithNamespace:namespaceToDelete
  351. bundleIdentifier:bundleIdentifier
  352. fromSource:RCNDBSourceActive];
  353. // Read from the database and verify expected values.
  354. [self->_DBManager
  355. loadMainWithBundleIdentifier:bundleIdentifier
  356. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  357. NSDictionary *activeConfig, NSDictionary *defaultConfig,
  358. NSDictionary *unusedRolloutMetadata) {
  359. NSMutableDictionary *res = [activeConfig mutableCopy];
  360. XCTAssertTrue(success);
  361. FIRRemoteConfigValue *value = res[namespaceToDelete][@"keyToDelete"];
  362. XCTAssertNil(value);
  363. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  364. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  365. [namespaceDeleteExpectation fulfill];
  366. }];
  367. };
  368. // Insert a key into the second namespace.
  369. RCNDBCompletion insertNamespace2Completion = ^void(BOOL success, NSDictionary *result) {
  370. XCTAssertTrue(success);
  371. // Ensure DB read succeeds.
  372. [self->_DBManager
  373. loadMainWithBundleIdentifier:bundleIdentifier
  374. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  375. NSDictionary *activeConfig, NSDictionary *defaultConfig,
  376. NSDictionary *unusedRolloutMetadata) {
  377. NSMutableDictionary *res = [activeConfig mutableCopy];
  378. XCTAssertTrue(success);
  379. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  380. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  381. [namespaceKeepExpectation fulfill];
  382. }];
  383. };
  384. // We will delete this key after storing in the database.
  385. NSString *valueToDelete = @"valueToDelete";
  386. NSString *keyToDelete = @"keyToDelete";
  387. NSArray *items = @[
  388. bundleIdentifier, namespaceToDelete, keyToDelete,
  389. [valueToDelete dataUsingEncoding:NSUTF8StringEncoding]
  390. ];
  391. [_DBManager insertMainTableWithValues:items
  392. fromSource:RCNDBSourceActive
  393. completionHandler:insertNamespace1Completion];
  394. // This key value will be retained.
  395. NSString *valueToRetain = @"valueToRetain";
  396. NSString *keyToRetain = @"keyToRetain";
  397. NSArray *items2 = @[
  398. bundleIdentifier, namespaceToKeep, keyToRetain,
  399. [valueToRetain dataUsingEncoding:NSUTF8StringEncoding]
  400. ];
  401. [_DBManager insertMainTableWithValues:items2
  402. fromSource:RCNDBSourceActive
  403. completionHandler:insertNamespace2Completion];
  404. [self waitForExpectationsWithTimeout:_expectionTimeout
  405. handler:^(NSError *error) {
  406. XCTAssertNil(error);
  407. }];
  408. }
  409. - (void)testWriteAndLoadExperiments {
  410. XCTestExpectation *updateAndLoadExperimentExpectation =
  411. [self expectationWithDescription:@"Update and load experiment in database successfully"];
  412. NSError *error;
  413. NSArray *payload2 = @[ @"ab", @"cd" ];
  414. NSData *payloadData2 = [NSJSONSerialization dataWithJSONObject:payload2
  415. options:NSJSONWritingPrettyPrinted
  416. error:&error];
  417. NSDictionary *payload3 =
  418. @{@"experiment_ID" : @"35667", @"experiment_activate_name" : @"activate_game"};
  419. NSData *payloadData3 = [NSJSONSerialization dataWithJSONObject:payload3
  420. options:NSJSONWritingPrettyPrinted
  421. error:&error];
  422. NSArray *payloads = @[ [[NSData alloc] init], payloadData2, payloadData3 ];
  423. RCNDBCompletion writePayloadCompletion = ^(BOOL success, NSDictionary *result) {
  424. NSDictionary *metadata =
  425. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  426. XCTAssertTrue(success);
  427. RCNDBCompletion writeMetadataCompletion = ^(BOOL success, NSDictionary *result) {
  428. XCTAssertTrue(success);
  429. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  430. XCTAssertTrue(success);
  431. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  432. // TODO: sort order
  433. // XCTAssertEqualObjects(payloads, experimentResults[@RCNExperimentTableKeyPayload]);
  434. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  435. XCTAssertEqualWithAccuracy(
  436. -11,
  437. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"]
  438. doubleValue],
  439. 1.0);
  440. XCTAssertEqualObjects(
  441. @"wonderful",
  442. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  443. [updateAndLoadExperimentExpectation fulfill];
  444. };
  445. [self->_DBManager loadExperimentWithCompletionHandler:readCompletion];
  446. };
  447. NSError *error;
  448. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  449. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  450. options:NSJSONWritingPrettyPrinted
  451. error:&error];
  452. [self->_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  453. value:serializedMetadata
  454. completionHandler:writeMetadataCompletion];
  455. };
  456. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  457. value:[[NSData alloc] init]
  458. completionHandler:nil];
  459. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  460. value:payloadData2
  461. completionHandler:nil];
  462. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  463. value:payloadData3
  464. completionHandler:writePayloadCompletion];
  465. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  466. }
  467. - (void)testWriteAndLoadActivatedExperiments {
  468. XCTestExpectation *updateAndLoadExperimentExpectation =
  469. [self expectationWithDescription:@"Update and load experiment in database successfully"];
  470. NSError *error;
  471. NSArray *payload2 = @[ @"ab", @"cd" ];
  472. NSData *payloadData2 = [NSJSONSerialization dataWithJSONObject:payload2
  473. options:NSJSONWritingPrettyPrinted
  474. error:&error];
  475. NSDictionary *payload3 =
  476. @{@"experiment_ID" : @"35667", @"experiment_activate_name" : @"activate_game"};
  477. NSData *payloadData3 = [NSJSONSerialization dataWithJSONObject:payload3
  478. options:NSJSONWritingPrettyPrinted
  479. error:&error];
  480. NSArray *payloads = @[ [[NSData alloc] init], payloadData2, payloadData3 ];
  481. RCNDBCompletion writePayloadCompletion = ^(BOOL success, NSDictionary *result) {
  482. XCTAssertTrue(success);
  483. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  484. XCTAssertTrue(success);
  485. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyActivePayload]);
  486. // TODO: Add sort when implementing in Swift to address flaky array order.
  487. // XCTAssertEqualObjects(payloads, experimentResults[@RCNExperimentTableKeyActivePayload]);
  488. [updateAndLoadExperimentExpectation fulfill];
  489. };
  490. [self->_DBManager loadExperimentWithCompletionHandler:readCompletion];
  491. };
  492. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  493. value:[[NSData alloc] init]
  494. completionHandler:nil];
  495. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  496. value:payloadData2
  497. completionHandler:nil];
  498. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  499. value:payloadData3
  500. completionHandler:writePayloadCompletion];
  501. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  502. }
  503. typedef void (^RCNDBLoadCompletion)(BOOL success,
  504. NSDictionary *fetchedConfig,
  505. NSDictionary *activeConfig,
  506. NSDictionary *defaultConfig,
  507. NSDictionary *rolloutMetadata);
  508. - (void)testWriteAndLoadMetadataMultipleTimes {
  509. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  510. expectationWithDescription:@"Update and load experiment metadata in database successfully"];
  511. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  512. XCTAssertTrue(success);
  513. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  514. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  515. XCTAssertEqualWithAccuracy(
  516. 12345678,
  517. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"] doubleValue],
  518. 1.0);
  519. XCTAssertEqualObjects(
  520. @"wonderful",
  521. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  522. [updateAndLoadMetadataExpectation fulfill];
  523. };
  524. NSDictionary *metadata =
  525. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  526. NSError *error;
  527. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  528. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  529. options:NSJSONWritingPrettyPrinted
  530. error:&error];
  531. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  532. value:serializedMetadata
  533. completionHandler:nil];
  534. metadata = @{@"last_known_start_time" : @(12345678), @"experiment_new_metadata" : @"wonderful"};
  535. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  536. serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  537. options:NSJSONWritingPrettyPrinted
  538. error:&error];
  539. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  540. value:serializedMetadata
  541. completionHandler:nil];
  542. [_DBManager loadExperimentWithCompletionHandler:readCompletion];
  543. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  544. }
  545. - (void)testWriteAndLoadFetchedAndActiveRollout {
  546. XCTestExpectation *writeAndLoadFetchedRolloutExpectation =
  547. [self expectationWithDescription:@"Write and load rollout in database successfully"];
  548. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  549. NSArray *fetchedRollout = @[
  550. @{
  551. @"rollout_id" : @"1",
  552. @"variant_id" : @"B",
  553. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  554. },
  555. @{
  556. @"rollout_id" : @"2",
  557. @"variant_id" : @"1",
  558. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  559. }
  560. ];
  561. NSArray *activeRollout = @[
  562. @{
  563. @"rollout_id" : @"1",
  564. @"variant_id" : @"B",
  565. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  566. },
  567. @{
  568. @"rollout_id" : @"3",
  569. @"variant_id" : @"a",
  570. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  571. }
  572. ];
  573. RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) {
  574. XCTAssertTrue(success);
  575. RCNDBLoadCompletion loadCompletion = ^(
  576. BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  577. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  578. XCTAssertTrue(success);
  579. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  580. XCTAssertEqualObjects(fetchedRollout, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  581. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  582. XCTAssertEqualObjects(activeRollout, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  583. [writeAndLoadFetchedRolloutExpectation fulfill];
  584. };
  585. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier
  586. completionHandler:loadCompletion];
  587. };
  588. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  589. value:fetchedRollout
  590. completionHandler:nil];
  591. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
  592. value:activeRollout
  593. completionHandler:writeRolloutCompletion];
  594. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  595. }
  596. - (void)testUpdateAndLoadRollout {
  597. XCTestExpectation *updateAndLoadFetchedRolloutExpectation =
  598. [self expectationWithDescription:@"Update and load rollout in database successfully"];
  599. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  600. NSArray *fetchedRollout = @[ @{
  601. @"rollout_id" : @"1",
  602. @"variant_id" : @"B",
  603. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  604. } ];
  605. NSArray *updatedFetchedRollout = @[
  606. @{
  607. @"rollout_id" : @"1",
  608. @"variant_id" : @"B",
  609. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  610. },
  611. @{
  612. @"rollout_id" : @"2",
  613. @"variant_id" : @"1",
  614. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  615. }
  616. ];
  617. RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) {
  618. XCTAssertTrue(success);
  619. RCNDBLoadCompletion loadCompletion =
  620. ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  621. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  622. XCTAssertTrue(success);
  623. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  624. XCTAssertEqualObjects(updatedFetchedRollout,
  625. rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  626. [updateAndLoadFetchedRolloutExpectation fulfill];
  627. };
  628. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier
  629. completionHandler:loadCompletion];
  630. };
  631. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  632. value:fetchedRollout
  633. completionHandler:nil];
  634. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  635. value:updatedFetchedRollout
  636. completionHandler:writeRolloutCompletion];
  637. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  638. }
  639. - (void)testLoadEmptyRollout {
  640. XCTestExpectation *updateAndLoadFetchedRolloutExpectation =
  641. [self expectationWithDescription:@"Load empty rollout in database successfully"];
  642. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  643. NSArray *emptyResult = [[NSArray alloc] init];
  644. RCNDBLoadCompletion loadCompletion =
  645. ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  646. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  647. XCTAssertTrue(success);
  648. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  649. XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  650. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  651. XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  652. [updateAndLoadFetchedRolloutExpectation fulfill];
  653. };
  654. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier completionHandler:loadCompletion];
  655. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  656. }
  657. - (void)testUpdateAndloadLastFetchStatus {
  658. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  659. expectationWithDescription:@"Update and load last fetch status in database successfully."];
  660. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  661. NSString *namespace = @"test_namespace";
  662. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  663. [self->_DBManager
  664. loadMetadataWithBundleIdentifier:bundleIdentifier
  665. namespace:namespace
  666. completionHandler:^(NSDictionary<NSString *, id> *_Nonnull result) {
  667. XCTAssertNotNil(result);
  668. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  669. (int)FIRRemoteConfigFetchStatusSuccess);
  670. XCTAssertEqual([result[RCNKeyLastFetchError] intValue],
  671. (int)FIRRemoteConfigErrorUnknown);
  672. RCNDBCompletion updateMetadataCompletion = ^(BOOL success,
  673. NSDictionary *updateResult) {
  674. [self->_DBManager
  675. loadMetadataWithBundleIdentifier:bundleIdentifier
  676. namespace:namespace
  677. completionHandler:^(
  678. NSDictionary<NSString *, id> *_Nonnull result) {
  679. XCTAssertTrue(success);
  680. XCTAssertNotNil(result);
  681. XCTAssertEqual(
  682. [result[RCNKeyLastFetchStatus] intValue],
  683. (int)FIRRemoteConfigFetchStatusThrottled);
  684. XCTAssertEqual(
  685. [result[RCNKeyLastFetchError] intValue],
  686. (int)FIRRemoteConfigErrorThrottled);
  687. [updateAndLoadMetadataExpectation fulfill];
  688. }];
  689. };
  690. // Update with throttle status.
  691. [self->_DBManager
  692. updateMetadataWithOption:UpdateOptionFetchStatus
  693. namespace:namespace
  694. values:@[
  695. @(FIRRemoteConfigFetchStatusThrottled),
  696. @(FIRRemoteConfigErrorThrottled)
  697. ]
  698. completionHandler:updateMetadataCompletion];
  699. }];
  700. };
  701. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  702. completionHandler:createMetadataCompletion];
  703. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  704. }
  705. /// Tests that we can insert values in the database and can update them.
  706. - (void)testInsertAndUpdateApplyTime {
  707. XCTestExpectation *updateAndLoadMetadataExpectation =
  708. [self expectationWithDescription:@"Update and load apply time in database successfully."];
  709. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  710. NSString *namespace = @"test_namespace";
  711. NSTimeInterval lastApplyTimestamp = [NSDate date].timeIntervalSince1970;
  712. // Metadata row must exist before update
  713. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  714. XCTAssertTrue(success);
  715. // Read newly created metadata.
  716. [self->_DBManager
  717. loadMetadataWithBundleIdentifier:bundleIdentifier
  718. namespace:namespace
  719. completionHandler:^(NSDictionary<NSString *, id> *_Nonnull result) {
  720. XCTAssertNotNil(result);
  721. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], (double)100);
  722. RCNDBCompletion updateMetadataCompletion = ^(BOOL success,
  723. NSDictionary *updateResult) {
  724. [self->_DBManager
  725. loadMetadataWithBundleIdentifier:bundleIdentifier
  726. namespace:namespace
  727. completionHandler:^(
  728. NSDictionary<NSString *, id> *_Nonnull result) {
  729. XCTAssertTrue(success);
  730. XCTAssertNotNil(result);
  731. XCTAssertEqual(
  732. [result[RCNKeyLastApplyTime] doubleValue],
  733. lastApplyTimestamp);
  734. [updateAndLoadMetadataExpectation fulfill];
  735. }];
  736. };
  737. // Update apply config timestamp.
  738. [self->_DBManager updateMetadataWithOption:UpdateOptionApplyTime
  739. namespace:namespace
  740. values:@[ @(lastApplyTimestamp) ]
  741. completionHandler:updateMetadataCompletion];
  742. }];
  743. };
  744. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  745. completionHandler:createMetadataCompletion];
  746. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  747. }
  748. - (void)testUpdateAndLoadSetDefaultsTime {
  749. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  750. expectationWithDescription:@"Update and load set defaults time in database successfully."];
  751. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  752. NSString *namespace = @"test_namespace";
  753. NSTimeInterval lastSetDefaultsTimestamp = [NSDate date].timeIntervalSince1970;
  754. // Metadata row must exist before update
  755. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  756. [self->_DBManager
  757. loadMetadataWithBundleIdentifier:bundleIdentifier
  758. namespace:namespace
  759. completionHandler:^(NSDictionary<NSString *, id> *_Nonnull result) {
  760. XCTAssertTrue(success);
  761. XCTAssertNotNil(result);
  762. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue],
  763. (double)200);
  764. RCNDBCompletion updateMetadataCompletion = ^(BOOL success,
  765. NSDictionary *updateResult) {
  766. [self->_DBManager
  767. loadMetadataWithBundleIdentifier:bundleIdentifier
  768. namespace:namespace
  769. completionHandler:^(
  770. NSDictionary<NSString *, id> *_Nonnull result) {
  771. XCTAssertTrue(success);
  772. XCTAssertNotNil(result);
  773. XCTAssertEqual(
  774. [result[RCNKeyLastSetDefaultsTime] doubleValue],
  775. lastSetDefaultsTimestamp);
  776. [updateAndLoadMetadataExpectation fulfill];
  777. }];
  778. };
  779. // Update setting default config timestamp.
  780. [self->_DBManager updateMetadataWithOption:UpdateOptionDefaultTime
  781. namespace:namespace
  782. values:@[ @(lastSetDefaultsTimestamp) ]
  783. completionHandler:updateMetadataCompletion];
  784. }];
  785. };
  786. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  787. completionHandler:createMetadataCompletion];
  788. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  789. }
  790. - (NSDictionary *)createSampleMetadata {
  791. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  792. NSString *namespace = @"test_namespace";
  793. NSDictionary *deviceContext = @{};
  794. NSDictionary *syncedDBCustomVariables = @{};
  795. NSArray *successFetchTimes = @[];
  796. NSArray *failureFetchTimes = @[];
  797. // serialize objects
  798. NSError *error;
  799. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  800. options:NSJSONWritingPrettyPrinted
  801. error:&error];
  802. NSData *serializedDeviceContext =
  803. [NSJSONSerialization dataWithJSONObject:deviceContext
  804. options:NSJSONWritingPrettyPrinted
  805. error:&error];
  806. NSData *serializedDigestPerNamespace =
  807. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  808. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  809. options:NSJSONWritingPrettyPrinted
  810. error:&error];
  811. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  812. options:NSJSONWritingPrettyPrinted
  813. error:&error];
  814. return @{
  815. RCNKeyBundleIdentifier : bundleIdentifier,
  816. RCNKeyNamespace : namespace,
  817. RCNKeyFetchTime : @(0),
  818. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  819. RCNKeyDeviceContext : serializedDeviceContext,
  820. RCNKeyAppContext : serializedAppContext,
  821. RCNKeySuccessFetchTime : serializedSuccessTime,
  822. RCNKeyFailureFetchTime : serializedFailureTime,
  823. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  824. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  825. RCNKeyLastApplyTime : @(100),
  826. RCNKeyLastSetDefaultsTime : @(200)
  827. };
  828. }
  829. @end