RCNConfigTest.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. @import FirebaseRemoteConfigInterop;
  25. static NSString *const RCNFakeSenderID = @"855865492447";
  26. static NSString *const RCNFakeToken = @"ctToAh17Exk:"
  27. @"APA91bFpX1aucYk5ONWt3MxyVxTyDKV8PKjaY2X3DPCZOOTHIBNf3ybxV5"
  28. @"aMQ7G8zUTrduobNLEUoSvGsncthR27gDF_qqZELqp2eEi1BL8k_"
  29. @"4AkeP2dLBq4f8MvuJTOEv2P5ChTdByr";
  30. static NSString *const RCNFakeDeviceID = @"4421690866479820589";
  31. static NSString *const RCNFakeSecretToken = @"6377571288467228941";
  32. @interface RCNConfigFetch (ForTest)
  33. // Exposes fetching user property method in the category.
  34. //- (void)fetchWithUserPropertiesCompletionHandler:(RCNAnalyticsUserPropertiesCompletion)block;
  35. - (void)refreshInstanceIDTokenAndFetchCheckInInfoWithCompletionHandler:
  36. (FIRRemoteConfigFetchCompletion)completionHandler;
  37. - (void)fetchCheckinInfoWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler;
  38. @end
  39. @interface RCNConfigTest : XCTestCase {
  40. NSTimeInterval _expectationTimeout;
  41. RCNConfigSettings *_settings;
  42. RCNConfigFetchResponse *_response;
  43. RCNConfigContent *_configContent;
  44. RCNConfigExperiment *_experiment;
  45. RCNConfigFetch *_configFetch;
  46. dispatch_queue_t _queue;
  47. NSString *_namespaceGoogleMobilePlatform;
  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. _namespaceGoogleMobilePlatform = FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform;
  66. // Fake a response with a default namespace and a custom namespace.
  67. NSDictionary *namespaceToConfig = @{
  68. _namespaceGoogleMobilePlatform : @{@"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:_namespaceGoogleMobilePlatform
  138. key:@"key1"
  139. value:@"value1"];
  140. [self checkConfigResult:result
  141. withNamespace:_namespaceGoogleMobilePlatform
  142. key:@"key2"
  143. value:@"value2"];
  144. [self checkConfigResult:result
  145. withNamespace:_namespaceGoogleMobilePlatform
  146. key:@"playerID"
  147. value:@"36"];
  148. [self checkConfigResult:result
  149. withNamespace:_namespaceGoogleMobilePlatform
  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:_namespaceGoogleMobilePlatform
  184. key:@"key1"
  185. value:@"value1"];
  186. [self checkConfigResult:result
  187. withNamespace:_namespaceGoogleMobilePlatform
  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:_namespaceGoogleMobilePlatform
  224. key:@"key1"
  225. value:@"value1"];
  226. [self checkConfigResult:result
  227. withNamespace:_namespaceGoogleMobilePlatform
  228. key:@"key2"
  229. value:@"value2"];
  230. [self checkConfigResult:result
  231. withNamespace:_namespaceGoogleMobilePlatform
  232. key:@"playerID"
  233. value:@"36"];
  234. [self checkConfigResult:result
  235. withNamespace:_namespaceGoogleMobilePlatform
  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:_namespaceGoogleMobilePlatform
  311. key:@"key1"
  312. value:@"value1"];
  313. [self checkConfigResult:result
  314. withNamespace:_namespaceGoogleMobilePlatform
  315. key:@"key2"
  316. value:@"value2"];
  317. [self checkConfigResult:result
  318. withNamespace:_namespaceGoogleMobilePlatform
  319. key:@"playerID"
  320. value:@"36"];
  321. [self checkConfigResult:result
  322. withNamespace:_namespaceGoogleMobilePlatform
  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. #pragma mark - helpers
  342. - (void)checkConfigResult:(NSDictionary *)result
  343. withNamespace:(NSString *)namespace
  344. key:(NSString *)key
  345. value:(NSString *)value {
  346. if (result[namespace]) {
  347. FIRRemoteConfigValue *configValue = result[namespace][key];
  348. XCTAssertEqualObjects(configValue.stringValue, value,
  349. @"Config result missing the key value pair.");
  350. } else {
  351. XCTAssertNotNil(result[namespace], @"Config result missing the namespace.");
  352. }
  353. }
  354. @end