RCNConfigContentTest.m 31 KB

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