RCNConfigContentTest.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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 "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
  19. #import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
  20. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  21. #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  23. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  24. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  25. @interface RCNConfigContent (Testing)
  26. - (BOOL)checkAndWaitForInitialDatabaseLoad;
  27. @end
  28. extern const NSTimeInterval kDatabaseLoadTimeoutSecs;
  29. @interface RCNConfigDBManagerMock : RCNConfigDBManager
  30. @property(nonatomic, assign) BOOL isLoadMainCompleted;
  31. @property(nonatomic, assign) BOOL isLoadPersonalizationCompleted;
  32. @end
  33. @implementation RCNConfigDBManagerMock
  34. - (void)createOrOpenDatabase {
  35. }
  36. - (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier
  37. completionHandler:(RCNDBLoadCompletion)handler {
  38. double justSmallDelay = 0.008;
  39. XCTAssertTrue(justSmallDelay < kDatabaseLoadTimeoutSecs);
  40. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(justSmallDelay * NSEC_PER_SEC)),
  41. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  42. self.isLoadMainCompleted = YES;
  43. handler(YES, nil, nil, nil);
  44. });
  45. }
  46. - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler {
  47. double justOtherSmallDelay = 0.009;
  48. XCTAssertTrue(justOtherSmallDelay < kDatabaseLoadTimeoutSecs);
  49. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(justOtherSmallDelay * NSEC_PER_SEC)),
  50. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  51. self.isLoadPersonalizationCompleted = YES;
  52. handler(YES, nil, nil, nil);
  53. });
  54. }
  55. @end
  56. @interface RCNConfigContentTest : XCTestCase {
  57. NSTimeInterval _expectationTimeout;
  58. RCNConfigContent *_configContent;
  59. NSString *namespaceApp1, *namespaceApp2;
  60. }
  61. @end
  62. /// Unit Tests for RCNConfigContent methods.
  63. @implementation RCNConfigContentTest
  64. - (void)setUp {
  65. [super setUp];
  66. _expectationTimeout = 1.0;
  67. namespaceApp1 = [NSString
  68. stringWithFormat:@"%@:%@", FIRNamespaceGoogleMobilePlatform, RCNTestsDefaultFIRAppName];
  69. namespaceApp2 = [NSString
  70. stringWithFormat:@"%@:%@", FIRNamespaceGoogleMobilePlatform, RCNTestsSecondFIRAppName];
  71. _configContent = [[RCNConfigContent alloc] initWithDBManager:nil];
  72. id partialMock = OCMPartialMock(_configContent);
  73. OCMStub([partialMock checkAndWaitForInitialDatabaseLoad]).andDo(nil);
  74. }
  75. /// Passing in a nil bundleID should not crash the app
  76. - (void)testCrashShouldNotHappenWithoutMainBundleID {
  77. id mockBundle = OCMPartialMock([NSBundle mainBundle]);
  78. OCMStub([mockBundle bundleIdentifier]).andReturn(nil);
  79. _configContent = [[RCNConfigContent alloc] initWithDBManager:nil];
  80. [mockBundle stopMocking];
  81. }
  82. /// Standard test case of receiving updated config from fetch.
  83. - (void)testUpdateConfigContentForMultipleApps {
  84. NSMutableDictionary<NSString *, id> *config1ToSet =
  85. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  86. NSDictionary<NSString *, NSString *> *entries = @{@"key1" : @"value1", @"key2" : @"value2"};
  87. [config1ToSet setValue:entries forKey:@"entries"];
  88. [_configContent updateConfigContentWithResponse:config1ToSet forNamespace:namespaceApp1];
  89. // Update for second app.
  90. NSMutableDictionary<NSString *, id> *config2ToSet =
  91. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  92. NSDictionary<NSString *, NSString *> *entries2 = @{@"key11" : @"value11", @"key21" : @"value21"};
  93. [config2ToSet setValue:entries2 forKey:@"entries"];
  94. [_configContent updateConfigContentWithResponse:config2ToSet forNamespace:namespaceApp2];
  95. // Check config for first app.
  96. NSDictionary *fetchedConfig = _configContent.fetchedConfig;
  97. XCTAssertNotNil(fetchedConfig[namespaceApp1][@"key1"]);
  98. XCTAssertEqualObjects([fetchedConfig[namespaceApp1][@"key1"] stringValue], @"value1");
  99. XCTAssertNotNil(fetchedConfig[namespaceApp1][@"key2"]);
  100. XCTAssertEqualObjects([fetchedConfig[namespaceApp1][@"key2"] stringValue], @"value2");
  101. // Check config for second app.
  102. fetchedConfig = _configContent.fetchedConfig;
  103. XCTAssertNotNil(fetchedConfig[namespaceApp2][@"key11"]);
  104. XCTAssertEqualObjects([fetchedConfig[namespaceApp2][@"key11"] stringValue], @"value11");
  105. XCTAssertNotNil(fetchedConfig[namespaceApp2][@"key21"]);
  106. XCTAssertEqualObjects([fetchedConfig[namespaceApp2][@"key21"] stringValue], @"value21");
  107. }
  108. /// Standard test case of receiving updated config from fetch.
  109. - (void)testUpdateConfigContentWithResponse {
  110. NSMutableDictionary *configToSet =
  111. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  112. NSDictionary *entries = @{@"key1" : @"value1", @"key2" : @"value2"};
  113. [configToSet setValue:entries forKey:@"entries"];
  114. [_configContent updateConfigContentWithResponse:configToSet
  115. forNamespace:FIRNamespaceGoogleMobilePlatform];
  116. NSDictionary *fetchedConfig = _configContent.fetchedConfig;
  117. XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"]);
  118. XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"] stringValue],
  119. @"value1");
  120. XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"]);
  121. XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"] stringValue],
  122. @"value2");
  123. }
  124. /// Verify that fetchedConfig is overwritten for a new fetch call.
  125. - (void)testUpdateConfigContentWithStatusUpdateWithDifferentKeys {
  126. NSMutableDictionary *configToSet =
  127. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  128. NSDictionary *entries = @{@"key1" : @"value1"};
  129. [configToSet setValue:entries forKey:@"entries"];
  130. [_configContent updateConfigContentWithResponse:configToSet
  131. forNamespace:FIRNamespaceGoogleMobilePlatform];
  132. configToSet = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  133. entries = @{@"key2" : @"value2", @"key3" : @"value3"};
  134. [configToSet setValue:entries forKey:@"entries"];
  135. [_configContent updateConfigContentWithResponse:configToSet
  136. forNamespace:FIRNamespaceGoogleMobilePlatform];
  137. NSDictionary *fetchedConfig = _configContent.fetchedConfig;
  138. XCTAssertNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"]);
  139. XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"]);
  140. XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"] stringValue],
  141. @"value2");
  142. XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key3"]);
  143. XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key3"] stringValue],
  144. @"value3");
  145. }
  146. /// Verify fetchedConfig is available across different namespaces.
  147. - (void)testUpdateConfigContentWithStatusUpdateWithDifferentNamespaces {
  148. NSMutableDictionary *configToSet =
  149. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  150. NSMutableDictionary *configToSet2 =
  151. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  152. NSDictionary *entries = @{@"key1" : @"value1"};
  153. NSDictionary *entries2 = @{@"key2" : @"value2"};
  154. [configToSet setValue:entries forKey:@"entries"];
  155. [configToSet2 setValue:entries2 forKey:@"entries"];
  156. [_configContent updateConfigContentWithResponse:configToSet forNamespace:@"namespace_1"];
  157. [_configContent updateConfigContentWithResponse:configToSet2 forNamespace:@"namespace_2"];
  158. [_configContent updateConfigContentWithResponse:configToSet forNamespace:@"namespace_3"];
  159. [_configContent updateConfigContentWithResponse:configToSet2 forNamespace:@"namespace_4"];
  160. NSDictionary *fetchedConfig = _configContent.fetchedConfig;
  161. XCTAssertNotNil(fetchedConfig[@"namespace_1"][@"key1"]);
  162. XCTAssertEqualObjects([fetchedConfig[@"namespace_1"][@"key1"] stringValue], @"value1");
  163. XCTAssertNotNil(fetchedConfig[@"namespace_2"][@"key2"]);
  164. XCTAssertEqualObjects([fetchedConfig[@"namespace_2"][@"key2"] stringValue], @"value2");
  165. XCTAssertNotNil(fetchedConfig[@"namespace_3"][@"key1"]);
  166. XCTAssertEqualObjects([fetchedConfig[@"namespace_3"][@"key1"] stringValue], @"value1");
  167. XCTAssertNotNil(fetchedConfig[@"namespace_4"][@"key2"]);
  168. XCTAssertEqualObjects([fetchedConfig[@"namespace_4"][@"key2"] stringValue], @"value2");
  169. }
  170. - (void)skip_testUpdateConfigContentWithStatusNoChange {
  171. // TODO: Add test case once new eTag based logic is implemented.
  172. }
  173. - (void)skip_testUpdateConfigContentWithRemoveNamespaceStatus {
  174. // TODO: Add test case once new eTag based logic is implemented.
  175. }
  176. - (void)skip_testUpdateConfigContentWithEmptyConfig {
  177. // TODO: Add test case once new eTag based logic is implemented.
  178. }
  179. - (void)testCopyFromDictionaryDoesNotUpdateFetchedConfig {
  180. NSMutableDictionary *configToSet =
  181. [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil];
  182. NSDictionary *entries = @{@"key1" : @"value1", @"key2" : @"value2"};
  183. [configToSet setValue:entries forKey:@"entries"];
  184. [_configContent updateConfigContentWithResponse:configToSet forNamespace:@"dummy_namespace"];
  185. NSDictionary *namespaceToConfig = @{
  186. @"dummy_namespace" : @{
  187. @"new_key" : @"new_value",
  188. }
  189. };
  190. [_configContent copyFromDictionary:namespaceToConfig
  191. toSource:RCNDBSourceFetched
  192. forNamespace:@"dummy_namespace"];
  193. XCTAssertEqual(((NSDictionary *)_configContent.fetchedConfig[@"dummy_namespace"]).count, 2);
  194. XCTAssertEqual(_configContent.activeConfig.count, 0);
  195. XCTAssertEqual(_configContent.defaultConfig.count, 0);
  196. }
  197. - (void)testCopyFromDictionaryUpdatesDefaultConfig {
  198. NSDictionary *embeddedDictionary = @{@"default_embedded_key" : @"default_embedded_Value"};
  199. NSData *dataValue = [NSJSONSerialization dataWithJSONObject:embeddedDictionary
  200. options:NSJSONWritingPrettyPrinted
  201. error:nil];
  202. NSDate *now = [NSDate date];
  203. NSError *error;
  204. NSData *JSONData = [NSJSONSerialization dataWithJSONObject:@{@"key1" : @"value1"}
  205. options:0
  206. error:&error];
  207. NSString *JSONString = [[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding];
  208. NSDictionary *namespaceToConfig = @{
  209. @"default_namespace" : @{
  210. @"new_string_key" : @"new_string_value",
  211. @"new_number_key" : @1234,
  212. @"new_data_key" : dataValue,
  213. @"new_date_key" : now,
  214. @"new_json_key" : JSONString
  215. }
  216. };
  217. [_configContent copyFromDictionary:namespaceToConfig
  218. toSource:RCNDBSourceDefault
  219. forNamespace:@"default_namespace"];
  220. NSDictionary *defaultConfig = _configContent.defaultConfig;
  221. XCTAssertEqual(_configContent.fetchedConfig.count, 0);
  222. XCTAssertEqual(_configContent.activeConfig.count, 0);
  223. XCTAssertNotNil(defaultConfig[@"default_namespace"]);
  224. XCTAssertEqual(((NSDictionary *)defaultConfig[@"default_namespace"]).count, 5);
  225. XCTAssertEqualObjects(@"new_string_value",
  226. [defaultConfig[@"default_namespace"][@"new_string_key"] stringValue]);
  227. XCTAssertEqualObjects(
  228. @1234, [((FIRRemoteConfigValue *)defaultConfig[@"default_namespace"][@"new_number_key"])
  229. numberValue]);
  230. NSDictionary<NSString *, NSString *> *sampleJSON = @{@"key1" : @"value1"};
  231. id configJSON = [(defaultConfig[@"default_namespace"][@"new_json_key"]) JSONValue];
  232. XCTAssertTrue([configJSON isKindOfClass:[NSDictionary class]]);
  233. XCTAssertTrue([sampleJSON isKindOfClass:[NSDictionary class]]);
  234. XCTAssertEqualObjects(sampleJSON, (NSDictionary *)configJSON);
  235. XCTAssertEqualObjects(dataValue,
  236. [defaultConfig[@"default_namespace"][@"new_data_key"] dataValue]);
  237. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  238. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  239. NSString *strValueForDate = [dateFormatter stringFromDate:now];
  240. XCTAssertEqualObjects(strValueForDate,
  241. [defaultConfig[@"default_namespace"][@"new_date_key"] stringValue]);
  242. }
  243. - (void)testCopyFromDictionaryUpdatesActiveConfig {
  244. // Active config values must be RCNConfigValue format
  245. NSDictionary *embeddedDictionary = @{@"active_embedded_key" : @"active_embedded_Value"};
  246. NSData *dataValue = [NSJSONSerialization dataWithJSONObject:embeddedDictionary
  247. options:NSJSONWritingPrettyPrinted
  248. error:nil];
  249. NSDictionary *namespaceToConfig = @{
  250. @"dummy_namespace" : @{
  251. @"new_key" : [[FIRRemoteConfigValue alloc] initWithData:dataValue source:-1],
  252. }
  253. };
  254. [_configContent copyFromDictionary:namespaceToConfig
  255. toSource:RCNDBSourceActive
  256. forNamespace:@"dummy_namespace"];
  257. XCTAssertEqual(((NSDictionary *)_configContent.activeConfig[@"dummy_namespace"]).count, 1);
  258. XCTAssertEqual(_configContent.fetchedConfig.count, 0);
  259. XCTAssertEqual(_configContent.defaultConfig.count, 0);
  260. XCTAssertEqualObjects(dataValue,
  261. [_configContent.activeConfig[@"dummy_namespace"][@"new_key"] dataValue]);
  262. }
  263. - (void)testCheckAndWaitForInitialDatabaseLoad {
  264. RCNConfigDBManagerMock *mockDBManager = [[RCNConfigDBManagerMock alloc] init];
  265. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:mockDBManager];
  266. // Check that no one of first three calls of `-checkAndWaitForInitialDatabaseLoad` do not produce
  267. // timeout error <begin>
  268. XCTestExpectation *expectation1 =
  269. [self expectationWithDescription:
  270. @"1st `checkAndWaitForInitialDatabaseLoad` return without timeout"];
  271. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  272. XCTAssertTrue([configContent checkAndWaitForInitialDatabaseLoad]);
  273. [expectation1 fulfill];
  274. });
  275. XCTestExpectation *expectation2 =
  276. [self expectationWithDescription:
  277. @"2nd `checkAndWaitForInitialDatabaseLoad` return without timeout"];
  278. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  279. XCTAssertTrue([configContent checkAndWaitForInitialDatabaseLoad]);
  280. [expectation2 fulfill];
  281. });
  282. XCTAssertTrue([configContent checkAndWaitForInitialDatabaseLoad]);
  283. // Check that both `-load...` methods already completed after 1st wait.
  284. // This make us sure that both `-loadMainWithBundleIdentifier` and
  285. // `-loadPersonalizationWithCompletionHandler` methods synched with
  286. // `-checkAndWaitForInitialDatabaseLoad`.
  287. XCTAssertTrue(mockDBManager.isLoadMainCompleted);
  288. XCTAssertTrue(mockDBManager.isLoadPersonalizationCompleted);
  289. // Check that no one of first three calls of `-checkAndWaitForInitialDatabaseLoad` do not produce
  290. // timeout error <end>.
  291. // This make us sure that there no threads "stucked" on `-checkAndWaitForInitialDatabaseLoad`.
  292. [self waitForExpectationsWithTimeout:0.5 * kDatabaseLoadTimeoutSecs handler:nil];
  293. }
  294. - (void)testConfigUpdate_noChange_emptyResponse {
  295. NSString *namespace = @"test_namespace";
  296. // populate fetched config
  297. NSMutableDictionary *fetchResponse =
  298. [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"} p13nMetadata:nil];
  299. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  300. // active config is the same as fetched config
  301. FIRRemoteConfigValue *value =
  302. [[FIRRemoteConfigValue alloc] initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
  303. source:FIRRemoteConfigSourceRemote];
  304. NSDictionary *namespaceToConfig = @{namespace : @{@"key1" : value}};
  305. [_configContent copyFromDictionary:namespaceToConfig
  306. toSource:RCNDBSourceActive
  307. forNamespace:namespace];
  308. FIRRemoteConfigUpdate *update =
  309. [_configContent getConfigUpdateForNamespace:namespace
  310. withExperimentChanges:[[NSMutableSet alloc] init]];
  311. XCTAssertTrue([update updatedKeys].count == 0);
  312. }
  313. - (void)testConfigUpdate_noParamChange_butExperimentChange {
  314. NSString *namespace = @"test_namespace";
  315. // populate fetched config
  316. NSMutableDictionary *fetchResponse =
  317. [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"} p13nMetadata:nil];
  318. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  319. // active config is the same as fetched config
  320. FIRRemoteConfigValue *value =
  321. [[FIRRemoteConfigValue alloc] initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
  322. source:FIRRemoteConfigSourceRemote];
  323. NSDictionary *namespaceToConfig = @{namespace : @{@"key1" : value}};
  324. [_configContent copyFromDictionary:namespaceToConfig
  325. toSource:RCNDBSourceActive
  326. forNamespace:namespace];
  327. NSMutableSet *experimentKeys = [[NSMutableSet alloc] init];
  328. [experimentKeys addObject:@"key_2"];
  329. FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace
  330. withExperimentChanges:experimentKeys];
  331. XCTAssertTrue([update updatedKeys].count == 1);
  332. XCTAssertTrue([[update updatedKeys] containsObject:@"key_2"]);
  333. }
  334. - (void)testConfigUpdate_paramAdded_returnsNewKey {
  335. NSString *namespace = @"test_namespace";
  336. NSString *newParam = @"key2";
  337. // populate active config
  338. FIRRemoteConfigValue *value =
  339. [[FIRRemoteConfigValue alloc] initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
  340. source:FIRRemoteConfigSourceRemote];
  341. NSDictionary *namespaceToConfig = @{namespace : @{@"key1" : value}};
  342. [_configContent copyFromDictionary:namespaceToConfig
  343. toSource:RCNDBSourceActive
  344. forNamespace:namespace];
  345. // fetch response has new param
  346. NSMutableDictionary *fetchResponse =
  347. [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1", newParam : @"value2"}
  348. p13nMetadata:nil];
  349. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  350. FIRRemoteConfigUpdate *update =
  351. [_configContent getConfigUpdateForNamespace:namespace
  352. withExperimentChanges:[[NSMutableSet alloc] init]];
  353. XCTAssertTrue([update updatedKeys].count == 1);
  354. XCTAssertTrue([[update updatedKeys] containsObject:newParam]);
  355. }
  356. - (void)testConfigUpdate_paramValueChanged_returnsUpdatedKey {
  357. NSString *namespace = @"test_namespace";
  358. NSString *existingParam = @"key1";
  359. NSString *oldValue = @"value1";
  360. NSString *updatedValue = @"value2";
  361. // active config contains old value
  362. FIRRemoteConfigValue *value =
  363. [[FIRRemoteConfigValue alloc] initWithData:[oldValue dataUsingEncoding:NSUTF8StringEncoding]
  364. source:FIRRemoteConfigSourceRemote];
  365. NSDictionary *namespaceToConfig = @{namespace : @{existingParam : value}};
  366. [_configContent copyFromDictionary:namespaceToConfig
  367. toSource:RCNDBSourceActive
  368. forNamespace:namespace];
  369. // fetch response contains updated value
  370. NSMutableDictionary *fetchResponse =
  371. [self createFetchResponseWithConfigEntries:@{existingParam : updatedValue} p13nMetadata:nil];
  372. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  373. FIRRemoteConfigUpdate *update =
  374. [_configContent getConfigUpdateForNamespace:namespace
  375. withExperimentChanges:[[NSMutableSet alloc] init]];
  376. XCTAssertTrue([update updatedKeys].count == 1);
  377. XCTAssertTrue([[update updatedKeys] containsObject:existingParam]);
  378. }
  379. - (void)testConfigUpdate_paramDeleted_returnsDeletedKey {
  380. NSString *namespace = @"test_namespace";
  381. NSString *existingParam = @"key1";
  382. NSString *newParam = @"key2";
  383. NSString *value1 = @"value1";
  384. // populate active config
  385. FIRRemoteConfigValue *value =
  386. [[FIRRemoteConfigValue alloc] initWithData:[value1 dataUsingEncoding:NSUTF8StringEncoding]
  387. source:FIRRemoteConfigSourceRemote];
  388. NSDictionary *namespaceToConfig = @{namespace : @{existingParam : value}};
  389. [_configContent copyFromDictionary:namespaceToConfig
  390. toSource:RCNDBSourceActive
  391. forNamespace:namespace];
  392. // fetch response does not contain existing param
  393. NSMutableDictionary *fetchResponse =
  394. [self createFetchResponseWithConfigEntries:@{newParam : value1} p13nMetadata:nil];
  395. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  396. FIRRemoteConfigUpdate *update =
  397. [_configContent getConfigUpdateForNamespace:namespace
  398. withExperimentChanges:[[NSMutableSet alloc] init]];
  399. XCTAssertTrue([update updatedKeys].count == 2);
  400. XCTAssertTrue([[update updatedKeys] containsObject:existingParam]); // deleted
  401. XCTAssertTrue([[update updatedKeys] containsObject:newParam]); // added
  402. }
  403. - (void)testConfigUpdate_p13nMetadataUpdated_returnsKey {
  404. NSString *namespace = @"test_namespace";
  405. NSString *existingParam = @"key1";
  406. NSString *value1 = @"value1";
  407. NSDictionary *oldMetadata = @{@"arm_index" : @"1"};
  408. NSDictionary *updatedMetadata = @{@"arm_index" : @"2"};
  409. // popuate fetched config
  410. NSMutableDictionary *fetchResponse =
  411. [self createFetchResponseWithConfigEntries:@{existingParam : value1}
  412. p13nMetadata:@{existingParam : oldMetadata}];
  413. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  414. // populate active config with the same content
  415. [_configContent activatePersonalization];
  416. FIRRemoteConfigValue *value =
  417. [[FIRRemoteConfigValue alloc] initWithData:[value1 dataUsingEncoding:NSUTF8StringEncoding]
  418. source:FIRRemoteConfigSourceRemote];
  419. NSDictionary *namespaceToConfig = @{namespace : @{existingParam : value}};
  420. [_configContent copyFromDictionary:namespaceToConfig
  421. toSource:RCNDBSourceActive
  422. forNamespace:namespace];
  423. // fetched response has updated p13n metadata
  424. [fetchResponse setValue:@{existingParam : updatedMetadata}
  425. forKey:RCNFetchResponseKeyPersonalizationMetadata];
  426. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  427. FIRRemoteConfigUpdate *update =
  428. [_configContent getConfigUpdateForNamespace:namespace
  429. withExperimentChanges:[[NSMutableSet alloc] init]];
  430. XCTAssertTrue([update updatedKeys].count == 1);
  431. XCTAssertTrue([[update updatedKeys] containsObject:existingParam]);
  432. }
  433. - (void)testConfigUpdate_valueSourceChanged_returnsKey {
  434. NSString *namespace = @"test_namespace";
  435. NSString *existingParam = @"key1";
  436. NSString *value1 = @"value1";
  437. // set default config
  438. FIRRemoteConfigValue *value =
  439. [[FIRRemoteConfigValue alloc] initWithData:[value1 dataUsingEncoding:NSUTF8StringEncoding]
  440. source:FIRRemoteConfigSourceDefault];
  441. NSDictionary *namespaceToConfig = @{namespace : @{existingParam : value}};
  442. [_configContent copyFromDictionary:namespaceToConfig
  443. toSource:RCNDBSourceDefault
  444. forNamespace:namespace];
  445. // fetch response contains same key->value
  446. NSMutableDictionary *fetchResponse =
  447. [self createFetchResponseWithConfigEntries:@{existingParam : value1} p13nMetadata:nil];
  448. [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace];
  449. FIRRemoteConfigUpdate *update =
  450. [_configContent getConfigUpdateForNamespace:namespace
  451. withExperimentChanges:[[NSMutableSet alloc] init]];
  452. XCTAssertTrue([update updatedKeys].count == 1);
  453. XCTAssertTrue([[update updatedKeys] containsObject:existingParam]);
  454. }
  455. #pragma mark - Test Helpers
  456. - (NSMutableDictionary *)createFetchResponseWithConfigEntries:(NSDictionary *)config
  457. p13nMetadata:(NSDictionary *)metadata {
  458. NSMutableDictionary *fetchResponse = [[NSMutableDictionary alloc]
  459. initWithObjectsAndKeys:RCNFetchResponseKeyStateUpdate, RCNFetchResponseKeyState, nil];
  460. if (config) {
  461. [fetchResponse setValue:config forKey:RCNFetchResponseKeyEntries];
  462. }
  463. if (metadata) {
  464. [fetchResponse setValue:metadata forKey:RCNFetchResponseKeyPersonalizationMetadata];
  465. }
  466. return fetchResponse;
  467. }
  468. @end