RCNConfigDBManagerTest.m 42 KB

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