RCNConfigTest.m 21 KB

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