RCNConfigTest.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import "FirebaseRemoteConfig/Sources/Protos/wireless/android/config/proto/Config.pbobjc.h"
  18. #import <FirebaseInstanceID/FIRInstanceID+Private.h>
  19. #import <FirebaseInstanceID/FIRInstanceIDCheckinPreferences.h>
  20. #import <OCMock/OCMock.h>
  21. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
  23. #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
  24. #import "FirebaseRemoteConfig/Sources/RCNConfigFetch.h"
  25. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  26. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  27. static NSString *const RCNFakeSenderID = @"855865492447";
  28. static NSString *const RCNFakeToken = @"ctToAh17Exk:"
  29. @"APA91bFpX1aucYk5ONWt3MxyVxTyDKV8PKjaY2X3DPCZOOTHIBNf3ybxV5"
  30. @"aMQ7G8zUTrduobNLEUoSvGsncthR27gDF_qqZELqp2eEi1BL8k_"
  31. @"4AkeP2dLBq4f8MvuJTOEv2P5ChTdByr";
  32. static NSString *const RCNFakeDeviceID = @"4421690866479820589";
  33. static NSString *const RCNFakeSecretToken = @"6377571288467228941";
  34. @interface RCNConfigFetch (ForTest)
  35. // Exposes fetching user property method in the category.
  36. //- (void)fetchWithUserPropertiesCompletionHandler:(RCNAnalyticsUserPropertiesCompletion)block;
  37. - (void)refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:
  38. (FIRRemoteConfigFetchCompletion)completionHandler;
  39. - (void)fetchCheckinInfoWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler;
  40. @end
  41. @interface RCNConfigTest : XCTestCase {
  42. NSTimeInterval _expectationTimeout;
  43. RCNConfigSettings *_settings;
  44. RCNConfigFetchResponse *_response;
  45. RCNConfigContent *_configContent;
  46. RCNConfigExperiment *_experiment;
  47. RCNConfigFetch *_configFetch;
  48. dispatch_queue_t _queue;
  49. }
  50. @end
  51. @implementation RCNConfigTest
  52. - (void)setUp {
  53. [super setUp];
  54. _expectationTimeout = 1.0;
  55. // Mock the singleton to an instance that is reset for each unit test
  56. _configContent = [[RCNConfigContent alloc] initWithDBManager:nil];
  57. _settings = [[RCNConfigSettings alloc] initWithDatabaseManager:nil];
  58. _experiment = [[RCNConfigExperiment alloc] initWithDBManager:nil];
  59. _queue = dispatch_queue_create("com.google.GoogleConfigService.FIRRemoteConfigTest",
  60. DISPATCH_QUEUE_CONCURRENT);
  61. RCNConfigFetch *fetcher = [[RCNConfigFetch alloc] initWithContent:_configContent
  62. settings:_settings
  63. experiment:_experiment
  64. queue:_queue];
  65. _configFetch = OCMPartialMock(fetcher);
  66. // Fake a response with a default namespace and a custom namespace.
  67. NSDictionary *namespaceToConfig = @{
  68. FIRNamespaceGoogleMobilePlatform : @{@"key1" : @"value1", @"key2" : @"value2"},
  69. FIRNamespaceGooglePlayPlatform : @{@"playerID" : @"36", @"gameLevel" : @"87"},
  70. };
  71. _response =
  72. [RCNTestUtilities responseWithNamespaceToConfig:namespaceToConfig
  73. statusArray:@[
  74. @(RCNAppNamespaceConfigTable_NamespaceStatus_Update),
  75. @(RCNAppNamespaceConfigTable_NamespaceStatus_Update)
  76. ]];
  77. NSData *responseData = [NSData gtm_dataByDeflatingData:_response.data error:nil];
  78. // Mock successful network fetches with an empty config response.
  79. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  80. completion(responseData, nil, nil);
  81. };
  82. [RCNConfigFetch setGlobalTestBlock:testBlock];
  83. // Mocks the user property fetch with a predefined dictionary.
  84. NSDictionary *userProperties = @{@"userProperty1" : @"100", @"userProperty2" : @"200"};
  85. OCMStub([_configFetch
  86. fetchWithUserPropertiesCompletionHandler:([OCMArg invokeBlockWithArgs:userProperties, nil])]);
  87. }
  88. - (void)tearDown {
  89. [RCNConfigFetch setGlobalTestBlock:nil];
  90. [super tearDown];
  91. }
  92. - (void)testInitMethod {
  93. RCNConfigFetch *fetcher = [[RCNConfigFetch alloc] init];
  94. XCTAssertNotNil(fetcher);
  95. }
  96. - (void)testFetchAllConfigsFailedWithoutCachedResult {
  97. XCTestExpectation *fetchFailedExpectation = [self
  98. expectationWithDescription:@"Test first config fetch failed without any cached result."];
  99. // Mock a failed network fetch.
  100. NSError *error = [NSError errorWithDomain:@"testDomain" code:1 userInfo:nil];
  101. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  102. completion(nil, nil, error);
  103. };
  104. [RCNConfigFetch setGlobalTestBlock:testBlock];
  105. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  106. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  107. XCTAssertNotNil(error);
  108. XCTAssertEqual(self->_configContent.fetchedConfig.count,
  109. 0); // There's no cached result yet since this is the first fetch.
  110. XCTAssertEqual(status, FIRRemoteConfigFetchStatusFailure,
  111. @"Fetch config failed, there is no cached config result yet. Status must "
  112. @"equal to FIRRemoteConfigFetchStatusNotAvailable.");
  113. XCTAssertEqual(self->_settings.expirationInSeconds, 0,
  114. @"expirationInSeconds is set successfully during fetch.");
  115. XCTAssertEqual(self->_settings.lastFetchTimeInterval, 0,
  116. @"last fetch time interval should not be set.");
  117. XCTAssertEqual(self->_settings.lastApplyTimeInterval, 0,
  118. @"last apply time interval should not be set.");
  119. [fetchFailedExpectation fulfill];
  120. };
  121. [_configFetch fetchAllConfigsWithExpirationDuration:0
  122. completionHandler:fetchAllConfigsCompletion];
  123. [self waitForExpectationsWithTimeout:_expectationTimeout
  124. handler:^(NSError *error) {
  125. XCTAssertNil(error);
  126. }];
  127. }
  128. - (void)testFetchAllConfigsSuccessfully {
  129. XCTestExpectation *fetchAllConfigsExpectation =
  130. [self expectationWithDescription:@"Test fetch all configs successfully."];
  131. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  132. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  133. XCTAssertNil(error);
  134. NSDictionary *result = self->_configContent.fetchedConfig;
  135. XCTAssertNotNil(result);
  136. [self checkConfigResult:result
  137. withNamespace:FIRNamespaceGoogleMobilePlatform
  138. key:@"key1"
  139. value:@"value1"];
  140. [self checkConfigResult:result
  141. withNamespace:FIRNamespaceGoogleMobilePlatform
  142. key:@"key2"
  143. value:@"value2"];
  144. [self checkConfigResult:result
  145. withNamespace:FIRNamespaceGooglePlayPlatform
  146. key:@"playerID"
  147. value:@"36"];
  148. [self checkConfigResult:result
  149. withNamespace:FIRNamespaceGooglePlayPlatform
  150. key:@"gameLevel"
  151. value:@"87"];
  152. XCTAssertEqual(self->_settings.expirationInSeconds, 43200,
  153. @"expirationInSeconds is set successfully during fetch.");
  154. XCTAssertGreaterThan(self->_settings.lastFetchTimeInterval, 0,
  155. @"last fetch time interval should be set.");
  156. XCTAssertEqual(self->_settings.lastApplyTimeInterval, 0,
  157. @"last apply time interval should not be set.");
  158. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  159. @"Callback of first successful config "
  160. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccess.");
  161. [fetchAllConfigsExpectation fulfill];
  162. };
  163. [_configFetch fetchAllConfigsWithExpirationDuration:43200
  164. completionHandler:fetchAllConfigsCompletion];
  165. [self waitForExpectationsWithTimeout:_expectationTimeout
  166. handler:^(NSError *error) {
  167. XCTAssertNil(error);
  168. }];
  169. }
  170. - (void)testFetchConfigInCachedResults {
  171. XCTestExpectation *fetchConfigExpectation =
  172. [self expectationWithDescription:@"Test fetch config within expiration duration, meaning "
  173. @"use fresh cached result instead of fetching from server."];
  174. FIRRemoteConfigFetchCompletion firstFetchCompletion =
  175. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  176. XCTAssertNil(error);
  177. FIRRemoteConfigFetchCompletion secondFetchCompletion = ^void(
  178. FIRRemoteConfigFetchStatus status, NSError *error) {
  179. XCTAssertNil(error);
  180. NSDictionary *result = self->_configContent.fetchedConfig;
  181. XCTAssertNotNil(result);
  182. [self checkConfigResult:result
  183. withNamespace:FIRNamespaceGoogleMobilePlatform
  184. key:@"key1"
  185. value:@"value1"];
  186. [self checkConfigResult:result
  187. withNamespace:FIRNamespaceGoogleMobilePlatform
  188. key:@"key2"
  189. value:@"value2"];
  190. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  191. "Config fetch's expiration duration is 43200 seconds, which means the"
  192. "config cached data hasn't expired. Return cached result. Status must be "
  193. "FIRRemoteConfigFetchStatusSuccess.");
  194. [fetchConfigExpectation fulfill];
  195. };
  196. [_configFetch fetchAllConfigsWithExpirationDuration:43200
  197. completionHandler:secondFetchCompletion];
  198. };
  199. [_configFetch fetchAllConfigsWithExpirationDuration:43200 completionHandler:firstFetchCompletion];
  200. [self waitForExpectationsWithTimeout:_expectationTimeout
  201. handler:^(NSError *error) {
  202. XCTAssertNil(error);
  203. }];
  204. }
  205. - (void)testFetchFailedWithCachedResult {
  206. XCTestExpectation *fetchFailedExpectation =
  207. [self expectationWithDescription:@"Test fetch failed from server, use cached result."];
  208. // Mock a failed network fetch.
  209. NSError *error = [NSError errorWithDomain:@"testDomain" code:1 userInfo:nil];
  210. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  211. completion(nil, nil, error);
  212. };
  213. [RCNConfigFetch setGlobalTestBlock:testBlock];
  214. // Mock previous fetch succeed with cached data.
  215. [_settings updateMetadata:YES namespaceToDigest:nil];
  216. [_configContent updateConfigContentWithResponse:[_response copy]];
  217. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  218. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  219. XCTAssertNotNil(error);
  220. NSDictionary *result = self->_configContent.fetchedConfig;
  221. XCTAssertNotNil(result);
  222. [self checkConfigResult:result
  223. withNamespace:FIRNamespaceGoogleMobilePlatform
  224. key:@"key1"
  225. value:@"value1"];
  226. [self checkConfigResult:result
  227. withNamespace:FIRNamespaceGoogleMobilePlatform
  228. key:@"key2"
  229. value:@"value2"];
  230. [self checkConfigResult:result
  231. withNamespace:FIRNamespaceGooglePlayPlatform
  232. key:@"playerID"
  233. value:@"36"];
  234. [self checkConfigResult:result
  235. withNamespace:FIRNamespaceGooglePlayPlatform
  236. key:@"gameLevel"
  237. value:@"87"];
  238. [fetchFailedExpectation fulfill];
  239. };
  240. // Expiration duration is set to 0, meaning always fetch from server because the cached result
  241. // expired in 0 seconds.
  242. [_configFetch fetchAllConfigsWithExpirationDuration:0
  243. completionHandler:fetchAllConfigsCompletion];
  244. [self waitForExpectationsWithTimeout:_expectationTimeout
  245. handler:^(NSError *error) {
  246. XCTAssertNil(error);
  247. }];
  248. }
  249. - (void)testFetchThrottledWithCachedResult {
  250. XCTestExpectation *fetchAllConfigsExpectation =
  251. [self expectationWithDescription:
  252. @"Test fetch being throttled after exceeding throttling limit, use cached result."];
  253. // Fake a new response with a different custom namespace.
  254. NSDictionary *namespaceToConfig = @{
  255. @"configns:MY_OWN_APP" :
  256. @{@"columnID" : @"28", @"columnName" : @"height", @"columnValue" : @"2"}
  257. };
  258. RCNConfigFetchResponse *newResponse = [RCNTestUtilities
  259. responseWithNamespaceToConfig:namespaceToConfig
  260. statusArray:@[ @(RCNAppNamespaceConfigTable_NamespaceStatus_Update) ]];
  261. // Mock 5 fetches ahead.
  262. for (int i = 0; i < RCNThrottledSuccessFetchCountDefault; i++) {
  263. [_settings updateMetadata:YES namespaceToDigest:nil];
  264. [_configContent updateConfigContentWithResponse:[newResponse copy]];
  265. }
  266. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  267. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  268. XCTAssertNotNil(error);
  269. NSDictionary *result = self->_configContent.fetchedConfig;
  270. XCTAssertNotNil(result); // Cached result is not nil.
  271. [self checkConfigResult:result
  272. withNamespace:@"configns:MY_OWN_APP"
  273. key:@"columnID"
  274. value:@"28"];
  275. [self checkConfigResult:result
  276. withNamespace:@"configns:MY_OWN_APP"
  277. key:@"columnName"
  278. value:@"height"];
  279. [self checkConfigResult:result
  280. withNamespace:@"configns:MY_OWN_APP"
  281. key:@"columnValue"
  282. value:@"2"];
  283. XCTAssertEqual(error.code, (int)FIRRemoteConfigErrorThrottled,
  284. @"Default success throttling rate is 5. Mocked 5 successful fetches from "
  285. @"server, this fetch will be throttled. Status must equal to "
  286. @"FIRRemoteConfigFetchStatusFetchThrottled.");
  287. [fetchAllConfigsExpectation fulfill];
  288. };
  289. [_configFetch fetchAllConfigsWithExpirationDuration:-1
  290. completionHandler:fetchAllConfigsCompletion];
  291. [self waitForExpectationsWithTimeout:_expectationTimeout
  292. handler:^(NSError *error) {
  293. XCTAssertNil(error);
  294. }];
  295. }
  296. - (void)testFetchThrottledWithStaledCachedResult {
  297. XCTestExpectation *fetchAllConfigsExpectation =
  298. [self expectationWithDescription:@"Test fetch being throttled, use staled cache result."];
  299. // Mock 5 fetches ahead.
  300. for (int i = 0; i < RCNThrottledSuccessFetchCountDefault; i++) {
  301. [_settings updateMetadata:YES namespaceToDigest:nil];
  302. [_configContent updateConfigContentWithResponse:[_response copy]];
  303. }
  304. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  305. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  306. XCTAssertNotNil(error);
  307. NSDictionary *result = self->_configContent.fetchedConfig;
  308. XCTAssertNotNil(result);
  309. [self checkConfigResult:result
  310. withNamespace:FIRNamespaceGoogleMobilePlatform
  311. key:@"key1"
  312. value:@"value1"];
  313. [self checkConfigResult:result
  314. withNamespace:FIRNamespaceGoogleMobilePlatform
  315. key:@"key2"
  316. value:@"value2"];
  317. [self checkConfigResult:result
  318. withNamespace:FIRNamespaceGooglePlayPlatform
  319. key:@"playerID"
  320. value:@"36"];
  321. [self checkConfigResult:result
  322. withNamespace:FIRNamespaceGooglePlayPlatform
  323. key:@"gameLevel"
  324. value:@"87"];
  325. XCTAssertEqual(
  326. error.code, FIRRemoteConfigErrorThrottled,
  327. @"Request fetching within throttling time interval, so this fetch will still be "
  328. @"throttled. "
  329. @"However, the App context (custom variables) has changed, meaning the return cached "
  330. @"result is staled. The status must equal to RCNConfigStatusFetchThrottledStale.");
  331. [fetchAllConfigsExpectation fulfill];
  332. };
  333. // Fetch with new custom variables.
  334. [_configFetch fetchAllConfigsWithExpirationDuration:0
  335. completionHandler:fetchAllConfigsCompletion];
  336. [self waitForExpectationsWithTimeout:_expectationTimeout
  337. handler:^(NSError *error) {
  338. XCTAssertNil(error);
  339. }];
  340. }
  341. - (void)testRefreshInstanceIDToken {
  342. XCTestExpectation *refreshTokenExpectation =
  343. [self expectationWithDescription:@"Test refresh Instance ID token."];
  344. FIRInstanceID *instanceID = [FIRInstanceID instanceID];
  345. _settings.senderID = RCNFakeSenderID;
  346. id mock = OCMPartialMock(instanceID);
  347. OCMStub([mock
  348. tokenWithAuthorizedEntity:RCNFakeSenderID
  349. scope:@"*"
  350. options:nil
  351. handler:([OCMArg invokeBlockWithArgs:RCNFakeToken, [NSNull null], nil])]);
  352. FIRRemoteConfigFetchCompletion completionHandler =
  353. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  354. XCTAssertEqualObjects(RCNFakeToken, _settings.configInstanceIDToken);
  355. [mock stopMocking];
  356. [refreshTokenExpectation fulfill];
  357. };
  358. [_configFetch refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:completionHandler];
  359. [self waitForExpectationsWithTimeout:_expectationTimeout
  360. handler:^(NSError *error) {
  361. XCTAssertNil(error);
  362. }];
  363. }
  364. - (void)testRefreshInstanceIDTokenWithError {
  365. XCTestExpectation *refreshTokenExpectation =
  366. [self expectationWithDescription:@"Test refresh Instance ID token."];
  367. FIRInstanceID *instanceID = [FIRInstanceID instanceID];
  368. _settings.senderID = RCNFakeSenderID;
  369. NSError *error = [NSError errorWithDomain:@"errorDomain" code:20 userInfo:nil];
  370. id mock = OCMPartialMock(instanceID);
  371. OCMStub([mock
  372. tokenWithAuthorizedEntity:RCNFakeSenderID
  373. scope:@"*"
  374. options:nil
  375. handler:([OCMArg invokeBlockWithArgs:[NSNull null], error, nil])]);
  376. FIRRemoteConfigFetchCompletion completionHandler =
  377. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  378. XCTAssertNil(_settings.configInstanceIDToken);
  379. XCTAssertNil(_settings.configInstanceID);
  380. [mock stopMocking];
  381. [refreshTokenExpectation fulfill];
  382. };
  383. [_configFetch refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:completionHandler];
  384. [self waitForExpectationsWithTimeout:_expectationTimeout
  385. handler:^(NSError *error) {
  386. XCTAssertNil(error);
  387. }];
  388. }
  389. - (void)testFetchCheckin {
  390. XCTestExpectation *refreshTokenExpectation =
  391. [self expectationWithDescription:@"Test fetch checkin."];
  392. FIRInstanceID *instanceID = [FIRInstanceID instanceID];
  393. _settings.senderID = RCNFakeSenderID;
  394. id mock = OCMPartialMock(instanceID);
  395. FIRInstanceIDCheckinPreferences *preferences =
  396. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:RCNFakeDeviceID
  397. secretToken:RCNFakeSecretToken];
  398. OCMStub([mock
  399. fetchCheckinInfoWithHandler:([OCMArg invokeBlockWithArgs:preferences, [NSNull null], nil])]);
  400. FIRRemoteConfigFetchCompletion completionHandler =
  401. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  402. XCTAssertEqualObjects(RCNFakeDeviceID, _settings.deviceAuthID);
  403. XCTAssertEqualObjects(RCNFakeSecretToken, _settings.secretToken);
  404. [mock stopMocking];
  405. [refreshTokenExpectation fulfill];
  406. };
  407. [_configFetch refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:completionHandler];
  408. [self waitForExpectationsWithTimeout:_expectationTimeout
  409. handler:^(NSError *error) {
  410. XCTAssertNil(error);
  411. }];
  412. }
  413. #pragma mark - helpers
  414. - (void)checkConfigResult:(NSDictionary *)result
  415. withNamespace:(NSString *)namespace
  416. key:(NSString *)key
  417. value:(NSString *)value {
  418. if (result[namespace]) {
  419. FIRRemoteConfigValue *configValue = result[namespace][key];
  420. XCTAssertEqualObjects(configValue.stringValue, value,
  421. @"Config result missing the key value pair.");
  422. } else {
  423. XCTAssertNotNil(result[namespace], @"Config result missing the namespace.");
  424. }
  425. }
  426. @end