RCNConfigTest.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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/RCNConfigFetch.h"
  19. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  20. #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
  21. #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  23. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  24. static NSString *const RCNFakeSenderID = @"855865492447";
  25. static NSString *const RCNFakeToken = @"ctToAh17Exk:"
  26. @"APA91bFpX1aucYk5ONWt3MxyVxTyDKV8PKjaY2X3DPCZOOTHIBNf3ybxV5"
  27. @"aMQ7G8zUTrduobNLEUoSvGsncthR27gDF_qqZELqp2eEi1BL8k_"
  28. @"4AkeP2dLBq4f8MvuJTOEv2P5ChTdByr";
  29. static NSString *const RCNFakeDeviceID = @"4421690866479820589";
  30. static NSString *const RCNFakeSecretToken = @"6377571288467228941";
  31. @interface RCNConfigFetch (ForTest)
  32. // Exposes fetching user property method in the category.
  33. //- (void)fetchWithUserPropertiesCompletionHandler:(RCNAnalyticsUserPropertiesCompletion)block;
  34. - (void)refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:
  35. (FIRRemoteConfigFetchCompletion)completionHandler;
  36. - (void)fetchCheckinInfoWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler;
  37. @end
  38. @interface RCNConfigTest : XCTestCase {
  39. NSTimeInterval _expectationTimeout;
  40. RCNConfigSettings *_settings;
  41. RCNConfigFetchResponse *_response;
  42. RCNConfigContent *_configContent;
  43. RCNConfigExperiment *_experiment;
  44. RCNConfigFetch *_configFetch;
  45. dispatch_queue_t _queue;
  46. }
  47. @end
  48. @implementation RCNConfigTest
  49. - (void)setUp {
  50. [super setUp];
  51. _expectationTimeout = 1.0;
  52. // Mock the singleton to an instance that is reset for each unit test
  53. _configContent = [[RCNConfigContent alloc] initWithDBManager:nil];
  54. _settings = [[RCNConfigSettings alloc] initWithDatabaseManager:nil];
  55. _experiment = [[RCNConfigExperiment alloc] initWithDBManager:nil];
  56. _queue = dispatch_queue_create("com.google.GoogleConfigService.FIRRemoteConfigTest",
  57. DISPATCH_QUEUE_CONCURRENT);
  58. RCNConfigFetch *fetcher = [[RCNConfigFetch alloc] initWithContent:_configContent
  59. settings:_settings
  60. experiment:_experiment
  61. queue:_queue];
  62. _configFetch = OCMPartialMock(fetcher);
  63. // Fake a response with a default namespace and a custom namespace.
  64. NSDictionary *namespaceToConfig = @{
  65. FIRNamespaceGoogleMobilePlatform : @{@"key1" : @"value1", @"key2" : @"value2"},
  66. FIRNamespaceGooglePlayPlatform : @{@"playerID" : @"36", @"gameLevel" : @"87"},
  67. };
  68. _response =
  69. [RCNTestUtilities responseWithNamespaceToConfig:namespaceToConfig
  70. statusArray:@[
  71. @(RCNAppNamespaceConfigTable_NamespaceStatus_Update),
  72. @(RCNAppNamespaceConfigTable_NamespaceStatus_Update)
  73. ]];
  74. NSData *responseData = [NSData gtm_dataByDeflatingData:_response.data error:nil];
  75. // Mock successful network fetches with an empty config response.
  76. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  77. completion(responseData, nil, nil);
  78. };
  79. [RCNConfigFetch setGlobalTestBlock:testBlock];
  80. // Mocks the user property fetch with a predefined dictionary.
  81. NSDictionary *userProperties = @{@"userProperty1" : @"100", @"userProperty2" : @"200"};
  82. OCMStub([_configFetch
  83. fetchWithUserPropertiesCompletionHandler:([OCMArg invokeBlockWithArgs:userProperties, nil])]);
  84. }
  85. - (void)tearDown {
  86. [RCNConfigFetch setGlobalTestBlock:nil];
  87. [super tearDown];
  88. }
  89. - (void)testInitMethod {
  90. RCNConfigFetch *fetcher = [[RCNConfigFetch alloc] init];
  91. XCTAssertNotNil(fetcher);
  92. }
  93. - (void)testFetchAllConfigsFailedWithoutCachedResult {
  94. XCTestExpectation *fetchFailedExpectation = [self
  95. expectationWithDescription:@"Test first config fetch failed without any cached result."];
  96. // Mock a failed network fetch.
  97. NSError *error = [NSError errorWithDomain:@"testDomain" code:1 userInfo:nil];
  98. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  99. completion(nil, nil, error);
  100. };
  101. [RCNConfigFetch setGlobalTestBlock:testBlock];
  102. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  103. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  104. XCTAssertNotNil(error);
  105. XCTAssertEqual(self->_configContent.fetchedConfig.count,
  106. 0); // There's no cached result yet since this is the first fetch.
  107. XCTAssertEqual(status, FIRRemoteConfigFetchStatusFailure,
  108. @"Fetch config failed, there is no cached config result yet. Status must "
  109. @"equal to FIRRemoteConfigFetchStatusNotAvailable.");
  110. XCTAssertEqual(self->_settings.expirationInSeconds, 0,
  111. @"expirationInSeconds is set successfully during fetch.");
  112. XCTAssertEqual(self->_settings.lastFetchTimeInterval, 0,
  113. @"last fetch time interval should not be set.");
  114. XCTAssertEqual(self->_settings.lastApplyTimeInterval, 0,
  115. @"last apply time interval should not be set.");
  116. [fetchFailedExpectation fulfill];
  117. };
  118. [_configFetch fetchAllConfigsWithExpirationDuration:0
  119. completionHandler:fetchAllConfigsCompletion];
  120. [self waitForExpectationsWithTimeout:_expectationTimeout
  121. handler:^(NSError *error) {
  122. XCTAssertNil(error);
  123. }];
  124. }
  125. - (void)testFetchAllConfigsSuccessfully {
  126. XCTestExpectation *fetchAllConfigsExpectation =
  127. [self expectationWithDescription:@"Test fetch all configs successfully."];
  128. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  129. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  130. XCTAssertNil(error);
  131. NSDictionary *result = self->_configContent.fetchedConfig;
  132. XCTAssertNotNil(result);
  133. [self checkConfigResult:result
  134. withNamespace:FIRNamespaceGoogleMobilePlatform
  135. key:@"key1"
  136. value:@"value1"];
  137. [self checkConfigResult:result
  138. withNamespace:FIRNamespaceGoogleMobilePlatform
  139. key:@"key2"
  140. value:@"value2"];
  141. [self checkConfigResult:result
  142. withNamespace:FIRNamespaceGooglePlayPlatform
  143. key:@"playerID"
  144. value:@"36"];
  145. [self checkConfigResult:result
  146. withNamespace:FIRNamespaceGooglePlayPlatform
  147. key:@"gameLevel"
  148. value:@"87"];
  149. XCTAssertEqual(self->_settings.expirationInSeconds, 43200,
  150. @"expirationInSeconds is set successfully during fetch.");
  151. XCTAssertGreaterThan(self->_settings.lastFetchTimeInterval, 0,
  152. @"last fetch time interval should be set.");
  153. XCTAssertEqual(self->_settings.lastApplyTimeInterval, 0,
  154. @"last apply time interval should not be set.");
  155. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  156. @"Callback of first successful config "
  157. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccess.");
  158. [fetchAllConfigsExpectation fulfill];
  159. };
  160. [_configFetch fetchAllConfigsWithExpirationDuration:43200
  161. completionHandler:fetchAllConfigsCompletion];
  162. [self waitForExpectationsWithTimeout:_expectationTimeout
  163. handler:^(NSError *error) {
  164. XCTAssertNil(error);
  165. }];
  166. }
  167. - (void)testFetchConfigInCachedResults {
  168. XCTestExpectation *fetchConfigExpectation =
  169. [self expectationWithDescription:@"Test fetch config within expiration duration, meaning "
  170. @"use fresh cached result instead of fetching from server."];
  171. FIRRemoteConfigFetchCompletion firstFetchCompletion =
  172. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  173. XCTAssertNil(error);
  174. FIRRemoteConfigFetchCompletion secondFetchCompletion = ^void(
  175. FIRRemoteConfigFetchStatus status, NSError *error) {
  176. XCTAssertNil(error);
  177. NSDictionary *result = self->_configContent.fetchedConfig;
  178. XCTAssertNotNil(result);
  179. [self checkConfigResult:result
  180. withNamespace:FIRNamespaceGoogleMobilePlatform
  181. key:@"key1"
  182. value:@"value1"];
  183. [self checkConfigResult:result
  184. withNamespace:FIRNamespaceGoogleMobilePlatform
  185. key:@"key2"
  186. value:@"value2"];
  187. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  188. "Config fetch's expiration duration is 43200 seconds, which means the"
  189. "config cached data hasn't expired. Return cached result. Status must be "
  190. "FIRRemoteConfigFetchStatusSuccess.");
  191. [fetchConfigExpectation fulfill];
  192. };
  193. [_configFetch fetchAllConfigsWithExpirationDuration:43200
  194. completionHandler:secondFetchCompletion];
  195. };
  196. [_configFetch fetchAllConfigsWithExpirationDuration:43200 completionHandler:firstFetchCompletion];
  197. [self waitForExpectationsWithTimeout:_expectationTimeout
  198. handler:^(NSError *error) {
  199. XCTAssertNil(error);
  200. }];
  201. }
  202. - (void)testFetchFailedWithCachedResult {
  203. XCTestExpectation *fetchFailedExpectation =
  204. [self expectationWithDescription:@"Test fetch failed from server, use cached result."];
  205. // Mock a failed network fetch.
  206. NSError *error = [NSError errorWithDomain:@"testDomain" code:1 userInfo:nil];
  207. RCNConfigFetcherTestBlock testBlock = ^(RCNConfigFetcherCompletion completion) {
  208. completion(nil, nil, error);
  209. };
  210. [RCNConfigFetch setGlobalTestBlock:testBlock];
  211. // Mock previous fetch succeed with cached data.
  212. [_settings updateMetadata:YES namespaceToDigest:nil];
  213. [_configContent updateConfigContentWithResponse:[_response copy]];
  214. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  215. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  216. XCTAssertNotNil(error);
  217. NSDictionary *result = self->_configContent.fetchedConfig;
  218. XCTAssertNotNil(result);
  219. [self checkConfigResult:result
  220. withNamespace:FIRNamespaceGoogleMobilePlatform
  221. key:@"key1"
  222. value:@"value1"];
  223. [self checkConfigResult:result
  224. withNamespace:FIRNamespaceGoogleMobilePlatform
  225. key:@"key2"
  226. value:@"value2"];
  227. [self checkConfigResult:result
  228. withNamespace:FIRNamespaceGooglePlayPlatform
  229. key:@"playerID"
  230. value:@"36"];
  231. [self checkConfigResult:result
  232. withNamespace:FIRNamespaceGooglePlayPlatform
  233. key:@"gameLevel"
  234. value:@"87"];
  235. [fetchFailedExpectation fulfill];
  236. };
  237. // Expiration duration is set to 0, meaning always fetch from server because the cached result
  238. // expired in 0 seconds.
  239. [_configFetch fetchAllConfigsWithExpirationDuration:0
  240. completionHandler:fetchAllConfigsCompletion];
  241. [self waitForExpectationsWithTimeout:_expectationTimeout
  242. handler:^(NSError *error) {
  243. XCTAssertNil(error);
  244. }];
  245. }
  246. - (void)testFetchThrottledWithCachedResult {
  247. XCTestExpectation *fetchAllConfigsExpectation =
  248. [self expectationWithDescription:
  249. @"Test fetch being throttled after exceeding throttling limit, use cached result."];
  250. // Fake a new response with a different custom namespace.
  251. NSDictionary *namespaceToConfig = @{
  252. @"configns:MY_OWN_APP" :
  253. @{@"columnID" : @"28", @"columnName" : @"height", @"columnValue" : @"2"}
  254. };
  255. RCNConfigFetchResponse *newResponse = [RCNTestUtilities
  256. responseWithNamespaceToConfig:namespaceToConfig
  257. statusArray:@[ @(RCNAppNamespaceConfigTable_NamespaceStatus_Update) ]];
  258. // Mock 5 fetches ahead.
  259. for (int i = 0; i < RCNThrottledSuccessFetchCountDefault; i++) {
  260. [_settings updateMetadata:YES namespaceToDigest:nil];
  261. [_configContent updateConfigContentWithResponse:[newResponse copy]];
  262. }
  263. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  264. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  265. XCTAssertNotNil(error);
  266. NSDictionary *result = self->_configContent.fetchedConfig;
  267. XCTAssertNotNil(result); // Cached result is not nil.
  268. [self checkConfigResult:result
  269. withNamespace:@"configns:MY_OWN_APP"
  270. key:@"columnID"
  271. value:@"28"];
  272. [self checkConfigResult:result
  273. withNamespace:@"configns:MY_OWN_APP"
  274. key:@"columnName"
  275. value:@"height"];
  276. [self checkConfigResult:result
  277. withNamespace:@"configns:MY_OWN_APP"
  278. key:@"columnValue"
  279. value:@"2"];
  280. XCTAssertEqual(error.code, (int)FIRRemoteConfigErrorThrottled,
  281. @"Default success throttling rate is 5. Mocked 5 successful fetches from "
  282. @"server, this fetch will be throttled. Status must equal to "
  283. @"FIRRemoteConfigFetchStatusFetchThrottled.");
  284. [fetchAllConfigsExpectation fulfill];
  285. };
  286. [_configFetch fetchAllConfigsWithExpirationDuration:-1
  287. completionHandler:fetchAllConfigsCompletion];
  288. [self waitForExpectationsWithTimeout:_expectationTimeout
  289. handler:^(NSError *error) {
  290. XCTAssertNil(error);
  291. }];
  292. }
  293. - (void)testFetchThrottledWithStaledCachedResult {
  294. XCTestExpectation *fetchAllConfigsExpectation =
  295. [self expectationWithDescription:@"Test fetch being throttled, use staled cache result."];
  296. // Mock 5 fetches ahead.
  297. for (int i = 0; i < RCNThrottledSuccessFetchCountDefault; i++) {
  298. [_settings updateMetadata:YES namespaceToDigest:nil];
  299. [_configContent updateConfigContentWithResponse:[_response copy]];
  300. }
  301. FIRRemoteConfigFetchCompletion fetchAllConfigsCompletion =
  302. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  303. XCTAssertNotNil(error);
  304. NSDictionary *result = self->_configContent.fetchedConfig;
  305. XCTAssertNotNil(result);
  306. [self checkConfigResult:result
  307. withNamespace:FIRNamespaceGoogleMobilePlatform
  308. key:@"key1"
  309. value:@"value1"];
  310. [self checkConfigResult:result
  311. withNamespace:FIRNamespaceGoogleMobilePlatform
  312. key:@"key2"
  313. value:@"value2"];
  314. [self checkConfigResult:result
  315. withNamespace:FIRNamespaceGooglePlayPlatform
  316. key:@"playerID"
  317. value:@"36"];
  318. [self checkConfigResult:result
  319. withNamespace:FIRNamespaceGooglePlayPlatform
  320. key:@"gameLevel"
  321. value:@"87"];
  322. XCTAssertEqual(
  323. error.code, FIRRemoteConfigErrorThrottled,
  324. @"Request fetching within throttling time interval, so this fetch will still be "
  325. @"throttled. "
  326. @"However, the App context (custom variables) has changed, meaning the return cached "
  327. @"result is staled. The status must equal to RCNConfigStatusFetchThrottledStale.");
  328. [fetchAllConfigsExpectation fulfill];
  329. };
  330. // Fetch with new custom variables.
  331. [_configFetch fetchAllConfigsWithExpirationDuration:0
  332. completionHandler:fetchAllConfigsCompletion];
  333. [self waitForExpectationsWithTimeout:_expectationTimeout
  334. handler:^(NSError *error) {
  335. XCTAssertNil(error);
  336. }];
  337. }
  338. #pragma mark - helpers
  339. - (void)checkConfigResult:(NSDictionary *)result
  340. withNamespace:(NSString *)namespace
  341. key:(NSString *)key
  342. value:(NSString *)value {
  343. if (result[namespace]) {
  344. FIRRemoteConfigValue *configValue = result[namespace][key];
  345. XCTAssertEqualObjects(configValue.stringValue, value,
  346. @"Config result missing the key value pair.");
  347. } else {
  348. XCTAssertNotNil(result[namespace], @"Config result missing the namespace.");
  349. }
  350. }
  351. @end