RCNConfigDBManagerTest.m 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  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)testWriteAndLoadMetadataResult {
  145. XCTestExpectation *writeAndLoadMetadataExpectation =
  146. [self expectationWithDescription:@"Write and load metadata in database successfully"];
  147. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  148. NSString *namespace = @"test_namespace";
  149. NSTimeInterval lastFetchTimestamp = [NSDate date].timeIntervalSince1970;
  150. NSDictionary *deviceContext =
  151. @{@"app_version" : @"1.0.1", @"app_build" : @"1.0.1.11", @"os_version" : @"iOS9.1"};
  152. NSDictionary *syncedDBCustomVariables = @{@"user_level" : @15, @"user_experiences" : @"2468"};
  153. NSArray *successFetchTimes = @[];
  154. NSTimeInterval now = [NSDate date].timeIntervalSince1970;
  155. NSArray *failureFetchTimes =
  156. @[ [NSNumber numberWithDouble:now - 200], [NSNumber numberWithDouble:now] ];
  157. // serialize objects
  158. NSError *error;
  159. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  160. options:NSJSONWritingPrettyPrinted
  161. error:&error];
  162. NSData *serializedDeviceContext =
  163. [NSJSONSerialization dataWithJSONObject:deviceContext
  164. options:NSJSONWritingPrettyPrinted
  165. error:&error];
  166. NSData *serializedDigestPerNamespace =
  167. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  168. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  169. options:NSJSONWritingPrettyPrinted
  170. error:&error];
  171. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  172. options:NSJSONWritingPrettyPrinted
  173. error:&error];
  174. NSDictionary *columnNameToValue = @{
  175. RCNKeyBundleIdentifier : bundleIdentifier,
  176. RCNKeyNamespace : namespace,
  177. RCNKeyFetchTime : @(lastFetchTimestamp),
  178. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  179. RCNKeyDeviceContext : serializedDeviceContext,
  180. RCNKeyAppContext : serializedAppContext,
  181. RCNKeySuccessFetchTime : serializedSuccessTime,
  182. RCNKeyFailureFetchTime : serializedFailureTime,
  183. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  184. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  185. RCNKeyLastApplyTime : @(now - 100),
  186. RCNKeyLastSetDefaultsTime : @(now - 200)
  187. };
  188. RCNDBCompletion completion = ^(BOOL success, NSDictionary *result1) {
  189. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  190. namespace:namespace];
  191. XCTAssertNotNil(result);
  192. XCTAssertEqualObjects(result[RCNKeyBundleIdentifier], bundleIdentifier);
  193. XCTAssertEqual([result[RCNKeyFetchTime] doubleValue], lastFetchTimestamp);
  194. XCTAssertEqualObjects([result[RCNKeyDigestPerNamespace] copy], @{});
  195. XCTAssertEqualObjects([result[RCNKeyDeviceContext] copy], deviceContext);
  196. XCTAssertEqualObjects([result[RCNKeyAppContext] copy], syncedDBCustomVariables);
  197. XCTAssertEqualObjects([result[RCNKeySuccessFetchTime] copy], successFetchTimes);
  198. // TODO(chliang): Fix the flakiness caused by the commented out test
  199. // XCTAssertTrue([[result[RCNKeyFailureFetchTime] copy] isEqualToArray:failureFetchTimes]);
  200. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  201. (int)FIRRemoteConfigFetchStatusSuccess);
  202. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorUnknown);
  203. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], now - 100);
  204. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], now - 200);
  205. [writeAndLoadMetadataExpectation fulfill];
  206. };
  207. [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:completion];
  208. [self waitForExpectationsWithTimeout:_expectionTimeout
  209. handler:^(NSError *error) {
  210. XCTAssertNil(error);
  211. }];
  212. }
  213. - (void)testWriteAndLoadMetadataForMultipleNamespaces {
  214. XCTestExpectation *writeAndLoadMetadataForMultipleNamespacesExpectation =
  215. [self expectationWithDescription:@"Metadata is stored and read based on namespace"];
  216. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  217. NSDictionary *deviceContext = @{};
  218. NSDictionary *syncedDBCustomVariables = @{};
  219. NSError *error;
  220. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  221. options:NSJSONWritingPrettyPrinted
  222. error:&error];
  223. NSData *serializedDeviceContext =
  224. [NSJSONSerialization dataWithJSONObject:deviceContext
  225. options:NSJSONWritingPrettyPrinted
  226. error:&error];
  227. NSData *serializedDigestPerNamespace =
  228. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  229. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:@[]
  230. options:NSJSONWritingPrettyPrinted
  231. error:&error];
  232. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:@[]
  233. options:NSJSONWritingPrettyPrinted
  234. error:&error];
  235. // Metadata for first namespace
  236. NSString *namespace = @"test_namespace";
  237. double lastApplyTime = 100;
  238. double lastSetDefaultsTime = 200;
  239. NSDictionary *valuesForNamespace = @{
  240. RCNKeyBundleIdentifier : bundleIdentifier,
  241. RCNKeyNamespace : namespace,
  242. RCNKeyFetchTime : @(0),
  243. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  244. RCNKeyDeviceContext : serializedDeviceContext,
  245. RCNKeyAppContext : serializedAppContext,
  246. RCNKeySuccessFetchTime : serializedSuccessTime,
  247. RCNKeyFailureFetchTime : serializedFailureTime,
  248. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  249. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  250. RCNKeyLastApplyTime : @(lastApplyTime),
  251. RCNKeyLastSetDefaultsTime : @(lastSetDefaultsTime)
  252. };
  253. // Metadata for second namespace
  254. NSString *namespace2 = @"test_namespace_2";
  255. double lastApplyTime2 = 300;
  256. double lastSetDefaultsTime2 = 400;
  257. NSDictionary *valuesForNamespace2 = @{
  258. RCNKeyBundleIdentifier : bundleIdentifier,
  259. RCNKeyNamespace : namespace2,
  260. RCNKeyFetchTime : @(0),
  261. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  262. RCNKeyDeviceContext : serializedDeviceContext,
  263. RCNKeyAppContext : serializedAppContext,
  264. RCNKeySuccessFetchTime : serializedSuccessTime,
  265. RCNKeyFailureFetchTime : serializedFailureTime,
  266. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  267. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  268. RCNKeyLastApplyTime : @(lastApplyTime2),
  269. RCNKeyLastSetDefaultsTime : @(lastSetDefaultsTime2)
  270. };
  271. RCNDBCompletion insertMetadataCompletion = ^void(BOOL success, NSDictionary *result) {
  272. XCTAssertTrue(success);
  273. // Load metadata for both namespaces and verify they retain their separate values
  274. NSDictionary *resultForNamespace =
  275. [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier namespace:namespace];
  276. NSDictionary *resultForNamespace2 =
  277. [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier namespace:namespace2];
  278. XCTAssertNotNil(resultForNamespace);
  279. XCTAssertEqual([resultForNamespace[RCNKeyLastApplyTime] doubleValue], lastApplyTime);
  280. XCTAssertEqual([resultForNamespace[RCNKeyLastSetDefaultsTime] doubleValue],
  281. lastSetDefaultsTime);
  282. XCTAssertNotNil(resultForNamespace2);
  283. XCTAssertEqual([resultForNamespace2[RCNKeyLastApplyTime] doubleValue], lastApplyTime2);
  284. XCTAssertEqual([resultForNamespace2[RCNKeyLastSetDefaultsTime] doubleValue],
  285. lastSetDefaultsTime2);
  286. [writeAndLoadMetadataForMultipleNamespacesExpectation fulfill];
  287. };
  288. // Write metadata for first namespace
  289. [_DBManager insertMetadataTableWithValues:valuesForNamespace
  290. completionHandler:^(BOOL success, NSDictionary *result1) {
  291. XCTAssertTrue(success);
  292. // Write metadata for second namespace
  293. [self->_DBManager
  294. insertMetadataTableWithValues:valuesForNamespace2
  295. completionHandler:insertMetadataCompletion];
  296. }];
  297. [self waitForExpectationsWithTimeout:_expectionTimeout
  298. handler:^(NSError *error) {
  299. XCTAssertNil(error);
  300. }];
  301. }
  302. // Create a key each for two namespaces, delete it from one namespace, read both namespaces.
  303. - (void)testDeleteParamAndLoadMainTable {
  304. XCTestExpectation *namespaceDeleteExpectation =
  305. [self expectationWithDescription:@"Contents of 'namespace_delete' should be deleted."];
  306. XCTestExpectation *namespaceKeepExpectation =
  307. [self expectationWithDescription:@"Write a key to namespace_keep and read back again."];
  308. NSString *namespaceToDelete = @"namespace_delete";
  309. NSString *namespaceToKeep = @"namespace_keep";
  310. NSString *bundleIdentifier = @"testBundleID";
  311. // Write something to the database for both namespaces.
  312. // Completion handler for the write to namespace_delete namespace.
  313. RCNDBCompletion insertNamespace1Completion = ^void(BOOL success, NSDictionary *result) {
  314. XCTAssertTrue(success);
  315. // Delete the key for given namespace.
  316. [self->_DBManager deleteRecordFromMainTableWithNamespace:namespaceToDelete
  317. bundleIdentifier:bundleIdentifier
  318. fromSource:RCNDBSourceActive];
  319. // Read from the database and verify expected values.
  320. [self->_DBManager
  321. loadMainWithBundleIdentifier:bundleIdentifier
  322. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  323. NSDictionary *activeConfig, NSDictionary *defaultConfig,
  324. NSDictionary *unusedRolloutMetadata) {
  325. NSMutableDictionary *res = [activeConfig mutableCopy];
  326. XCTAssertTrue(success);
  327. FIRRemoteConfigValue *value = res[namespaceToDelete][@"keyToDelete"];
  328. XCTAssertNil(value);
  329. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  330. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  331. [namespaceDeleteExpectation fulfill];
  332. }];
  333. };
  334. // Insert a key into the second namespace.
  335. RCNDBCompletion insertNamespace2Completion = ^void(BOOL success, NSDictionary *result) {
  336. XCTAssertTrue(success);
  337. // Ensure DB read succeeds.
  338. [self->_DBManager
  339. loadMainWithBundleIdentifier:bundleIdentifier
  340. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  341. NSDictionary *activeConfig, NSDictionary *defaultConfig,
  342. NSDictionary *unusedRolloutMetadata) {
  343. NSMutableDictionary *res = [activeConfig mutableCopy];
  344. XCTAssertTrue(success);
  345. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  346. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  347. [namespaceKeepExpectation fulfill];
  348. }];
  349. };
  350. // We will delete this key after storing in the database.
  351. NSString *valueToDelete = @"valueToDelete";
  352. NSString *keyToDelete = @"keyToDelete";
  353. NSArray *items = @[
  354. bundleIdentifier, namespaceToDelete, keyToDelete,
  355. [valueToDelete dataUsingEncoding:NSUTF8StringEncoding]
  356. ];
  357. [_DBManager insertMainTableWithValues:items
  358. fromSource:RCNDBSourceActive
  359. completionHandler:insertNamespace1Completion];
  360. // This key value will be retained.
  361. NSString *valueToRetain = @"valueToRetain";
  362. NSString *keyToRetain = @"keyToRetain";
  363. NSArray *items2 = @[
  364. bundleIdentifier, namespaceToKeep, keyToRetain,
  365. [valueToRetain dataUsingEncoding:NSUTF8StringEncoding]
  366. ];
  367. [_DBManager insertMainTableWithValues:items2
  368. fromSource:RCNDBSourceActive
  369. completionHandler:insertNamespace2Completion];
  370. [self waitForExpectationsWithTimeout:_expectionTimeout
  371. handler:^(NSError *error) {
  372. XCTAssertNil(error);
  373. }];
  374. }
  375. - (void)testWriteAndLoadExperiments {
  376. XCTestExpectation *updateAndLoadExperimentExpectation =
  377. [self expectationWithDescription:@"Update and load experiment in database successfully"];
  378. NSError *error;
  379. NSArray *payload2 = @[ @"ab", @"cd" ];
  380. NSData *payloadData2 = [NSJSONSerialization dataWithJSONObject:payload2
  381. options:NSJSONWritingPrettyPrinted
  382. error:&error];
  383. NSDictionary *payload3 =
  384. @{@"experiment_ID" : @"35667", @"experiment_activate_name" : @"activate_game"};
  385. NSData *payloadData3 = [NSJSONSerialization dataWithJSONObject:payload3
  386. options:NSJSONWritingPrettyPrinted
  387. error:&error];
  388. NSArray *payloads = @[ [[NSData alloc] init], payloadData2, payloadData3 ];
  389. RCNDBCompletion writePayloadCompletion = ^(BOOL success, NSDictionary *result) {
  390. NSDictionary *metadata =
  391. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  392. XCTAssertTrue(success);
  393. RCNDBCompletion writeMetadataCompletion = ^(BOOL success, NSDictionary *result) {
  394. XCTAssertTrue(success);
  395. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  396. XCTAssertTrue(success);
  397. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  398. XCTAssertEqualObjects(payloads, experimentResults[@RCNExperimentTableKeyPayload]);
  399. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  400. XCTAssertEqualWithAccuracy(
  401. -11,
  402. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"]
  403. doubleValue],
  404. 1.0);
  405. XCTAssertEqualObjects(
  406. @"wonderful",
  407. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  408. [updateAndLoadExperimentExpectation fulfill];
  409. };
  410. [self->_DBManager loadExperimentWithCompletionHandler:readCompletion];
  411. };
  412. NSError *error;
  413. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  414. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  415. options:NSJSONWritingPrettyPrinted
  416. error:&error];
  417. [self->_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  418. value:serializedMetadata
  419. completionHandler:writeMetadataCompletion];
  420. };
  421. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  422. value:[[NSData alloc] init]
  423. completionHandler:nil];
  424. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  425. value:payloadData2
  426. completionHandler:nil];
  427. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  428. value:payloadData3
  429. completionHandler:writePayloadCompletion];
  430. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  431. }
  432. - (void)testWriteAndLoadActivatedExperiments {
  433. XCTestExpectation *updateAndLoadExperimentExpectation =
  434. [self expectationWithDescription:@"Update and load experiment in database successfully"];
  435. NSError *error;
  436. NSArray *payload2 = @[ @"ab", @"cd" ];
  437. NSData *payloadData2 = [NSJSONSerialization dataWithJSONObject:payload2
  438. options:NSJSONWritingPrettyPrinted
  439. error:&error];
  440. NSDictionary *payload3 =
  441. @{@"experiment_ID" : @"35667", @"experiment_activate_name" : @"activate_game"};
  442. NSData *payloadData3 = [NSJSONSerialization dataWithJSONObject:payload3
  443. options:NSJSONWritingPrettyPrinted
  444. error:&error];
  445. NSArray *payloads = @[ [[NSData alloc] init], payloadData2, payloadData3 ];
  446. RCNDBCompletion writePayloadCompletion = ^(BOOL success, NSDictionary *result) {
  447. XCTAssertTrue(success);
  448. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  449. XCTAssertTrue(success);
  450. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyActivePayload]);
  451. XCTAssertEqualObjects(payloads, experimentResults[@RCNExperimentTableKeyActivePayload]);
  452. [updateAndLoadExperimentExpectation fulfill];
  453. };
  454. [self->_DBManager loadExperimentWithCompletionHandler:readCompletion];
  455. };
  456. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  457. value:[[NSData alloc] init]
  458. completionHandler:nil];
  459. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  460. value:payloadData2
  461. completionHandler:nil];
  462. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
  463. value:payloadData3
  464. completionHandler:writePayloadCompletion];
  465. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  466. }
  467. - (void)testWriteAndLoadMetadataMultipleTimes {
  468. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  469. expectationWithDescription:@"Update and load experiment metadata in database successfully"];
  470. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  471. XCTAssertTrue(success);
  472. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  473. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  474. XCTAssertEqualWithAccuracy(
  475. 12345678,
  476. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"] doubleValue],
  477. 1.0);
  478. XCTAssertEqualObjects(
  479. @"wonderful",
  480. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  481. [updateAndLoadMetadataExpectation fulfill];
  482. };
  483. NSDictionary *metadata =
  484. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  485. NSError *error;
  486. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  487. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  488. options:NSJSONWritingPrettyPrinted
  489. error:&error];
  490. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  491. value:serializedMetadata
  492. completionHandler:nil];
  493. metadata = @{@"last_known_start_time" : @(12345678), @"experiment_new_metadata" : @"wonderful"};
  494. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  495. serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  496. options:NSJSONWritingPrettyPrinted
  497. error:&error];
  498. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  499. value:serializedMetadata
  500. completionHandler:nil];
  501. [_DBManager loadExperimentWithCompletionHandler:readCompletion];
  502. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  503. }
  504. - (void)testWriteAndLoadFetchedAndActiveRollout {
  505. XCTestExpectation *writeAndLoadFetchedRolloutExpectation =
  506. [self expectationWithDescription:@"Write and load rollout in database successfully"];
  507. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  508. NSArray *fetchedRollout = @[
  509. @{
  510. @"rollout_id" : @"1",
  511. @"variant_id" : @"B",
  512. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  513. },
  514. @{
  515. @"rollout_id" : @"2",
  516. @"variant_id" : @"1",
  517. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  518. }
  519. ];
  520. NSArray *activeRollout = @[
  521. @{
  522. @"rollout_id" : @"1",
  523. @"variant_id" : @"B",
  524. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  525. },
  526. @{
  527. @"rollout_id" : @"3",
  528. @"variant_id" : @"a",
  529. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  530. }
  531. ];
  532. RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) {
  533. XCTAssertTrue(success);
  534. RCNDBLoadCompletion loadCompletion = ^(
  535. BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  536. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  537. XCTAssertTrue(success);
  538. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  539. XCTAssertEqualObjects(fetchedRollout, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  540. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  541. XCTAssertEqualObjects(activeRollout, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  542. [writeAndLoadFetchedRolloutExpectation fulfill];
  543. };
  544. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier
  545. completionHandler:loadCompletion];
  546. };
  547. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  548. value:fetchedRollout
  549. completionHandler:nil];
  550. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
  551. value:activeRollout
  552. completionHandler:writeRolloutCompletion];
  553. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  554. }
  555. - (void)testUpdateAndLoadRollout {
  556. XCTestExpectation *updateAndLoadFetchedRolloutExpectation =
  557. [self expectationWithDescription:@"Update and load rollout in database successfully"];
  558. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  559. NSArray *fetchedRollout = @[ @{
  560. @"rollout_id" : @"1",
  561. @"variant_id" : @"B",
  562. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  563. } ];
  564. NSArray *updatedFetchedRollout = @[
  565. @{
  566. @"rollout_id" : @"1",
  567. @"variant_id" : @"B",
  568. @"affected_parameter_keys" : @[ @"key_1", @"key_2" ]
  569. },
  570. @{
  571. @"rollout_id" : @"2",
  572. @"variant_id" : @"1",
  573. @"affected_parameter_keys" : @[ @"key_1", @"key_3" ]
  574. }
  575. ];
  576. RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) {
  577. XCTAssertTrue(success);
  578. RCNDBLoadCompletion loadCompletion =
  579. ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  580. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  581. XCTAssertTrue(success);
  582. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  583. XCTAssertEqualObjects(updatedFetchedRollout,
  584. rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  585. [updateAndLoadFetchedRolloutExpectation fulfill];
  586. };
  587. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier
  588. completionHandler:loadCompletion];
  589. };
  590. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  591. value:fetchedRollout
  592. completionHandler:nil];
  593. [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
  594. value:updatedFetchedRollout
  595. completionHandler:writeRolloutCompletion];
  596. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  597. }
  598. - (void)testLoadEmptyRollout {
  599. XCTestExpectation *updateAndLoadFetchedRolloutExpectation =
  600. [self expectationWithDescription:@"Load empty rollout in database successfully"];
  601. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  602. NSArray *emptyResult = [[NSArray alloc] init];
  603. RCNDBLoadCompletion loadCompletion =
  604. ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig,
  605. NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) {
  606. XCTAssertTrue(success);
  607. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  608. XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]);
  609. XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  610. XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]);
  611. [updateAndLoadFetchedRolloutExpectation fulfill];
  612. };
  613. [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier completionHandler:loadCompletion];
  614. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  615. }
  616. - (void)testUpdateAndloadLastFetchStatus {
  617. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  618. expectationWithDescription:@"Update and load last fetch status in database successfully."];
  619. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  620. NSString *namespace = @"test_namespace";
  621. // Metadata row must exist before update
  622. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  623. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  624. namespace:namespace];
  625. XCTAssertTrue(success);
  626. XCTAssertNotNil(result);
  627. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  628. (int)FIRRemoteConfigFetchStatusSuccess);
  629. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorUnknown);
  630. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  631. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  632. namespace:namespace];
  633. XCTAssertTrue(success);
  634. XCTAssertNotNil(result);
  635. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  636. (int)FIRRemoteConfigFetchStatusThrottled);
  637. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorThrottled);
  638. [updateAndLoadMetadataExpectation fulfill];
  639. };
  640. // Update with throttle status.
  641. [self->_DBManager
  642. updateMetadataWithOption:RCNUpdateOptionFetchStatus
  643. namespace:namespace
  644. values:@[
  645. @(FIRRemoteConfigFetchStatusThrottled), @(FIRRemoteConfigErrorThrottled)
  646. ]
  647. completionHandler:updateMetadataCompletion];
  648. };
  649. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  650. completionHandler:createMetadataCompletion];
  651. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  652. }
  653. /// Tests that we can insert values in the database and can update them.
  654. - (void)testInsertAndUpdateApplyTime {
  655. XCTestExpectation *updateAndLoadMetadataExpectation =
  656. [self expectationWithDescription:@"Update and load apply time in database successfully."];
  657. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  658. NSString *namespace = @"test_namespace";
  659. NSTimeInterval lastApplyTimestamp = [NSDate date].timeIntervalSince1970;
  660. // Metadata row must exist before update
  661. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  662. XCTAssertTrue(success);
  663. // Read newly created metadata.
  664. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  665. namespace:namespace];
  666. XCTAssertNotNil(result);
  667. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], (double)100);
  668. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  669. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  670. namespace:namespace];
  671. XCTAssertTrue(success);
  672. XCTAssertNotNil(result);
  673. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], lastApplyTimestamp);
  674. [updateAndLoadMetadataExpectation fulfill];
  675. };
  676. // Update apply config timestamp.
  677. [self->_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime
  678. namespace:namespace
  679. values:@[ @(lastApplyTimestamp) ]
  680. completionHandler:updateMetadataCompletion];
  681. };
  682. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  683. completionHandler:createMetadataCompletion];
  684. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  685. }
  686. - (void)testUpdateAndLoadSetDefaultsTime {
  687. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  688. expectationWithDescription:@"Update and load set defaults time in database successfully."];
  689. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  690. NSString *namespace = @"test_namespace";
  691. NSTimeInterval lastSetDefaultsTimestamp = [NSDate date].timeIntervalSince1970;
  692. // Metadata row must exist before update
  693. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  694. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  695. namespace:namespace];
  696. XCTAssertTrue(success);
  697. XCTAssertNotNil(result);
  698. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], (double)200);
  699. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  700. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier
  701. namespace:namespace];
  702. XCTAssertTrue(success);
  703. XCTAssertNotNil(result);
  704. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], lastSetDefaultsTimestamp);
  705. [updateAndLoadMetadataExpectation fulfill];
  706. };
  707. // Update setting default config timestamp.
  708. [self->_DBManager updateMetadataWithOption:RCNUpdateOptionDefaultTime
  709. namespace:namespace
  710. values:@[ @(lastSetDefaultsTimestamp) ]
  711. completionHandler:updateMetadataCompletion];
  712. };
  713. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  714. completionHandler:createMetadataCompletion];
  715. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  716. }
  717. - (NSDictionary *)createSampleMetadata {
  718. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  719. NSString *namespace = @"test_namespace";
  720. NSDictionary *deviceContext = @{};
  721. NSDictionary *syncedDBCustomVariables = @{};
  722. NSArray *successFetchTimes = @[];
  723. NSArray *failureFetchTimes = @[];
  724. // serialize objects
  725. NSError *error;
  726. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  727. options:NSJSONWritingPrettyPrinted
  728. error:&error];
  729. NSData *serializedDeviceContext =
  730. [NSJSONSerialization dataWithJSONObject:deviceContext
  731. options:NSJSONWritingPrettyPrinted
  732. error:&error];
  733. NSData *serializedDigestPerNamespace =
  734. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  735. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  736. options:NSJSONWritingPrettyPrinted
  737. error:&error];
  738. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  739. options:NSJSONWritingPrettyPrinted
  740. error:&error];
  741. return @{
  742. RCNKeyBundleIdentifier : bundleIdentifier,
  743. RCNKeyNamespace : namespace,
  744. RCNKeyFetchTime : @(0),
  745. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  746. RCNKeyDeviceContext : serializedDeviceContext,
  747. RCNKeyAppContext : serializedAppContext,
  748. RCNKeySuccessFetchTime : serializedSuccessTime,
  749. RCNKeyFailureFetchTime : serializedFailureTime,
  750. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  751. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  752. RCNKeyLastApplyTime : @(100),
  753. RCNKeyLastSetDefaultsTime : @(200)
  754. };
  755. }
  756. @end