RCNConfigDBManagerTest.m 29 KB

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