RCNConfigDBManagerTest.m 34 KB

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