RCNRemoteConfigTest.m 84 KB

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