RCNRemoteConfigTest.m 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508
  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/OCMStubRecorder.h>
  17. #import <OCMock/OCMock.h>
  18. #import <XCTest/XCTest.h>
  19. #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
  20. #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
  21. #import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  23. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  24. #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
  25. #import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
  26. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  27. #import <GoogleUtilities/GULNSData+zlib.h>
  28. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  29. @interface RCNConfigFetch (ForTest)
  30. - (instancetype)initWithContent:(RCNConfigContent *)content
  31. DBManager:(RCNConfigDBManager *)DBManager
  32. settings:(RCNConfigSettings *)settings
  33. experiment:(RCNConfigExperiment *)experiment
  34. queue:(dispatch_queue_t)queue
  35. namespace:firebaseNamespace
  36. app:firebaseApp;
  37. /// Skip fetching user properties from analytics because we cannot mock the action here. Instead
  38. /// overriding the method to skip.
  39. - (void)fetchWithUserPropertiesCompletionHandler:(FIRAInteropUserPropertiesCallback)block;
  40. - (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
  41. completionHandler:
  42. (RCNConfigFetcherCompletion)fetcherCompletion;
  43. - (void)fetchWithUserProperties:(NSDictionary *)userProperties
  44. completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler;
  45. - (NSString *)constructServerURL;
  46. - (NSURLSession *)currentNetworkSession;
  47. @end
  48. @interface FIRRemoteConfig (ForTest)
  49. - (void)updateWithNewInstancesForConfigFetch:(RCNConfigFetch *)configFetch
  50. configContent:(RCNConfigContent *)configContent
  51. configSettings:(RCNConfigSettings *)configSettings
  52. configExperiment:(RCNConfigExperiment *)configExperiment;
  53. @end
  54. @implementation FIRRemoteConfig (ForTest)
  55. - (void)updateWithNewInstancesForConfigFetch:(RCNConfigFetch *)configFetch
  56. configContent:(RCNConfigContent *)configContent
  57. configSettings:(RCNConfigSettings *)configSettings
  58. configExperiment:(RCNConfigExperiment *)configExperiment {
  59. [self setValue:configFetch forKey:@"_configFetch"];
  60. [self setValue:configContent forKey:@"_configContent"];
  61. [self setValue:configSettings forKey:@"_settings"];
  62. [self setValue:configExperiment forKey:@"_configExperiment"];
  63. }
  64. @end
  65. @interface RCNConfigDBManager (Test)
  66. - (void)removeDatabaseOnDatabaseQueueAtPath:(NSString *)path;
  67. @end
  68. @interface RCNUserDefaultsManager (Test)
  69. + (NSUserDefaults *)sharedUserDefaultsForBundleIdentifier:(NSString *)bundleIdentifier;
  70. @end
  71. @interface RCNConfigSettings (Test)
  72. - (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties;
  73. @end
  74. typedef NS_ENUM(NSInteger, RCNTestRCInstance) {
  75. RCNTestRCInstanceDefault,
  76. RCNTestRCInstanceSecondNamespace,
  77. RCNTestRCInstanceSecondApp,
  78. RCNTestRCNumTotalInstances
  79. };
  80. @interface RCNRemoteConfigTest : XCTestCase {
  81. NSTimeInterval _expectationTimeout;
  82. NSTimeInterval _checkCompletionTimeout;
  83. NSMutableArray<FIRRemoteConfig *> *_configInstances;
  84. NSMutableArray<NSDictionary<NSString *, NSString *> *> *_entries;
  85. NSMutableArray<NSDictionary<NSString *, id> *> *_response;
  86. NSMutableArray<NSData *> *_responseData;
  87. NSMutableArray<NSURLResponse *> *_URLResponse;
  88. NSMutableArray<id> *_configFetch;
  89. RCNConfigDBManager *_DBManager;
  90. NSUserDefaults *_userDefaults;
  91. NSString *_userDefaultsSuiteName;
  92. NSString *_DBPath;
  93. id _DBManagerMock;
  94. id _experimentMock;
  95. id _userDefaultsMock;
  96. NSString *_fullyQualifiedNamespace;
  97. RCNConfigSettings *_settings;
  98. dispatch_queue_t _queue;
  99. }
  100. @end
  101. @implementation RCNRemoteConfigTest
  102. - (void)setUp {
  103. [super setUp];
  104. FIRSetLoggerLevel(FIRLoggerLevelMax);
  105. _expectationTimeout = 5;
  106. _checkCompletionTimeout = 1.0;
  107. // Always remove the database at the start of testing.
  108. _DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
  109. _DBManagerMock = OCMClassMock([RCNConfigDBManager class]);
  110. OCMStub([_DBManagerMock remoteConfigPathForDatabase]).andReturn(_DBPath);
  111. _DBManager = [[RCNConfigDBManager alloc] init];
  112. _userDefaultsSuiteName = [RCNTestUtilities userDefaultsSuiteNameForTestSuite];
  113. _userDefaults = [[NSUserDefaults alloc] initWithSuiteName:_userDefaultsSuiteName];
  114. _userDefaultsMock = OCMClassMock([RCNUserDefaultsManager class]);
  115. OCMStub([_userDefaultsMock sharedUserDefaultsForBundleIdentifier:[OCMArg any]])
  116. .andReturn(_userDefaults);
  117. _experimentMock = OCMClassMock([RCNConfigExperiment class]);
  118. OCMStub([_experimentMock
  119. updateExperimentsWithHandler:([OCMArg invokeBlockWithArgs:[NSNull null], nil])]);
  120. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  121. _configInstances = [[NSMutableArray alloc] initWithCapacity:3];
  122. _entries = [[NSMutableArray alloc] initWithCapacity:3];
  123. _response = [[NSMutableArray alloc] initWithCapacity:3];
  124. _responseData = [[NSMutableArray alloc] initWithCapacity:3];
  125. _URLResponse = [[NSMutableArray alloc] initWithCapacity:3];
  126. _configFetch = [[NSMutableArray alloc] initWithCapacity:3];
  127. // Populate the default, second app, second namespace instances.
  128. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  129. // Fake a response for default instance.
  130. NSMutableDictionary<NSString *, NSString *> *valuesDict = [[NSMutableDictionary alloc] init];
  131. for (int count = 1; count <= 100; count++) {
  132. NSString *key = [NSString stringWithFormat:@"key%d-%d", count, i];
  133. NSString *value = [NSString stringWithFormat:@"value%d-%d", count, i];
  134. valuesDict[key] = value;
  135. }
  136. _entries[i] = valuesDict;
  137. NSString *currentAppName = nil;
  138. FIROptions *currentOptions = nil;
  139. NSString *currentNamespace = nil;
  140. switch (i) {
  141. case RCNTestRCInstanceSecondNamespace:
  142. currentAppName = RCNTestsDefaultFIRAppName;
  143. currentOptions = [self firstAppOptions];
  144. currentNamespace = RCNTestsPerfNamespace;
  145. break;
  146. case RCNTestRCInstanceSecondApp:
  147. currentAppName = RCNTestsSecondFIRAppName;
  148. currentOptions = [self secondAppOptions];
  149. currentNamespace = FIRNamespaceGoogleMobilePlatform;
  150. break;
  151. case RCNTestRCInstanceDefault:
  152. default:
  153. currentAppName = RCNTestsDefaultFIRAppName;
  154. currentOptions = [self firstAppOptions];
  155. currentNamespace = RCNTestsFIRNamespace;
  156. break;
  157. }
  158. _fullyQualifiedNamespace =
  159. [NSString stringWithFormat:@"%@:%@", currentNamespace, currentAppName];
  160. FIRRemoteConfig *config =
  161. OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  162. FIROptions:currentOptions
  163. namespace:currentNamespace
  164. DBManager:_DBManager
  165. configContent:configContent
  166. analytics:nil]);
  167. _configInstances[i] = config;
  168. _settings = [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
  169. namespace:_fullyQualifiedNamespace
  170. firebaseAppName:currentAppName
  171. googleAppID:currentOptions.googleAppID];
  172. _queue = dispatch_queue_create(
  173. [[NSString stringWithFormat:@"testqueue: %d", i] cStringUsingEncoding:NSUTF8StringEncoding],
  174. DISPATCH_QUEUE_SERIAL);
  175. _configFetch[i] =
  176. OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  177. DBManager:_DBManager
  178. settings:_settings
  179. analytics:nil
  180. experiment:_experimentMock
  181. queue:_queue
  182. namespace:_fullyQualifiedNamespace
  183. options:currentOptions]);
  184. OCMStubRecorder *mock = OCMStub([_configFetch[i] fetchConfigWithExpirationDuration:0
  185. completionHandler:OCMOCK_ANY]);
  186. mock = [mock ignoringNonObjectArgs];
  187. mock.andDo(^(NSInvocation *invocation) {
  188. __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status,
  189. NSError *_Nullable error) = nil;
  190. [invocation getArgument:&handler atIndex:3];
  191. [self->_configFetch[i] fetchWithUserProperties:[[NSDictionary alloc] init]
  192. completionHandler:handler];
  193. });
  194. _response[i] = @{@"state" : @"UPDATE", @"entries" : _entries[i]};
  195. _responseData[i] = [NSJSONSerialization dataWithJSONObject:_response[i] options:0 error:nil];
  196. _URLResponse[i] = [[NSHTTPURLResponse alloc]
  197. initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  198. statusCode:200
  199. HTTPVersion:nil
  200. headerFields:@{@"etag" : [NSString stringWithFormat:@"etag1-%d", i]}];
  201. id completionBlock =
  202. [OCMArg invokeBlockWithArgs:_responseData[i], _URLResponse[i], [NSNull null], nil];
  203. OCMStub([_configFetch[i] URLSessionDataTaskWithContent:[OCMArg any]
  204. completionHandler:completionBlock])
  205. .andReturn(nil);
  206. [_configInstances[i] updateWithNewInstancesForConfigFetch:_configFetch[i]
  207. configContent:configContent
  208. configSettings:_settings
  209. configExperiment:_experimentMock];
  210. }
  211. }
  212. - (void)tearDown {
  213. [_DBManager removeDatabaseOnDatabaseQueueAtPath:_DBPath];
  214. [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:_userDefaultsSuiteName];
  215. [_DBManagerMock stopMocking];
  216. _DBManagerMock = nil;
  217. [_userDefaultsMock stopMocking];
  218. _userDefaultsMock = nil;
  219. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  220. [(id)_configInstances[i] stopMocking];
  221. [(id)_configFetch[i] stopMocking];
  222. }
  223. [_configInstances removeAllObjects];
  224. [_configFetch removeAllObjects];
  225. _configInstances = nil;
  226. _configFetch = nil;
  227. [super tearDown];
  228. }
  229. - (void)testFetchConfigWithNilCallback {
  230. NSMutableArray<XCTestExpectation *> *expectations =
  231. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  232. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  233. expectations[i] = [self
  234. expectationWithDescription:
  235. [NSString stringWithFormat:@"Set defaults no callback expectation - instance %d", i]];
  236. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  237. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:nil];
  238. dispatch_after(
  239. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_checkCompletionTimeout * NSEC_PER_SEC)),
  240. dispatch_get_main_queue(), ^{
  241. XCTAssertEqual(self->_configInstances[i].lastFetchStatus,
  242. FIRRemoteConfigFetchStatusSuccess);
  243. [expectations[i] fulfill];
  244. });
  245. }
  246. [self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
  247. }
  248. - (void)testFetchConfigsSuccessfully {
  249. NSMutableArray<XCTestExpectation *> *expectations =
  250. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  251. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  252. expectations[i] =
  253. [self expectationWithDescription:
  254. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  255. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  256. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  257. NSError *error) {
  258. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  259. XCTAssertNil(error);
  260. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  261. XCTAssertTrue(changed);
  262. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  263. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  264. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  265. NSString *value2 = [NSString stringWithFormat:@"value2-%d", i];
  266. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  267. XCTAssertEqualObjects(self->_configInstances[i][key2].stringValue, value2);
  268. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  269. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  270. @"Callback of first successful config "
  271. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccessFresh.");
  272. XCTAssertNotNil(self->_configInstances[i].lastFetchTime);
  273. XCTAssertGreaterThan(self->_configInstances[i].lastFetchTime.timeIntervalSince1970, 0,
  274. @"last fetch time interval should be set.");
  275. [expectations[i] fulfill];
  276. }];
  277. };
  278. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  279. }
  280. [self waitForExpectationsWithTimeout:_expectationTimeout
  281. handler:^(NSError *error) {
  282. XCTAssertNil(error);
  283. }];
  284. }
  285. - (void)testFetchAndActivate {
  286. NSMutableArray<XCTestExpectation *> *expectations =
  287. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  288. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  289. expectations[i] =
  290. [self expectationWithDescription:
  291. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  292. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  293. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion = ^void(
  294. FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  295. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  296. XCTAssertNil(error);
  297. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  298. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  299. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  300. NSString *value2 = [NSString stringWithFormat:@"value2-%d", i];
  301. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  302. XCTAssertEqualObjects(self->_configInstances[i][key2].stringValue, value2);
  303. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  304. XCTAssertEqual(
  305. status, FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote,
  306. @"Callback of first successful config "
  307. @"fetchAndActivate status must equal to FIRRemoteConfigFetchAndStatusSuccessFromRemote.");
  308. XCTAssertNotNil(self->_configInstances[i].lastFetchTime);
  309. XCTAssertGreaterThan(self->_configInstances[i].lastFetchTime.timeIntervalSince1970, 0,
  310. @"last fetch time interval should be set.");
  311. [expectations[i] fulfill];
  312. };
  313. // Update the minimum fetch interval to 0. This disables the cache and forces a remote fetch
  314. // request.
  315. FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
  316. settings.minimumFetchInterval = 0;
  317. [_configInstances[i] setConfigSettings:settings];
  318. [_configInstances[i] fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  319. }
  320. [self waitForExpectationsWithTimeout:_expectationTimeout
  321. handler:^(NSError *error) {
  322. XCTAssertNil(error);
  323. }];
  324. }
  325. // TODO: Try splitting into smaller tests.
  326. - (void)testFetchConfigsSuccessfullyWithNewActivateMethodSignature {
  327. NSMutableArray<XCTestExpectation *> *expectations =
  328. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  329. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  330. expectations[i] =
  331. [self expectationWithDescription:
  332. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  333. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  334. FIRRemoteConfigFetchCompletion fetchCompletion = ^(FIRRemoteConfigFetchStatus status,
  335. NSError *error) {
  336. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  337. XCTAssertNil(error);
  338. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  339. XCTAssertTrue(changed);
  340. XCTAssertNil(error);
  341. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  342. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  343. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  344. NSString *value2 = [NSString stringWithFormat:@"value2-%d", i];
  345. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  346. XCTAssertEqualObjects(self->_configInstances[i][key2].stringValue, value2);
  347. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  348. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  349. @"Callback of first successful config "
  350. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccessFresh.");
  351. XCTAssertNotNil(self->_configInstances[i].lastFetchTime);
  352. XCTAssertGreaterThan(self->_configInstances[i].lastFetchTime.timeIntervalSince1970, 0,
  353. @"last fetch time interval should be set.");
  354. // A second activate should have no effect.
  355. [self->_configInstances[i]
  356. activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  357. XCTAssertFalse(changed);
  358. XCTAssertNil(error);
  359. }];
  360. [expectations[i] fulfill];
  361. }];
  362. };
  363. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  364. }
  365. [self waitForExpectationsWithTimeout:_expectationTimeout
  366. handler:^(NSError *error) {
  367. XCTAssertNil(error);
  368. }];
  369. }
  370. - (void)testEnumeratingConfigResults {
  371. NSMutableArray<XCTestExpectation *> *expectations =
  372. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  373. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  374. expectations[i] = [self
  375. expectationWithDescription:
  376. [NSString stringWithFormat:@"Test enumerating configs successfully - instance %d", i]];
  377. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  378. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  379. NSError *error) {
  380. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  381. XCTAssertNil(error);
  382. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  383. XCTAssertTrue(changed);
  384. NSString *key5 = [NSString stringWithFormat:@"key5-%d", i];
  385. NSString *key19 = [NSString stringWithFormat:@"key19-%d", i];
  386. NSString *value5 = [NSString stringWithFormat:@"value5-%d", i];
  387. NSString *value19 = [NSString stringWithFormat:@"value19-%d", i];
  388. XCTAssertEqualObjects(self->_configInstances[i][key5].stringValue, value5);
  389. XCTAssertEqualObjects(self->_configInstances[i][key19].stringValue, value19);
  390. // Test enumerating config values.
  391. for (NSString *key in self->_configInstances[i]) {
  392. if ([key isEqualToString:key5]) {
  393. XCTAssertEqualObjects(self->_configInstances[i][key5].stringValue, value5);
  394. }
  395. if ([key isEqualToString:key19]) {
  396. XCTAssertEqualObjects(self->_configInstances[i][key19].stringValue, value19);
  397. }
  398. }
  399. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  400. @"Callback of first successful config "
  401. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccessFresh.");
  402. XCTAssertNotNil(self->_configInstances[i].lastFetchTime);
  403. XCTAssertGreaterThan(self->_configInstances[i].lastFetchTime.timeIntervalSince1970, 0,
  404. @"last fetch time interval should be set.");
  405. [expectations[i] fulfill];
  406. }];
  407. };
  408. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  409. }
  410. [self waitForExpectationsWithTimeout:_expectationTimeout
  411. handler:^(NSError *error) {
  412. XCTAssertNil(error);
  413. }];
  414. }
  415. - (void)testFetchAndActivate3pNamespaceUpdatesExperiments {
  416. [[_experimentMock expect] updateExperimentsWithResponse:[OCMArg any]];
  417. XCTestExpectation *expectation = [self
  418. expectationWithDescription:[NSString stringWithFormat:@"FetchAndActivate call for 'firebase' "
  419. @"namespace updates experiment data"]];
  420. XCTAssertEqual(_configInstances[RCNTestRCInstanceDefault].lastFetchStatus,
  421. FIRRemoteConfigFetchStatusNoFetchYet);
  422. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
  423. ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  424. XCTAssertEqual(status, FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote);
  425. XCTAssertNil(error);
  426. XCTAssertEqual(self->_configInstances[RCNTestRCInstanceDefault].lastFetchStatus,
  427. FIRRemoteConfigFetchStatusSuccess);
  428. XCTAssertNotNil(self->_configInstances[RCNTestRCInstanceDefault].lastFetchTime);
  429. XCTAssertGreaterThan(
  430. self->_configInstances[RCNTestRCInstanceDefault].lastFetchTime.timeIntervalSince1970, 0,
  431. @"last fetch time interval should be set.");
  432. [expectation fulfill];
  433. };
  434. [_configInstances[RCNTestRCInstanceDefault]
  435. fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  436. [self waitForExpectationsWithTimeout:_expectationTimeout
  437. handler:^(NSError *error) {
  438. XCTAssertNil(error);
  439. }];
  440. }
  441. - (void)testFetchAndActivateOtherNamespaceDoesntUpdateExperiments {
  442. [[_experimentMock reject] updateExperimentsWithResponse:[OCMArg any]];
  443. XCTestExpectation *expectation = [self
  444. expectationWithDescription:
  445. [NSString stringWithFormat:@"FetchAndActivate call for namespace other than 'firebase' "
  446. @"doesn't update experiment data"]];
  447. XCTAssertEqual(_configInstances[RCNTestRCInstanceSecondNamespace].lastFetchStatus,
  448. FIRRemoteConfigFetchStatusNoFetchYet);
  449. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
  450. ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  451. XCTAssertEqual(status, FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote);
  452. XCTAssertNil(error);
  453. XCTAssertEqual(self->_configInstances[RCNTestRCInstanceSecondNamespace].lastFetchStatus,
  454. FIRRemoteConfigFetchStatusSuccess);
  455. XCTAssertNotNil(self->_configInstances[RCNTestRCInstanceSecondNamespace].lastFetchTime);
  456. XCTAssertGreaterThan(self->_configInstances[RCNTestRCInstanceSecondNamespace]
  457. .lastFetchTime.timeIntervalSince1970,
  458. 0, @"last fetch time interval should be set.");
  459. [expectation fulfill];
  460. };
  461. [_configInstances[RCNTestRCInstanceSecondNamespace]
  462. fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  463. [self waitForExpectationsWithTimeout:_expectationTimeout
  464. handler:^(NSError *error) {
  465. XCTAssertNil(error);
  466. }];
  467. }
  468. - (void)testFetchConfigsFailed {
  469. // Override the setup values to return back an error status.
  470. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  471. // Populate the default, second app, second namespace instances.
  472. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  473. NSString *currentAppName = nil;
  474. FIROptions *currentOptions = nil;
  475. NSString *currentNamespace = nil;
  476. switch (i) {
  477. case RCNTestRCInstanceSecondNamespace:
  478. currentAppName = RCNTestsDefaultFIRAppName;
  479. currentOptions = [self firstAppOptions];
  480. currentNamespace = RCNTestsPerfNamespace;
  481. break;
  482. case RCNTestRCInstanceSecondApp:
  483. currentAppName = RCNTestsSecondFIRAppName;
  484. currentOptions = [self secondAppOptions];
  485. currentNamespace = FIRNamespaceGoogleMobilePlatform;
  486. break;
  487. case RCNTestRCInstanceDefault:
  488. default:
  489. currentAppName = RCNTestsDefaultFIRAppName;
  490. currentOptions = [self firstAppOptions];
  491. currentNamespace = RCNTestsFIRNamespace;
  492. break;
  493. }
  494. RCNUserDefaultsManager *userDefaultsManager =
  495. [[RCNUserDefaultsManager alloc] initWithAppName:currentAppName
  496. bundleID:[NSBundle mainBundle].bundleIdentifier
  497. namespace:_fullyQualifiedNamespace];
  498. userDefaultsManager.lastFetchTime = 0;
  499. FIRRemoteConfig *config =
  500. OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  501. FIROptions:currentOptions
  502. namespace:currentNamespace
  503. DBManager:_DBManager
  504. configContent:configContent
  505. analytics:nil]);
  506. _configInstances[i] = config;
  507. _configFetch[i] =
  508. OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  509. DBManager:_DBManager
  510. settings:_settings
  511. analytics:nil
  512. experiment:nil
  513. queue:_queue
  514. namespace:_fullyQualifiedNamespace
  515. options:currentOptions]);
  516. OCMStub([_configFetch[i] fetchConfigWithExpirationDuration:43200 completionHandler:OCMOCK_ANY])
  517. .andDo(^(NSInvocation *invocation) {
  518. __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status,
  519. NSError *_Nullable error) = nil;
  520. [invocation getArgument:&handler atIndex:3];
  521. [self->_configFetch[i] fetchWithUserProperties:[[NSDictionary alloc] init]
  522. completionHandler:handler];
  523. });
  524. _response[i] = @{};
  525. _responseData[i] = [NSJSONSerialization dataWithJSONObject:_response[i] options:0 error:nil];
  526. _URLResponse[i] =
  527. [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  528. statusCode:500
  529. HTTPVersion:nil
  530. headerFields:@{@"etag" : @"etag1"}];
  531. [_configInstances[i] updateWithNewInstancesForConfigFetch:_configFetch[i]
  532. configContent:configContent
  533. configSettings:_settings
  534. configExperiment:nil];
  535. }
  536. // Make the fetch calls for all instances.
  537. NSMutableArray<XCTestExpectation *> *expectations =
  538. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  539. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  540. expectations[i] = [self
  541. expectationWithDescription:
  542. [NSString stringWithFormat:@"Test enumerating configs successfully - instance %d", i]];
  543. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  544. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  545. NSError *error) {
  546. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusFailure);
  547. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  548. XCTAssertFalse(changed);
  549. XCTAssertNil(error);
  550. FIRRemoteConfigValue *value = self->_configInstances[RCNTestRCInstanceDefault][@"key1"];
  551. XCTAssertEqual((int)value.source, (int)FIRRemoteConfigSourceStatic);
  552. XCTAssertEqualObjects(value.stringValue, @"");
  553. XCTAssertEqual(value.boolValue, NO);
  554. [expectations[i] fulfill];
  555. }];
  556. };
  557. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  558. }
  559. [self waitForExpectationsWithTimeout:_expectationTimeout
  560. handler:^(NSError *error) {
  561. XCTAssertNil(error);
  562. }];
  563. }
  564. // TODO(mandard): Break up test with helper methods.
  565. - (void)testFetchConfigsFailedErrorNoNetwork {
  566. // Override the setup values to return back an error status.
  567. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  568. // Populate the default, second app, second namespace instances.
  569. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  570. NSString *currentAppName = nil;
  571. FIROptions *currentOptions = nil;
  572. NSString *currentNamespace = nil;
  573. switch (i) {
  574. case RCNTestRCInstanceSecondNamespace:
  575. currentAppName = RCNTestsDefaultFIRAppName;
  576. currentOptions = [self firstAppOptions];
  577. currentNamespace = RCNTestsPerfNamespace;
  578. break;
  579. case RCNTestRCInstanceSecondApp:
  580. currentAppName = RCNTestsSecondFIRAppName;
  581. currentOptions = [self secondAppOptions];
  582. currentNamespace = FIRNamespaceGoogleMobilePlatform;
  583. break;
  584. case RCNTestRCInstanceDefault:
  585. default:
  586. currentAppName = RCNTestsDefaultFIRAppName;
  587. currentOptions = [self firstAppOptions];
  588. currentNamespace = RCNTestsFIRNamespace;
  589. break;
  590. }
  591. NSString *fullyQualifiedNamespace =
  592. [NSString stringWithFormat:@"%@:%@", currentNamespace, currentAppName];
  593. RCNUserDefaultsManager *userDefaultsManager =
  594. [[RCNUserDefaultsManager alloc] initWithAppName:currentAppName
  595. bundleID:[NSBundle mainBundle].bundleIdentifier
  596. namespace:fullyQualifiedNamespace];
  597. userDefaultsManager.lastFetchTime = 0;
  598. FIRRemoteConfig *config =
  599. OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  600. FIROptions:currentOptions
  601. namespace:currentNamespace
  602. DBManager:_DBManager
  603. configContent:configContent
  604. analytics:nil]);
  605. _configInstances[i] = config;
  606. RCNConfigSettings *settings =
  607. [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
  608. namespace:fullyQualifiedNamespace
  609. firebaseAppName:currentAppName
  610. googleAppID:currentOptions.googleAppID];
  611. dispatch_queue_t queue = dispatch_queue_create(
  612. [[NSString stringWithFormat:@"testqueue: %d", i] cStringUsingEncoding:NSUTF8StringEncoding],
  613. DISPATCH_QUEUE_SERIAL);
  614. _configFetch[i] = OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  615. DBManager:_DBManager
  616. settings:settings
  617. analytics:nil
  618. experiment:nil
  619. queue:queue
  620. namespace:fullyQualifiedNamespace
  621. options:currentOptions]);
  622. OCMStub([_configFetch[i] fetchConfigWithExpirationDuration:43200 completionHandler:OCMOCK_ANY])
  623. .andDo(^(NSInvocation *invocation) {
  624. __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status,
  625. NSError *_Nullable error) = nil;
  626. [invocation getArgument:&handler atIndex:3];
  627. [self->_configFetch[i] fetchWithUserProperties:[[NSDictionary alloc] init]
  628. completionHandler:handler];
  629. });
  630. _response[i] = @{};
  631. _responseData[i] = [NSJSONSerialization dataWithJSONObject:_response[i] options:0 error:nil];
  632. // A no network error is accompanied with an HTTP status code of 0.
  633. _URLResponse[i] =
  634. [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  635. statusCode:0
  636. HTTPVersion:nil
  637. headerFields:@{@"etag" : @"etag1"}];
  638. [_configInstances[i] updateWithNewInstancesForConfigFetch:_configFetch[i]
  639. configContent:configContent
  640. configSettings:settings
  641. configExperiment:nil];
  642. }
  643. // Make the fetch calls for all instances.
  644. NSMutableArray<XCTestExpectation *> *expectations =
  645. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  646. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  647. expectations[i] = [self
  648. expectationWithDescription:
  649. [NSString stringWithFormat:@"Test enumerating configs successfully - instance %d", i]];
  650. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  651. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  652. NSError *error) {
  653. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusFailure);
  654. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  655. XCTAssertFalse(changed);
  656. XCTAssertNil(error);
  657. // No such key, still return a static value.
  658. FIRRemoteConfigValue *value = self->_configInstances[RCNTestRCInstanceDefault][@"key1"];
  659. XCTAssertEqual((int)value.source, (int)FIRRemoteConfigSourceStatic);
  660. XCTAssertEqualObjects(value.stringValue, @"");
  661. XCTAssertEqual(value.boolValue, NO);
  662. [expectations[i] fulfill];
  663. }];
  664. };
  665. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  666. }
  667. [self waitForExpectationsWithTimeout:_expectationTimeout
  668. handler:^(NSError *error) {
  669. XCTAssertNil(error);
  670. }];
  671. }
  672. - (void)testFetchFailedNoNetworkErrorDoesNotThrottle {
  673. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  674. NSString *currentAppName = RCNTestsDefaultFIRAppName;
  675. FIROptions *currentOptions = [self firstAppOptions];
  676. NSString *currentNamespace = RCNTestsFIRNamespace;
  677. NSString *fullyQualifiedNamespace =
  678. [NSString stringWithFormat:@"%@:%@", currentNamespace, currentAppName];
  679. RCNUserDefaultsManager *userDefaultsManager =
  680. [[RCNUserDefaultsManager alloc] initWithAppName:currentAppName
  681. bundleID:[NSBundle mainBundle].bundleIdentifier
  682. namespace:fullyQualifiedNamespace];
  683. userDefaultsManager.lastFetchTime = 0;
  684. FIRRemoteConfig *config = OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  685. FIROptions:currentOptions
  686. namespace:currentNamespace
  687. DBManager:_DBManager
  688. configContent:configContent
  689. analytics:nil]);
  690. RCNConfigSettings *settings =
  691. [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
  692. namespace:fullyQualifiedNamespace
  693. firebaseAppName:currentAppName
  694. googleAppID:currentOptions.googleAppID];
  695. dispatch_queue_t queue = dispatch_queue_create(
  696. [[NSString stringWithFormat:@"testqueue"] cStringUsingEncoding:NSUTF8StringEncoding],
  697. DISPATCH_QUEUE_SERIAL);
  698. RCNConfigFetch *configFetch =
  699. OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  700. DBManager:_DBManager
  701. settings:settings
  702. analytics:nil
  703. experiment:nil
  704. queue:queue
  705. namespace:fullyQualifiedNamespace
  706. options:currentOptions]);
  707. OCMStub([configFetch fetchConfigWithExpirationDuration:43200 completionHandler:OCMOCK_ANY])
  708. .andDo(^(NSInvocation *invocation) {
  709. __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status,
  710. NSError *_Nullable error) = nil;
  711. [invocation getArgument:&handler atIndex:3];
  712. [configFetch fetchWithUserProperties:[[NSDictionary alloc] init] completionHandler:handler];
  713. });
  714. _responseData[0] = [NSJSONSerialization dataWithJSONObject:@{} options:0 error:nil];
  715. // A no network error is accompanied with an HTTP status code of 0.
  716. _URLResponse[0] =
  717. [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  718. statusCode:0
  719. HTTPVersion:nil
  720. headerFields:@{@"etag" : @"etag1"}];
  721. [config updateWithNewInstancesForConfigFetch:configFetch
  722. configContent:configContent
  723. configSettings:settings
  724. configExperiment:nil];
  725. XCTestExpectation *expectation =
  726. [self expectationWithDescription:@"Network error doesn't increase throttle interval"];
  727. XCTAssertEqual(config.lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  728. FIRRemoteConfigFetchCompletion fetchCompletion =
  729. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  730. XCTAssertEqual(config.lastFetchStatus, FIRRemoteConfigFetchStatusFailure);
  731. XCTAssertEqual(settings.exponentialBackoffRetryInterval, 0);
  732. [expectation fulfill];
  733. };
  734. [config fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  735. [self waitForExpectationsWithTimeout:_expectationTimeout
  736. handler:^(NSError *error) {
  737. XCTAssertNil(error);
  738. }];
  739. }
  740. // Activate should return false if a fetch response returns 200 with NO_CHANGE as the response body.
  741. - (void)testActivateOnFetchNoChangeStatus {
  742. // Override the setup values to return back an error status.
  743. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  744. // Populate the default, second app, second namespace instances.
  745. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  746. NSString *currentAppName = nil;
  747. FIROptions *currentOptions = nil;
  748. NSString *currentNamespace = nil;
  749. switch (i) {
  750. case RCNTestRCInstanceSecondNamespace:
  751. currentAppName = RCNTestsDefaultFIRAppName;
  752. currentOptions = [self firstAppOptions];
  753. currentNamespace = RCNTestsPerfNamespace;
  754. break;
  755. case RCNTestRCInstanceSecondApp:
  756. currentAppName = RCNTestsSecondFIRAppName;
  757. currentOptions = [self secondAppOptions];
  758. currentNamespace = FIRNamespaceGoogleMobilePlatform;
  759. break;
  760. case RCNTestRCInstanceDefault:
  761. default:
  762. currentAppName = RCNTestsDefaultFIRAppName;
  763. currentOptions = [self firstAppOptions];
  764. currentNamespace = RCNTestsFIRNamespace;
  765. break;
  766. }
  767. NSString *fullyQualifiedNamespace =
  768. [NSString stringWithFormat:@"%@:%@", currentNamespace, currentAppName];
  769. RCNUserDefaultsManager *userDefaultsManager =
  770. [[RCNUserDefaultsManager alloc] initWithAppName:currentAppName
  771. bundleID:[NSBundle mainBundle].bundleIdentifier
  772. namespace:fullyQualifiedNamespace];
  773. userDefaultsManager.lastFetchTime = 10;
  774. FIRRemoteConfig *config =
  775. OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  776. FIROptions:currentOptions
  777. namespace:currentNamespace
  778. DBManager:_DBManager
  779. configContent:configContent
  780. analytics:nil]);
  781. _configInstances[i] = config;
  782. RCNConfigSettings *settings =
  783. [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
  784. namespace:fullyQualifiedNamespace
  785. firebaseAppName:currentAppName
  786. googleAppID:currentOptions.googleAppID];
  787. // Start the test with the assumption that we have some data that was fetched and activated.
  788. settings.lastETag = @"etag1";
  789. settings.lastETagUpdateTime = 100;
  790. settings.lastApplyTimeInterval = 101;
  791. dispatch_queue_t queue =
  792. dispatch_queue_create([[NSString stringWithFormat:@"testNoStatusFetchQueue: %d", i]
  793. cStringUsingEncoding:NSUTF8StringEncoding],
  794. DISPATCH_QUEUE_SERIAL);
  795. _configFetch[i] = OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  796. DBManager:_DBManager
  797. settings:settings
  798. analytics:nil
  799. experiment:nil
  800. queue:queue
  801. namespace:fullyQualifiedNamespace
  802. options:currentOptions]);
  803. OCMStub([_configFetch[i] fetchConfigWithExpirationDuration:43200 completionHandler:OCMOCK_ANY])
  804. .andDo(^(NSInvocation *invocation) {
  805. __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status,
  806. NSError *_Nullable error) = nil;
  807. [invocation getArgument:&handler atIndex:3];
  808. [self->_configFetch[i] fetchWithUserProperties:[[NSDictionary alloc] init]
  809. completionHandler:handler];
  810. });
  811. _response[i] = @{@"state" : @"NO_CHANGE"};
  812. _responseData[i] = [NSJSONSerialization dataWithJSONObject:_response[i] options:0 error:nil];
  813. _URLResponse[i] =
  814. [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  815. statusCode:200
  816. HTTPVersion:nil
  817. headerFields:@{@"etag" : @"etag1"}];
  818. id completionBlock =
  819. [OCMArg invokeBlockWithArgs:_responseData[i], _URLResponse[i], [NSNull null], nil];
  820. OCMStub([_configFetch[i] URLSessionDataTaskWithContent:[OCMArg any]
  821. completionHandler:completionBlock])
  822. .andReturn(nil);
  823. [_configInstances[i] updateWithNewInstancesForConfigFetch:_configFetch[i]
  824. configContent:configContent
  825. configSettings:settings
  826. configExperiment:nil];
  827. }
  828. // Make the fetch calls for all instances.
  829. NSMutableArray<XCTestExpectation *> *expectations =
  830. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  831. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  832. expectations[i] = [self
  833. expectationWithDescription:
  834. [NSString stringWithFormat:@"Test enumerating configs successfully - instance %d", i]];
  835. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  836. // Make sure activate returns false in fetch completion.
  837. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  838. NSError *error) {
  839. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  840. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  841. XCTAssertFalse(changed);
  842. XCTAssertNil(error);
  843. [expectations[i] fulfill];
  844. }];
  845. };
  846. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  847. }
  848. [self waitForExpectationsWithTimeout:_expectationTimeout
  849. handler:^(NSError *error) {
  850. XCTAssertNil(error);
  851. }];
  852. }
  853. - (void)testConfigValueForKey {
  854. NSMutableArray<XCTestExpectation *> *expectations =
  855. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  856. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  857. expectations[i] =
  858. [self expectationWithDescription:
  859. [NSString stringWithFormat:@"Test configValueForKey: method - instance %d", i]];
  860. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  861. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  862. NSError *error) {
  863. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess);
  864. XCTAssertNil(error);
  865. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  866. XCTAssertTrue(changed);
  867. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  868. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  869. NSString *key3 = [NSString stringWithFormat:@"key3-%d", i];
  870. NSString *key7 = [NSString stringWithFormat:@"key7-%d", i];
  871. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  872. NSString *value2 = [NSString stringWithFormat:@"value2-%d", i];
  873. NSString *value3 = [NSString stringWithFormat:@"value3-%d", i];
  874. NSString *value7 = [NSString stringWithFormat:@"value7-%d", i];
  875. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  876. XCTAssertEqualObjects(self->_configInstances[i][key2].stringValue, value2);
  877. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  878. XCTAssertEqualObjects([self->_configInstances[i] configValueForKey:key3].stringValue,
  879. value3);
  880. if (i == RCNTestRCInstanceDefault) {
  881. XCTAssertEqualObjects([self->_configInstances[i] configValueForKey:key7].stringValue,
  882. value7);
  883. }
  884. XCTAssertEqualObjects([self->_configInstances[i] configValueForKey:key7].stringValue,
  885. value7);
  886. XCTAssertNotNil([self->_configInstances[i] configValueForKey:nil]);
  887. XCTAssertEqual([self->_configInstances[i] configValueForKey:nil].source,
  888. FIRRemoteConfigSourceStatic);
  889. XCTAssertEqual([self->_configInstances[i] configValueForKey:nil].source,
  890. FIRRemoteConfigSourceStatic);
  891. XCTAssertEqual([self->_configInstances[i] configValueForKey:nil source:-1].source,
  892. FIRRemoteConfigSourceStatic);
  893. [expectations[i] fulfill];
  894. }];
  895. };
  896. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  897. }
  898. [self waitForExpectationsWithTimeout:_expectationTimeout
  899. handler:^(NSError *error) {
  900. XCTAssertNil(error);
  901. }];
  902. }
  903. - (void)testFetchConfigWithDefaultSets {
  904. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  905. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  906. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  907. fetchConfigsExpectation[i] = [self
  908. expectationWithDescription:
  909. [NSString stringWithFormat:@"Test fetch configs with defaults set - instance %d", i]];
  910. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  911. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  912. NSString *key0 = [NSString stringWithFormat:@"key0-%d", i];
  913. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  914. NSString *value2 = [NSString stringWithFormat:@"value2-%d", i];
  915. NSDictionary<NSString *, NSString *> *defaults = @{key1 : @"default key1", key0 : @"value0-0"};
  916. [_configInstances[i] setDefaults:defaults];
  917. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  918. NSError *error) {
  919. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  920. XCTAssertNil(error);
  921. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, @"default key1");
  922. XCTAssertEqual(self->_configInstances[i][key1].source, FIRRemoteConfigSourceDefault);
  923. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  924. XCTAssertTrue(changed);
  925. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  926. XCTAssertEqual(self->_configInstances[i][key1].source, FIRRemoteConfigSourceRemote);
  927. XCTAssertEqualObjects([self->_configInstances[i] defaultValueForKey:key1].stringValue,
  928. @"default key1");
  929. XCTAssertEqualObjects(self->_configInstances[i][key2].stringValue, value2);
  930. XCTAssertEqualObjects(self->_configInstances[i][key0].stringValue, @"value0-0");
  931. XCTAssertNil([self->_configInstances[i] defaultValueForKey:nil]);
  932. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  933. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  934. @"Callback of first successful config "
  935. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccess.");
  936. [fetchConfigsExpectation[i] fulfill];
  937. }];
  938. };
  939. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  940. }
  941. [self waitForExpectationsWithTimeout:_expectationTimeout
  942. handler:^(NSError *error) {
  943. XCTAssertNil(error);
  944. }];
  945. }
  946. - (void)testDefaultsSetsOnly {
  947. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  948. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  949. NSString *key2 = [NSString stringWithFormat:@"key2-%d", i];
  950. NSString *key3 = [NSString stringWithFormat:@"key3-%d", i];
  951. NSString *key4 = [NSString stringWithFormat:@"key4-%d", i];
  952. NSString *key5 = [NSString stringWithFormat:@"key5-%d", i];
  953. NSString *defaultValue1 = @"default value1";
  954. NSData *defaultValue2 = [defaultValue1 dataUsingEncoding:NSUTF8StringEncoding];
  955. NSNumber *defaultValue3 = [NSNumber numberWithFloat:3.1415926];
  956. NSDate *defaultValue4 = [NSDate date];
  957. BOOL defaultValue5 = NO;
  958. NSMutableDictionary<NSString *, id> *defaults = [NSMutableDictionary dictionaryWithDictionary:@{
  959. key1 : defaultValue1,
  960. key2 : defaultValue2,
  961. key3 : defaultValue3,
  962. key4 : defaultValue4,
  963. key5 : @(defaultValue5),
  964. }];
  965. [_configInstances[i] setDefaults:defaults];
  966. if (i == RCNTestRCInstanceSecondNamespace) {
  967. [defaults setObject:@"2860" forKey:@"experience"];
  968. [_configInstances[i] setDefaults:defaults];
  969. }
  970. // Remove objects right away to make sure dispatch_async gets the copy.
  971. [defaults removeAllObjects];
  972. FIRRemoteConfig *config = _configInstances[i];
  973. XCTAssertEqualObjects(config[key1].stringValue, defaultValue1, @"Should support string format");
  974. XCTAssertEqualObjects(config[key2].dataValue, defaultValue2, @"Should support data format");
  975. XCTAssertEqual(config[key3].numberValue.longValue, defaultValue3.longValue,
  976. @"Should support number format");
  977. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  978. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  979. NSString *strValueOfDate = [dateFormatter stringFromDate:(NSDate *)defaultValue4];
  980. XCTAssertEqualObjects(
  981. config[key4].stringValue, strValueOfDate,
  982. @"Date format can be set as an input from plist, but output coming out of "
  983. @"defaultConfig as string.");
  984. XCTAssertEqual(config[key5].boolValue, defaultValue5, @"Should support bool format");
  985. if (i == RCNTestRCInstanceSecondNamespace) {
  986. XCTAssertEqualObjects(
  987. [_configInstances[i] configValueForKey:@"experience"].stringValue, @"2860",
  988. @"Only default config has the key, must equal to default config value.");
  989. }
  990. // Reset default sets
  991. [_configInstances[i] setDefaults:nil];
  992. XCTAssertEqual([_configInstances[i] allKeysFromSource:FIRRemoteConfigSourceDefault].count, 0);
  993. }
  994. }
  995. - (void)testSetDefaultsWithNilParams {
  996. NSMutableArray<XCTestExpectation *> *expectations =
  997. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  998. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  999. expectations[i] = [self
  1000. expectationWithDescription:
  1001. [NSString stringWithFormat:@"Set defaults no callback expectation - instance %d", i]];
  1002. // Should work when passing nil.
  1003. [_configInstances[i] setDefaults:nil];
  1004. [_configInstances[i] setDefaults:nil];
  1005. dispatch_after(
  1006. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_checkCompletionTimeout * NSEC_PER_SEC)),
  1007. dispatch_get_main_queue(), ^{
  1008. XCTAssertEqual(
  1009. [self->_configInstances[i] allKeysFromSource:FIRRemoteConfigSourceDefault].count, 0);
  1010. [expectations[i] fulfill];
  1011. });
  1012. }
  1013. [self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
  1014. }
  1015. - (void)testFetchConfigOverwriteDefaultSet {
  1016. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  1017. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  1018. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1019. fetchConfigsExpectation[i] = [self
  1020. expectationWithDescription:
  1021. [NSString stringWithFormat:@"Test fetch configs with defaults set - instance %d", i]];
  1022. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  1023. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  1024. [_configInstances[i] setDefaults:@{key1 : @"default key1"}];
  1025. FIRRemoteConfigValue *value = _configInstances[i][key1];
  1026. XCTAssertEqualObjects(value.stringValue, @"default key1");
  1027. XCTAssertEqual(value.source, FIRRemoteConfigSourceDefault);
  1028. value = _configInstances[i][@"A key doesn't exist"];
  1029. XCTAssertEqual(value.source, FIRRemoteConfigSourceStatic);
  1030. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  1031. NSError *error) {
  1032. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  1033. XCTAssertNil(error);
  1034. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  1035. XCTAssertTrue(changed);
  1036. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  1037. XCTAssertEqual(self->_configInstances[i][key1].source, FIRRemoteConfigSourceRemote);
  1038. XCTAssertEqualObjects([self->_configInstances[i] defaultValueForKey:key1].stringValue,
  1039. @"default key1");
  1040. OCMVerify([self->_configInstances[i] objectForKeyedSubscript:key1]);
  1041. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  1042. @"Callback of first successful config "
  1043. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccess.");
  1044. [fetchConfigsExpectation[i] fulfill];
  1045. }];
  1046. };
  1047. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  1048. }
  1049. [self waitForExpectationsWithTimeout:_expectationTimeout
  1050. handler:^(NSError *error) {
  1051. XCTAssertNil(error);
  1052. }];
  1053. }
  1054. - (void)testGetConfigValueBySource {
  1055. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  1056. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  1057. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1058. fetchConfigsExpectation[i] =
  1059. [self expectationWithDescription:
  1060. [NSString stringWithFormat:@"Test get config value by source - instance %d", i]];
  1061. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  1062. NSString *value1 = [NSString stringWithFormat:@"value1-%d", i];
  1063. NSDictionary<NSString *, NSString *> *defaults = @{key1 : @"default value1"};
  1064. [_configInstances[i] setDefaults:defaults];
  1065. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  1066. NSError *error) {
  1067. XCTAssertEqual(self->_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusSuccess);
  1068. XCTAssertNil(error);
  1069. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, @"default value1");
  1070. XCTAssertEqual(self->_configInstances[i][key1].source, FIRRemoteConfigSourceDefault);
  1071. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  1072. XCTAssertTrue(changed);
  1073. XCTAssertEqualObjects(self->_configInstances[i][key1].stringValue, value1);
  1074. XCTAssertEqual(self->_configInstances[i][key1].source, FIRRemoteConfigSourceRemote);
  1075. FIRRemoteConfigValue *value;
  1076. if (i == RCNTestRCInstanceDefault) {
  1077. value = [self->_configInstances[i] configValueForKey:key1
  1078. source:FIRRemoteConfigSourceRemote];
  1079. XCTAssertEqualObjects(value.stringValue, value1);
  1080. value = [self->_configInstances[i] configValueForKey:key1
  1081. source:FIRRemoteConfigSourceDefault];
  1082. XCTAssertEqualObjects(value.stringValue, @"default value1");
  1083. value = [self->_configInstances[i] configValueForKey:key1
  1084. source:FIRRemoteConfigSourceStatic];
  1085. } else {
  1086. value = [self->_configInstances[i] configValueForKey:key1
  1087. source:FIRRemoteConfigSourceRemote];
  1088. XCTAssertEqualObjects(value.stringValue, value1);
  1089. value = [self->_configInstances[i] configValueForKey:key1
  1090. source:FIRRemoteConfigSourceDefault];
  1091. XCTAssertEqualObjects(value.stringValue, @"default value1");
  1092. value = [self->_configInstances[i] configValueForKey:key1
  1093. source:FIRRemoteConfigSourceStatic];
  1094. }
  1095. XCTAssertEqualObjects(value.stringValue, @"");
  1096. XCTAssertEqualObjects(value.numberValue, @(0));
  1097. XCTAssertEqual(value.boolValue, NO);
  1098. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess,
  1099. @"Callback of first successful config "
  1100. @"fetch. Status must equal to FIRRemoteConfigFetchStatusSuccess.");
  1101. [fetchConfigsExpectation[i] fulfill];
  1102. }];
  1103. };
  1104. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  1105. }
  1106. [self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
  1107. }
  1108. - (void)testInvalidKeyOrNamespace {
  1109. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1110. FIRRemoteConfigValue *value = [_configInstances[i] configValueForKey:nil];
  1111. XCTAssertNotNil(value);
  1112. XCTAssertEqual(value.source, FIRRemoteConfigSourceStatic);
  1113. value = [_configInstances[i] configValueForKey:nil];
  1114. XCTAssertNotNil(value);
  1115. XCTAssertEqual(value.source, FIRRemoteConfigSourceStatic);
  1116. value = [_configInstances[i] configValueForKey:nil source:5];
  1117. XCTAssertNotNil(value);
  1118. XCTAssertEqual(value.source, FIRRemoteConfigSourceStatic);
  1119. }
  1120. }
  1121. // Remote Config converts UTC times in the plists to local times. This utility function makes it
  1122. // possible to check the times when running the tests in any timezone.
  1123. static NSString *UTCToLocal(NSString *utcTime) {
  1124. // Create a UTC dateFormatter.
  1125. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  1126. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  1127. [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
  1128. NSDate *date = [dateFormatter dateFromString:utcTime];
  1129. [dateFormatter setTimeZone:[NSTimeZone localTimeZone]];
  1130. return [dateFormatter stringFromDate:date];
  1131. }
  1132. // Manage different bundle locations for Swift Package Manager, CocoaPods static, CocoaPods dynamic.
  1133. - (void)setDefaultsFor:(FIRRemoteConfig *)config {
  1134. #if SWIFT_PACKAGE
  1135. NSBundle *bundle = Firebase_RemoteConfigUnit_SWIFTPM_MODULE_BUNDLE();
  1136. NSString *plistFile = [bundle pathForResource:@"Defaults-testInfo" ofType:@"plist"];
  1137. #else
  1138. NSBundle *bundle = [NSBundle mainBundle];
  1139. NSString *plistFile = [bundle pathForResource:@"Defaults-testInfo" ofType:@"plist"];
  1140. if (plistFile != nil) {
  1141. [config setDefaultsFromPlistFileName:@"Defaults-testInfo"];
  1142. return;
  1143. }
  1144. // We've linked dynamically and the plist file is in the test's bundle.
  1145. for (bundle in [NSBundle allBundles]) {
  1146. plistFile = [bundle pathForResource:@"Defaults-testInfo" ofType:@"plist"];
  1147. if (plistFile != nil) {
  1148. break;
  1149. }
  1150. }
  1151. #endif
  1152. NSDictionary *defaults = [[NSDictionary alloc] initWithContentsOfFile:plistFile];
  1153. [config setDefaults:defaults];
  1154. }
  1155. - (void)testSetDefaultsFromPlist {
  1156. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1157. FIRRemoteConfig *config = _configInstances[i];
  1158. [self setDefaultsFor:config];
  1159. XCTAssertEqualObjects(_configInstances[i][@"lastCheckTime"].stringValue,
  1160. UTCToLocal(@"2016-02-28 18:33:31"));
  1161. XCTAssertEqual(_configInstances[i][@"isPaidUser"].boolValue, YES);
  1162. XCTAssertEqualObjects(_configInstances[i][@"dataValue"].stringValue, @"2.4");
  1163. XCTAssertEqualObjects(_configInstances[i][@"New item"].numberValue, @(2.4));
  1164. XCTAssertEqualObjects(_configInstances[i][@"Languages"].stringValue, @"English");
  1165. XCTAssertEqualObjects(_configInstances[i][@"FileInfo"].stringValue,
  1166. @"To setup default config.");
  1167. XCTAssertEqualObjects(_configInstances[i][@"format"].stringValue, @"key to value.");
  1168. XCTAssertEqualObjects(_configInstances[i][@"arrayValue"].JSONValue,
  1169. ((id) @[ @"foo", @"bar", @"baz" ]));
  1170. XCTAssertEqualObjects(_configInstances[i][@"dictValue"].JSONValue,
  1171. ((id) @{@"foo" : @"foo", @"bar" : @"bar", @"baz" : @"baz"}));
  1172. // If given a wrong file name, the default will not be set and kept as previous results.
  1173. [_configInstances[i] setDefaultsFromPlistFileName:@""];
  1174. XCTAssertEqualObjects(_configInstances[i][@"lastCheckTime"].stringValue,
  1175. UTCToLocal(@"2016-02-28 18:33:31"));
  1176. [_configInstances[i] setDefaultsFromPlistFileName:@"non-existed_file"];
  1177. XCTAssertEqualObjects(_configInstances[i][@"dataValue"].stringValue, @"2.4");
  1178. [_configInstances[i] setDefaultsFromPlistFileName:nil];
  1179. XCTAssertEqualObjects(_configInstances[i][@"New item"].numberValue, @(2.4));
  1180. [_configInstances[i] setDefaultsFromPlistFileName:@""];
  1181. XCTAssertEqualObjects(_configInstances[i][@"Languages"].stringValue, @"English");
  1182. }
  1183. }
  1184. - (void)testAllKeysFromSource {
  1185. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  1186. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  1187. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1188. fetchConfigsExpectation[i] = [self
  1189. expectationWithDescription:[NSString
  1190. stringWithFormat:@"Test allKeys methods - instance %d", i]];
  1191. NSString *key1 = [NSString stringWithFormat:@"key1-%d", i];
  1192. NSString *key0 = [NSString stringWithFormat:@"key0-%d", i];
  1193. NSDictionary<NSString *, NSString *> *defaults = @{key1 : @"default key1", key0 : @"value0-0"};
  1194. [_configInstances[i] setDefaults:defaults];
  1195. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  1196. NSError *error) {
  1197. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess);
  1198. XCTAssertNil(error);
  1199. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  1200. XCTAssertTrue(changed);
  1201. XCTAssertEqual(
  1202. [self->_configInstances[i] allKeysFromSource:FIRRemoteConfigSourceRemote].count, 100);
  1203. XCTAssertEqual(
  1204. [self->_configInstances[i] allKeysFromSource:FIRRemoteConfigSourceDefault].count, 2);
  1205. XCTAssertEqual(
  1206. [self->_configInstances[i] allKeysFromSource:FIRRemoteConfigSourceStatic].count, 0);
  1207. [fetchConfigsExpectation[i] fulfill];
  1208. }];
  1209. };
  1210. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  1211. }
  1212. [self waitForExpectationsWithTimeout:_expectationTimeout
  1213. handler:^(NSError *error) {
  1214. XCTAssertNil(error);
  1215. }];
  1216. }
  1217. - (void)testAllKeysWithPrefix {
  1218. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  1219. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  1220. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1221. fetchConfigsExpectation[i] = [self
  1222. expectationWithDescription:[NSString
  1223. stringWithFormat:@"Test allKeys methods - instance %d", i]];
  1224. FIRRemoteConfigFetchCompletion fetchCompletion = ^void(FIRRemoteConfigFetchStatus status,
  1225. NSError *error) {
  1226. XCTAssertEqual(status, FIRRemoteConfigFetchStatusSuccess);
  1227. XCTAssertNil(error);
  1228. NSLog(@"Testing _configInstances %d", i);
  1229. [self->_configInstances[i] activateWithCompletion:^(BOOL changed, NSError *_Nullable error) {
  1230. XCTAssertTrue(changed);
  1231. // Test keysWithPrefix: method.
  1232. XCTAssertEqual([self->_configInstances[i] keysWithPrefix:@"key1"].count, 12);
  1233. XCTAssertEqual([self->_configInstances[i] keysWithPrefix:@"key"].count, 100);
  1234. XCTAssertEqual([self->_configInstances[i] keysWithPrefix:@"invalid key"].count, 0);
  1235. XCTAssertEqual([self->_configInstances[i] keysWithPrefix:nil].count, 100);
  1236. XCTAssertEqual([self->_configInstances[i] keysWithPrefix:@""].count, 100);
  1237. [fetchConfigsExpectation[i] fulfill];
  1238. }];
  1239. };
  1240. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  1241. }
  1242. [self waitForExpectationsWithTimeout:_expectationTimeout
  1243. handler:^(NSError *error) {
  1244. XCTAssertNil(error);
  1245. }];
  1246. }
  1247. /// Test the minimum fetch interval is applied and read back correctly.
  1248. - (void)testSetMinimumFetchIntervalConfigSetting {
  1249. NSMutableArray<XCTestExpectation *> *fetchConfigsExpectation =
  1250. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  1251. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1252. fetchConfigsExpectation[i] = [self
  1253. expectationWithDescription:
  1254. [NSString stringWithFormat:@"Test minimumFetchInterval expectation - instance %d", i]];
  1255. FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
  1256. settings.minimumFetchInterval = 123;
  1257. [_configInstances[i] setConfigSettings:settings];
  1258. XCTAssertEqual([_configInstances[i] configSettings].minimumFetchInterval, 123);
  1259. FIRRemoteConfigFetchCompletion fetchCompletion =
  1260. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  1261. XCTAssertFalse([self->_configInstances[i].settings hasMinimumFetchIntervalElapsed:43200]);
  1262. // Update minimum fetch interval.
  1263. FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
  1264. settings.minimumFetchInterval = 0;
  1265. [self->_configInstances[i] setConfigSettings:settings];
  1266. XCTAssertEqual([self->_configInstances[i] configSettings].minimumFetchInterval, 0);
  1267. XCTAssertTrue([self->_configInstances[i].settings hasMinimumFetchIntervalElapsed:0]);
  1268. [fetchConfigsExpectation[i] fulfill];
  1269. };
  1270. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  1271. }
  1272. [self waitForExpectationsWithTimeout:_expectationTimeout
  1273. handler:^(NSError *error) {
  1274. XCTAssertNil(error);
  1275. }];
  1276. }
  1277. /// Test the fetch timeout is properly set and read back.
  1278. - (void)testSetFetchTimeoutConfigSetting {
  1279. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  1280. FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
  1281. settings.fetchTimeout = 1;
  1282. [_configInstances[i] setConfigSettings:settings];
  1283. XCTAssertEqual([_configInstances[i] configSettings].fetchTimeout, 1);
  1284. NSURLSession *networkSession = [_configFetch[i] currentNetworkSession];
  1285. XCTAssertNotNil(networkSession);
  1286. XCTAssertEqual(networkSession.configuration.timeoutIntervalForResource, 1);
  1287. XCTAssertEqual(networkSession.configuration.timeoutIntervalForRequest, 1);
  1288. }
  1289. }
  1290. - (void)testFetchRequestWithUserPropertiesOnly {
  1291. NSDictionary *userProperties = @{@"user_key" : @"user_value"};
  1292. NSString *req = [_settings nextRequestWithUserProperties:userProperties];
  1293. XCTAssertTrue([req containsString:@"analytics_user_properties:{\"user_key\":\"user_value\"}"]);
  1294. XCTAssertFalse([req containsString:@"first_open_time"]);
  1295. }
  1296. - (void)testFetchRequestWithFirstOpenTimeAndUserProperties {
  1297. NSDictionary *userProperties = @{@"_fot" : @1649116800000, @"user_key" : @"user_value"};
  1298. NSString *req = [_settings nextRequestWithUserProperties:userProperties];
  1299. XCTAssertTrue([req containsString:@"first_open_time:'2022-04-05T00:00:00Z'"]);
  1300. XCTAssertTrue([req containsString:@"analytics_user_properties:{\"user_key\":\"user_value\"}"]);
  1301. }
  1302. - (void)testFetchRequestFirstOpenTimeOnly {
  1303. NSDictionary *userProperties = @{@"_fot" : @1650315600000};
  1304. NSString *req = [_settings nextRequestWithUserProperties:userProperties];
  1305. XCTAssertTrue([req containsString:@"first_open_time:'2022-04-18T21:00:00Z'"]);
  1306. XCTAssertFalse([req containsString:@"analytics_user_properties"]);
  1307. }
  1308. #pragma mark - Public Factory Methods
  1309. - (void)testConfigureConfigWithValidInput {
  1310. // Configure the default app with our options and ensure the Remote Config instance is set up
  1311. // properly.
  1312. if (![FIRApp isDefaultAppConfigured]) {
  1313. XCTAssertNoThrow([FIRApp configureWithOptions:[self firstAppOptions]]);
  1314. }
  1315. XCTAssertNoThrow([FIRRemoteConfig remoteConfig]);
  1316. FIRRemoteConfig *config = [FIRRemoteConfig remoteConfig];
  1317. XCTAssertNotNil(config);
  1318. // Ensure the same instance is returned from the singleton.
  1319. FIRRemoteConfig *sameConfig = [FIRRemoteConfig remoteConfig];
  1320. XCTAssertNotNil(sameConfig);
  1321. XCTAssertEqual(config, sameConfig);
  1322. // Ensure the app name is stored properly.
  1323. XCTAssertEqual([config valueForKey:@"_appName"], kFIRDefaultAppName);
  1324. }
  1325. #pragma mark - Test Helpers
  1326. - (FIROptions *)firstAppOptions {
  1327. // TODO: Evaluate if we want to hardcode things here instead.
  1328. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
  1329. GCMSenderID:@"correct_gcm_sender_id"];
  1330. options.APIKey = @"AIzaSy-ApiKeyWithValidFormat_0123456789";
  1331. options.projectID = @"abc-xyz-123";
  1332. return options;
  1333. }
  1334. - (FIROptions *)secondAppOptions {
  1335. NSBundle *bundle = [NSBundle bundleForClass:[self class]];
  1336. #if SWIFT_PACKAGE
  1337. bundle = Firebase_RemoteConfigUnit_SWIFTPM_MODULE_BUNDLE();
  1338. #endif
  1339. NSString *plistPath = [bundle pathForResource:@"SecondApp-GoogleService-Info" ofType:@"plist"];
  1340. FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:plistPath];
  1341. XCTAssertNotNil(options);
  1342. return options;
  1343. }
  1344. @end