RCNConfigDBManagerTest.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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 <XCTest/XCTest.h>
  17. #import "sqlite3.h"
  18. #import <FirebaseCore/FIRAppInternal.h>
  19. #import <OCMock/OCMock.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> *>
  77. *defaultConfig) {
  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 serailizedly"];
  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 loadMainWithBundleIdentifier:bundleIdentifier
  117. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  118. NSDictionary *activeConfig,
  119. NSDictionary *defaultConfig) {
  120. NSMutableDictionary *res = [fetchedConfig mutableCopy];
  121. XCTAssertTrue(success);
  122. FIRRemoteConfigValue *value = res[namespace_p][@"key100"];
  123. XCTAssertEqualObjects(value.stringValue, @"value100");
  124. if (success) {
  125. [loadConfigContentExpectation fulfill];
  126. }
  127. }];
  128. }
  129. };
  130. NSString *value = [NSString stringWithFormat:@"value%d", i];
  131. NSString *key = [NSString stringWithFormat:@"key%d", i];
  132. NSArray *values =
  133. @[ bundleIdentifier, namespace_p, key, [value dataUsingEncoding:NSUTF8StringEncoding] ];
  134. [_DBManager insertMainTableWithValues:values
  135. fromSource:RCNDBSourceFetched
  136. completionHandler:insertCompletion];
  137. }
  138. [self waitForExpectationsWithTimeout:_expectionTimeout
  139. handler:^(NSError *error) {
  140. XCTAssertNil(error);
  141. }];
  142. }
  143. - (void)testWriteAndLoadInternalMetadataResult {
  144. XCTestExpectation *loadConfigContentExpectation = [self
  145. expectationWithDescription:@"Write and read internal metadata in database successfully"];
  146. __block int count = 0;
  147. for (int i = 0; i <= 100; ++i) {
  148. // check DB write correctly
  149. RCNDBCompletion insertCompletion = ^void(BOOL success, NSDictionary *result) {
  150. count++;
  151. XCTAssertTrue(success);
  152. if (count == 100) {
  153. // check DB read correctly
  154. NSDictionary *result = [self->_DBManager loadInternalMetadataTable];
  155. NSString *stringValue = [[NSString alloc] initWithData:result[@"key100"]
  156. encoding:NSUTF8StringEncoding];
  157. XCTAssertEqualObjects(stringValue, @"value100");
  158. if (success) {
  159. [loadConfigContentExpectation fulfill];
  160. }
  161. }
  162. };
  163. NSString *value = [NSString stringWithFormat:@"value%d", i];
  164. NSString *key = [NSString stringWithFormat:@"key%d", i];
  165. NSArray *values = @[ key, [value dataUsingEncoding:NSUTF8StringEncoding] ];
  166. [_DBManager insertInternalMetadataTableWithValues:values completionHandler:insertCompletion];
  167. }
  168. [self waitForExpectationsWithTimeout:_expectionTimeout
  169. handler:^(NSError *error) {
  170. XCTAssertNil(error);
  171. }];
  172. }
  173. - (void)testWriteAndLoadMetadataResult {
  174. XCTestExpectation *writeAndLoadMetadataExpectation =
  175. [self expectationWithDescription:@"Write and load metadata in database successfully"];
  176. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  177. NSTimeInterval lastFetchTimestamp = [NSDate date].timeIntervalSince1970;
  178. NSDictionary *deviceContext = @{@"app_version" : @"1.0.1", @"os_version" : @"iOS9.1"};
  179. NSDictionary *syncedDBCustomVariables = @{@"user_level" : @15, @"user_experiences" : @"2468"};
  180. NSArray *successFetchTimes = @[];
  181. NSTimeInterval now = [NSDate date].timeIntervalSince1970;
  182. NSArray *failureFetchTimes =
  183. @[ [NSNumber numberWithDouble:now - 200], [NSNumber numberWithDouble:now] ];
  184. // serialize objects
  185. NSError *error;
  186. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  187. options:NSJSONWritingPrettyPrinted
  188. error:&error];
  189. NSData *serializedDeviceContext =
  190. [NSJSONSerialization dataWithJSONObject:deviceContext
  191. options:NSJSONWritingPrettyPrinted
  192. error:&error];
  193. NSData *serializedDigestPerNamespace =
  194. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  195. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  196. options:NSJSONWritingPrettyPrinted
  197. error:&error];
  198. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  199. options:NSJSONWritingPrettyPrinted
  200. error:&error];
  201. NSDictionary *columnNameToValue = @{
  202. RCNKeyBundleIdentifier : bundleIdentifier,
  203. RCNKeyFetchTime : @(lastFetchTimestamp),
  204. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  205. RCNKeyDeviceContext : serializedDeviceContext,
  206. RCNKeyAppContext : serializedAppContext,
  207. RCNKeySuccessFetchTime : serializedSuccessTime,
  208. RCNKeyFailureFetchTime : serializedFailureTime,
  209. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  210. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  211. RCNKeyLastApplyTime : @(now - 100),
  212. RCNKeyLastSetDefaultsTime : @(now - 200)
  213. };
  214. RCNDBCompletion completion = ^(BOOL success, NSDictionary *result1) {
  215. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  216. XCTAssertNotNil(result);
  217. XCTAssertEqualObjects(result[RCNKeyBundleIdentifier], bundleIdentifier);
  218. XCTAssertEqual([result[RCNKeyFetchTime] doubleValue], lastFetchTimestamp);
  219. XCTAssertEqualObjects([result[RCNKeyDigestPerNamespace] copy], @{});
  220. XCTAssertEqualObjects([result[RCNKeyDeviceContext] copy], deviceContext);
  221. XCTAssertEqualObjects([result[RCNKeyAppContext] copy], syncedDBCustomVariables);
  222. XCTAssertEqualObjects([result[RCNKeySuccessFetchTime] copy], successFetchTimes);
  223. // TODO(chliang): Fix the flakiness caused by the commented out test
  224. // XCTAssertTrue([[result[RCNKeyFailureFetchTime] copy] isEqualToArray:failureFetchTimes]);
  225. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  226. (int)FIRRemoteConfigFetchStatusSuccess);
  227. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorUnknown);
  228. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], now - 100);
  229. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], now - 200);
  230. [writeAndLoadMetadataExpectation fulfill];
  231. };
  232. [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:completion];
  233. [self waitForExpectationsWithTimeout:_expectionTimeout
  234. handler:^(NSError *error) {
  235. XCTAssertNil(error);
  236. }];
  237. }
  238. // Create a key each for two namespaces, delete it from one namespace, read both namespaces.
  239. - (void)testDeleteParamAndLoadMainTable {
  240. XCTestExpectation *namespaceDeleteExpectation =
  241. [self expectationWithDescription:@"Contents of 'namespace_delete' should be deleted."];
  242. XCTestExpectation *namespaceKeepExpectation =
  243. [self expectationWithDescription:@"Write a key to namespace_keep and read back again."];
  244. NSString *namespaceToDelete = @"namespace_delete";
  245. NSString *namespaceToKeep = @"namespace_keep";
  246. NSString *bundleIdentifier = @"testBundleID";
  247. // Write something to the database for both namespaces.
  248. // Completion handler for the write to namespace_delete namespace.
  249. RCNDBCompletion insertNamespace1Completion = ^void(BOOL success, NSDictionary *result) {
  250. XCTAssertTrue(success);
  251. // Delete the key for given namespace.
  252. [self->_DBManager deleteRecordFromMainTableWithNamespace:namespaceToDelete
  253. bundleIdentifier:bundleIdentifier
  254. fromSource:RCNDBSourceActive];
  255. // Read from the database and verify expected values.
  256. [self->_DBManager
  257. loadMainWithBundleIdentifier:bundleIdentifier
  258. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  259. NSDictionary *activeConfig, NSDictionary *defaultConfig) {
  260. NSMutableDictionary *res = [activeConfig mutableCopy];
  261. XCTAssertTrue(success);
  262. FIRRemoteConfigValue *value = res[namespaceToDelete][@"keyToDelete"];
  263. XCTAssertNil(value);
  264. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  265. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  266. [namespaceDeleteExpectation fulfill];
  267. }];
  268. };
  269. // Insert a key into the second namespace.
  270. RCNDBCompletion insertNamespace2Completion = ^void(BOOL success, NSDictionary *result) {
  271. XCTAssertTrue(success);
  272. // Ensure DB read succeeds.
  273. [self->_DBManager
  274. loadMainWithBundleIdentifier:bundleIdentifier
  275. completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
  276. NSDictionary *activeConfig, NSDictionary *defaultConfig) {
  277. NSMutableDictionary *res = [activeConfig mutableCopy];
  278. XCTAssertTrue(success);
  279. FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"];
  280. XCTAssertTrue([value2.stringValue isEqualToString:@"valueToRetain"]);
  281. [namespaceKeepExpectation fulfill];
  282. }];
  283. };
  284. // We will delete this key after storing in the database.
  285. NSString *valueToDelete = @"valueToDelete";
  286. NSString *keyToDelete = @"keyToDelete";
  287. NSArray *items = @[
  288. bundleIdentifier, namespaceToDelete, keyToDelete,
  289. [valueToDelete dataUsingEncoding:NSUTF8StringEncoding]
  290. ];
  291. [_DBManager insertMainTableWithValues:items
  292. fromSource:RCNDBSourceActive
  293. completionHandler:insertNamespace1Completion];
  294. // This key value will be retained.
  295. NSString *valueToRetain = @"valueToRetain";
  296. NSString *keyToRetain = @"keyToRetain";
  297. NSArray *items2 = @[
  298. bundleIdentifier, namespaceToKeep, keyToRetain,
  299. [valueToRetain dataUsingEncoding:NSUTF8StringEncoding]
  300. ];
  301. [_DBManager insertMainTableWithValues:items2
  302. fromSource:RCNDBSourceActive
  303. completionHandler:insertNamespace2Completion];
  304. [self waitForExpectationsWithTimeout:_expectionTimeout
  305. handler:^(NSError *error) {
  306. XCTAssertNil(error);
  307. }];
  308. }
  309. - (void)testWriteAndLoadExperiments {
  310. XCTestExpectation *updateAndLoadExperimentExpectation =
  311. [self expectationWithDescription:@"Update and load experiment in database successfully"];
  312. NSError *error;
  313. NSArray *payload2 = @[ @"ab", @"cd" ];
  314. NSData *payloadData2 = [NSJSONSerialization dataWithJSONObject:payload2
  315. options:NSJSONWritingPrettyPrinted
  316. error:&error];
  317. NSDictionary *payload3 =
  318. @{@"experiment_ID" : @"35667", @"experiment_activate_name" : @"activate_game"};
  319. NSData *payloadData3 = [NSJSONSerialization dataWithJSONObject:payload3
  320. options:NSJSONWritingPrettyPrinted
  321. error:&error];
  322. NSArray *payloads = @[ [[NSData alloc] init], payloadData2, payloadData3 ];
  323. RCNDBCompletion writePayloadCompletion = ^(BOOL success, NSDictionary *result) {
  324. NSDictionary *metadata =
  325. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  326. XCTAssertTrue(success);
  327. RCNDBCompletion writeMetadataCompletion = ^(BOOL success, NSDictionary *result) {
  328. XCTAssertTrue(success);
  329. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  330. XCTAssertTrue(success);
  331. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  332. XCTAssertEqualObjects(payloads, experimentResults[@RCNExperimentTableKeyPayload]);
  333. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  334. XCTAssertEqualWithAccuracy(
  335. -11,
  336. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"]
  337. doubleValue],
  338. 1.0);
  339. XCTAssertEqualObjects(
  340. @"wonderful",
  341. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  342. [updateAndLoadExperimentExpectation fulfill];
  343. };
  344. [self->_DBManager loadExperimentWithCompletionHandler:readCompletion];
  345. };
  346. NSError *error;
  347. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  348. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  349. options:NSJSONWritingPrettyPrinted
  350. error:&error];
  351. [self->_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  352. value:serializedMetadata
  353. completionHandler:writeMetadataCompletion];
  354. };
  355. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  356. value:[[NSData alloc] init]
  357. completionHandler:nil];
  358. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  359. value:payloadData2
  360. completionHandler:nil];
  361. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
  362. value:payloadData3
  363. completionHandler:writePayloadCompletion];
  364. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  365. }
  366. - (void)testWriteAndLoadMetadataMultipleTimes {
  367. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  368. expectationWithDescription:@"Update and load experiment metadata in database successfully"];
  369. RCNDBCompletion readCompletion = ^(BOOL success, NSDictionary *experimentResults) {
  370. XCTAssertTrue(success);
  371. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyPayload]);
  372. XCTAssertNotNil(experimentResults[@RCNExperimentTableKeyMetadata]);
  373. XCTAssertEqualWithAccuracy(
  374. 12345678,
  375. [experimentResults[@RCNExperimentTableKeyMetadata][@"last_known_start_time"] doubleValue],
  376. 1.0);
  377. XCTAssertEqualObjects(
  378. @"wonderful",
  379. experimentResults[@RCNExperimentTableKeyMetadata][@"experiment_new_metadata"]);
  380. [updateAndLoadMetadataExpectation fulfill];
  381. };
  382. NSDictionary *metadata =
  383. @{@"last_known_start_time" : @(-11), @"experiment_new_metadata" : @"wonderful"};
  384. NSError *error;
  385. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  386. NSData *serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  387. options:NSJSONWritingPrettyPrinted
  388. error:&error];
  389. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  390. value:serializedMetadata
  391. completionHandler:nil];
  392. metadata = @{@"last_known_start_time" : @(12345678), @"experiment_new_metadata" : @"wonderful"};
  393. XCTAssertTrue([NSJSONSerialization isValidJSONObject:metadata]);
  394. serializedMetadata = [NSJSONSerialization dataWithJSONObject:metadata
  395. options:NSJSONWritingPrettyPrinted
  396. error:&error];
  397. [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
  398. value:serializedMetadata
  399. completionHandler:nil];
  400. [_DBManager loadExperimentWithCompletionHandler:readCompletion];
  401. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  402. }
  403. - (void)testUpdateAndloadLastFetchStatus {
  404. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  405. expectationWithDescription:@"Update and load last fetch status in database successfully."];
  406. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  407. // Metadata row must exist before update
  408. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  409. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  410. XCTAssertTrue(success);
  411. XCTAssertNotNil(result);
  412. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  413. (int)FIRRemoteConfigFetchStatusSuccess);
  414. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorUnknown);
  415. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  416. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  417. XCTAssertTrue(success);
  418. XCTAssertNotNil(result);
  419. XCTAssertEqual([result[RCNKeyLastFetchStatus] intValue],
  420. (int)FIRRemoteConfigFetchStatusThrottled);
  421. XCTAssertEqual([result[RCNKeyLastFetchError] intValue], (int)FIRRemoteConfigErrorThrottled);
  422. [updateAndLoadMetadataExpectation fulfill];
  423. };
  424. // Update with throttle status.
  425. [self->_DBManager
  426. updateMetadataWithOption:RCNUpdateOptionFetchStatus
  427. values:@[
  428. @(FIRRemoteConfigFetchStatusThrottled), @(FIRRemoteConfigErrorThrottled)
  429. ]
  430. completionHandler:updateMetadataCompletion];
  431. };
  432. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  433. completionHandler:createMetadataCompletion];
  434. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  435. }
  436. /// TODO: Fix test case.
  437. /// Tests that we can insert values in the database and can update them.
  438. - (void)ignore_InsertAndUpdateApplyTime {
  439. XCTestExpectation *updateAndLoadMetadataExpectation =
  440. [self expectationWithDescription:@"Update and load apply time in database successfully."];
  441. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  442. NSTimeInterval lastApplyTimestamp = [NSDate date].timeIntervalSince1970;
  443. // Metadata row must exist before update
  444. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  445. XCTAssertTrue(success);
  446. // Read newly created metadata.
  447. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  448. XCTAssertNotNil(result);
  449. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], (double)100);
  450. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  451. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  452. XCTAssertTrue(success);
  453. XCTAssertNotNil(result);
  454. XCTAssertEqual([result[RCNKeyLastApplyTime] doubleValue], lastApplyTimestamp);
  455. [updateAndLoadMetadataExpectation fulfill];
  456. };
  457. // Update apply config timestamp.
  458. [self->_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime
  459. values:@[ @(lastApplyTimestamp) ]
  460. completionHandler:updateMetadataCompletion];
  461. };
  462. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  463. completionHandler:createMetadataCompletion];
  464. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  465. }
  466. - (void)testUpdateAndLoadSetDefaultsTime {
  467. XCTestExpectation *updateAndLoadMetadataExpectation = [self
  468. expectationWithDescription:@"Update and load set defaults time in database successfully."];
  469. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  470. NSTimeInterval lastSetDefaultsTimestamp = [NSDate date].timeIntervalSince1970;
  471. // Metadata row must exist before update
  472. RCNDBCompletion createMetadataCompletion = ^(BOOL success, NSDictionary *createResult) {
  473. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  474. XCTAssertTrue(success);
  475. XCTAssertNotNil(result);
  476. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], (double)200);
  477. RCNDBCompletion updateMetadataCompletion = ^(BOOL success, NSDictionary *updateResult) {
  478. NSDictionary *result = [self->_DBManager loadMetadataWithBundleIdentifier:bundleIdentifier];
  479. XCTAssertTrue(success);
  480. XCTAssertNotNil(result);
  481. XCTAssertEqual([result[RCNKeyLastSetDefaultsTime] doubleValue], lastSetDefaultsTimestamp);
  482. [updateAndLoadMetadataExpectation fulfill];
  483. };
  484. // Update setting default config timestamp.
  485. [self->_DBManager updateMetadataWithOption:RCNUpdateOptionDefaultTime
  486. values:@[ @(lastSetDefaultsTimestamp) ]
  487. completionHandler:updateMetadataCompletion];
  488. };
  489. [_DBManager insertMetadataTableWithValues:[self createSampleMetadata]
  490. completionHandler:createMetadataCompletion];
  491. [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil];
  492. }
  493. - (NSDictionary *)createSampleMetadata {
  494. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  495. NSDictionary *deviceContext = @{};
  496. NSDictionary *syncedDBCustomVariables = @{};
  497. NSArray *successFetchTimes = @[];
  498. NSArray *failureFetchTimes = @[];
  499. // serialize objects
  500. NSError *error;
  501. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:syncedDBCustomVariables
  502. options:NSJSONWritingPrettyPrinted
  503. error:&error];
  504. NSData *serializedDeviceContext =
  505. [NSJSONSerialization dataWithJSONObject:deviceContext
  506. options:NSJSONWritingPrettyPrinted
  507. error:&error];
  508. NSData *serializedDigestPerNamespace =
  509. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  510. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:successFetchTimes
  511. options:NSJSONWritingPrettyPrinted
  512. error:&error];
  513. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:failureFetchTimes
  514. options:NSJSONWritingPrettyPrinted
  515. error:&error];
  516. return @{
  517. RCNKeyBundleIdentifier : bundleIdentifier,
  518. RCNKeyFetchTime : @(0),
  519. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  520. RCNKeyDeviceContext : serializedDeviceContext,
  521. RCNKeyAppContext : serializedAppContext,
  522. RCNKeySuccessFetchTime : serializedSuccessTime,
  523. RCNKeyFailureFetchTime : serializedFailureTime,
  524. RCNKeyLastFetchStatus : @(FIRRemoteConfigFetchStatusSuccess),
  525. RCNKeyLastFetchError : @(FIRRemoteConfigErrorUnknown),
  526. RCNKeyLastApplyTime : @(100),
  527. RCNKeyLastSetDefaultsTime : @(200)
  528. };
  529. }
  530. @end