RCNConfigContentTest.m 33 KB

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