RCNInstanceIDTest.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import <FirebaseRemoteConfig/FIRRemoteConfig.h>
  18. #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
  19. #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
  20. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  21. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
  23. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  24. #import <FirebaseCore/FIRAppInternal.h>
  25. #import <FirebaseCore/FIRLogger.h>
  26. #import <FirebaseCore/FIROptions.h>
  27. #import <FirebaseInstallations/FirebaseInstallations.h>
  28. #import <GoogleUtilities/GULNSData+zlib.h>
  29. #import <OCMock/OCMock.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. @end
  39. @interface RCNConfigDBManager (Test)
  40. - (void)removeDatabaseOnDatabaseQueueAtPath:(NSString *)path;
  41. @end
  42. @interface RCNUserDefaultsManager (Test)
  43. + (NSUserDefaults *)sharedUserDefaultsForBundleIdentifier:(NSString *)bundleIdentifier;
  44. @end
  45. @interface FIRInstallationsAuthTokenResult (Test)
  46. - (instancetype)initWithToken:(NSString *)token expirationDate:(NSDate *)expirationDate;
  47. @end
  48. typedef NS_ENUM(NSInteger, RCNTestRCInstance) {
  49. RCNTestRCInstanceDefault,
  50. RCNTestRCNumTotalInstances, // TODO(mandard): Remove once OCMock issue is resolved (#4877).
  51. RCNTestRCInstanceSecondNamespace,
  52. RCNTestRCInstanceSecondApp,
  53. };
  54. @class FIRInstallationsIDController;
  55. @interface FIRInstallations (Tests)
  56. - (instancetype)initWithAppOptions:(FIROptions *)appOptions
  57. appName:(NSString *)appName
  58. installationsIDController:(FIRInstallationsIDController *)installationsIDController
  59. prefetchAuthToken:(BOOL)prefetchAuthToken;
  60. @end
  61. @interface RCNInstallationsTests : XCTestCase {
  62. NSTimeInterval _expectationTimeout;
  63. NSTimeInterval _checkCompletionTimeout;
  64. NSMutableArray<FIRRemoteConfig *> *_configInstances;
  65. NSMutableArray<NSDictionary<NSString *, NSString *> *> *_entries;
  66. NSMutableArray<NSDictionary<NSString *, id> *> *_response;
  67. NSMutableArray<NSData *> *_responseData;
  68. NSMutableArray<NSURLResponse *> *_URLResponse;
  69. NSMutableArray<RCNConfigFetch *> *_configFetch;
  70. RCNConfigDBManager *_DBManager;
  71. NSUserDefaults *_userDefaults;
  72. NSString *_userDefaultsSuiteName;
  73. NSString *_DBPath;
  74. id _installationsMock;
  75. }
  76. @end
  77. @implementation RCNInstallationsTests
  78. - (void)setUpConfigMock {
  79. FIRSetLoggerLevel(FIRLoggerLevelMax);
  80. _expectationTimeout = 5;
  81. _checkCompletionTimeout = 1.0;
  82. [FIRApp configureWithOptions:[self firstAppOptions]];
  83. // Always remove the database at the start of testing.
  84. _DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
  85. id classMock = OCMClassMock([RCNConfigDBManager class]);
  86. OCMStub([classMock remoteConfigPathForDatabase]).andReturn(_DBPath);
  87. _DBManager = [[RCNConfigDBManager alloc] init];
  88. _userDefaultsSuiteName = [RCNTestUtilities userDefaultsSuiteNameForTestSuite];
  89. _userDefaults = [[NSUserDefaults alloc] initWithSuiteName:_userDefaultsSuiteName];
  90. id userDefaultsClassMock = OCMClassMock([RCNUserDefaultsManager class]);
  91. OCMStub([userDefaultsClassMock sharedUserDefaultsForBundleIdentifier:[OCMArg any]])
  92. .andReturn(_userDefaults);
  93. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:_DBManager];
  94. _configInstances = [[NSMutableArray alloc] initWithCapacity:3];
  95. _entries = [[NSMutableArray alloc] initWithCapacity:3];
  96. _response = [[NSMutableArray alloc] initWithCapacity:3];
  97. _responseData = [[NSMutableArray alloc] initWithCapacity:3];
  98. _URLResponse = [[NSMutableArray alloc] initWithCapacity:3];
  99. _configFetch = [[NSMutableArray alloc] initWithCapacity:3];
  100. // Populate the default, second app, second namespace instances.
  101. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  102. // Fake a response for default instance.
  103. NSMutableDictionary<NSString *, NSString *> *valuesDict = [[NSMutableDictionary alloc] init];
  104. for (int count = 1; count <= 100; count++) {
  105. NSString *key = [NSString stringWithFormat:@"key%d-%d", count, i];
  106. NSString *value = [NSString stringWithFormat:@"value%d-%d", count, i];
  107. valuesDict[key] = value;
  108. }
  109. _entries[i] = valuesDict;
  110. NSString *currentAppName = nil;
  111. FIROptions *currentOptions = nil;
  112. NSString *currentNamespace = nil;
  113. switch (i) {
  114. case RCNTestRCInstanceSecondNamespace:
  115. currentAppName = RCNTestsDefaultFIRAppName;
  116. currentOptions = [self firstAppOptions];
  117. currentNamespace = RCNTestsPerfNamespace;
  118. break;
  119. case RCNTestRCInstanceSecondApp:
  120. currentAppName = RCNTestsSecondFIRAppName;
  121. currentOptions = [self secondAppOptions];
  122. currentNamespace = FIRNamespaceGoogleMobilePlatform;
  123. break;
  124. case RCNTestRCInstanceDefault:
  125. default:
  126. currentAppName = RCNTestsDefaultFIRAppName;
  127. currentOptions = [self firstAppOptions];
  128. currentNamespace = RCNTestsFIRNamespace;
  129. break;
  130. }
  131. NSString *fullyQualifiedNamespace =
  132. [NSString stringWithFormat:@"%@:%@", currentNamespace, currentAppName];
  133. FIRRemoteConfig *config =
  134. OCMPartialMock([[FIRRemoteConfig alloc] initWithAppName:currentAppName
  135. FIROptions:currentOptions
  136. namespace:currentNamespace
  137. DBManager:_DBManager
  138. configContent:configContent
  139. analytics:nil]);
  140. _configInstances[i] = config;
  141. RCNConfigSettings *settings =
  142. [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
  143. namespace:fullyQualifiedNamespace
  144. firebaseAppName:currentAppName
  145. googleAppID:currentOptions.googleAppID];
  146. dispatch_queue_t queue = dispatch_queue_create(
  147. [[NSString stringWithFormat:@"testqueue: %d", i] cStringUsingEncoding:NSUTF8StringEncoding],
  148. DISPATCH_QUEUE_SERIAL);
  149. _configFetch[i] = OCMPartialMock([[RCNConfigFetch alloc] initWithContent:configContent
  150. DBManager:_DBManager
  151. settings:settings
  152. analytics:nil
  153. experiment:nil
  154. queue:queue
  155. namespace:fullyQualifiedNamespace
  156. options:currentOptions]);
  157. }
  158. }
  159. // Mock instance ID methods.
  160. - (void)mockInstanceIDMethodForTokenAndIdentity:(nullable NSString *)token
  161. tokenError:(nullable NSError *)tokenError
  162. identity:(nullable NSString *)identity
  163. identityError:(nullable NSError *)identityError {
  164. // Mock the installations retreival method.
  165. _installationsMock = OCMClassMock([FIRInstallations class]);
  166. id installationIDCompletionArg =
  167. [OCMArg checkWithBlock:^BOOL(FIRInstallationsIDHandler completion) {
  168. if (completion) {
  169. completion(identity, identityError);
  170. }
  171. return YES;
  172. }];
  173. OCMStub([_installationsMock installationIDWithCompletion:installationIDCompletionArg]);
  174. FIRInstallationsAuthTokenResult *tokenResult;
  175. if (token) {
  176. tokenResult = [[FIRInstallationsAuthTokenResult alloc] initWithToken:token
  177. expirationDate:[NSDate distantFuture]];
  178. }
  179. id authTokenCompletionArg =
  180. [OCMArg checkWithBlock:^BOOL(FIRInstallationsTokenHandler completion) {
  181. if (completion) {
  182. completion(tokenResult, tokenError);
  183. }
  184. return YES;
  185. }];
  186. OCMStub([_installationsMock authTokenWithCompletion:authTokenCompletionArg]);
  187. OCMStub([_installationsMock installationsWithApp:[OCMArg any]]).andReturn(_installationsMock);
  188. [self setUpConfigMock];
  189. }
  190. - (void)tearDown {
  191. [_DBManager removeDatabaseOnDatabaseQueueAtPath:_DBPath];
  192. [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:_userDefaultsSuiteName];
  193. [FIRApp resetApps];
  194. [_installationsMock stopMocking];
  195. _installationsMock = nil;
  196. [super tearDown];
  197. }
  198. // Instance ID token is nil. Error is not nil. Verify fetch fails.
  199. - (void)testNilInstallationsAuthTokenAndError {
  200. NSMutableArray<XCTestExpectation *> *expectations =
  201. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  202. // Set the token as nil.
  203. [self
  204. mockInstanceIDMethodForTokenAndIdentity:nil
  205. tokenError:[NSError
  206. errorWithDomain:kFirebaseInstallationsErrorDomain
  207. code:FIRInstallationsErrorCodeUnknown
  208. userInfo:nil]
  209. identity:nil
  210. identityError:nil];
  211. // Test for each RC FIRApp, namespace instance.
  212. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  213. expectations[i] =
  214. [self expectationWithDescription:
  215. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  216. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  217. FIRRemoteConfigFetchCompletion fetchCompletion =
  218. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  219. XCTAssertNotNil(error);
  220. [expectations[i] fulfill];
  221. };
  222. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  223. }
  224. [self waitForExpectationsWithTimeout:_expectationTimeout
  225. handler:^(NSError *error) {
  226. XCTAssertNil(error);
  227. }];
  228. }
  229. // Test IID error. Subsequent request also fails with same error (b/148975341).
  230. - (void)testMultipleFetchCallsFailing {
  231. NSMutableArray<XCTestExpectation *> *expectations =
  232. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  233. // Set the token as nil.
  234. NSError *tokenError = [NSError errorWithDomain:kFirebaseInstallationsErrorDomain
  235. code:FIRInstallationsErrorCodeUnknown
  236. userInfo:nil];
  237. [self mockInstanceIDMethodForTokenAndIdentity:nil
  238. tokenError:tokenError
  239. identity:nil
  240. identityError:nil];
  241. // Test for each RC FIRApp, namespace instance.
  242. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  243. expectations[i] =
  244. [self expectationWithDescription:
  245. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  246. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  247. FIRRemoteConfigFetchCompletion fetchCompletion =
  248. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  249. XCTAssertNotNil(error);
  250. XCTAssert([[error.userInfo objectForKey:@"NSLocalizedDescription"]
  251. containsString:@"Failed to get installations token"]);
  252. // Make a second fetch call.
  253. [self->_configInstances[i]
  254. fetchWithExpirationDuration:43200
  255. completionHandler:^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  256. XCTAssertNotNil(error);
  257. XCTAssert([[error.userInfo objectForKey:@"NSLocalizedDescription"]
  258. containsString:@"Failed to get installations token"]);
  259. [expectations[i] fulfill];
  260. }];
  261. };
  262. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  263. }
  264. [self waitForExpectationsWithTimeout:_expectationTimeout
  265. handler:^(NSError *error) {
  266. XCTAssertNil(error);
  267. }];
  268. }
  269. // Instance ID token is not nil. Error is not nil. Verify fetch fails.
  270. - (void)testValidInstanceIDTokenAndValidError {
  271. NSMutableArray<XCTestExpectation *> *expectations =
  272. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  273. // Test for each RC FIRApp, namespace instance.
  274. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  275. // Set the token as nil.
  276. NSError *tokenError = [NSError errorWithDomain:kFirebaseInstallationsErrorDomain
  277. code:FIRInstallationsErrorCodeUnknown
  278. userInfo:nil];
  279. [self mockInstanceIDMethodForTokenAndIdentity:@"abcd"
  280. tokenError:tokenError
  281. identity:nil
  282. identityError:nil];
  283. expectations[i] =
  284. [self expectationWithDescription:
  285. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  286. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  287. FIRRemoteConfigFetchCompletion fetchCompletion =
  288. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  289. XCTAssertNotNil(error);
  290. [expectations[i] fulfill];
  291. };
  292. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  293. }
  294. [self waitForExpectationsWithTimeout:_expectationTimeout
  295. handler:^(NSError *error) {
  296. XCTAssertNil(error);
  297. }];
  298. }
  299. // Instance ID token is nil. Error is nil. Verify fetch fails.
  300. - (void)testNilInstanceIDTokenAndNilError {
  301. NSMutableArray<XCTestExpectation *> *expectations =
  302. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  303. // Test for each RC FIRApp, namespace instance.
  304. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  305. // Set the token as nil.
  306. [self mockInstanceIDMethodForTokenAndIdentity:nil
  307. tokenError:nil
  308. identity:nil
  309. identityError:nil];
  310. expectations[i] =
  311. [self expectationWithDescription:
  312. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  313. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  314. FIRRemoteConfigFetchCompletion fetchCompletion =
  315. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  316. XCTAssertNotNil(error);
  317. [expectations[i] fulfill];
  318. };
  319. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  320. }
  321. [self waitForExpectationsWithTimeout:_expectationTimeout
  322. handler:^(NSError *error) {
  323. XCTAssertNil(error);
  324. }];
  325. }
  326. // Instance ID token is valid. InstanceID is nil with no error. Verify fetch fails.
  327. - (void)testNilInstanceIDWithValidInstanceIDToken {
  328. NSMutableArray<XCTestExpectation *> *expectations =
  329. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  330. // Test for each RC FIRApp, namespace instance.
  331. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  332. // Set the token as nil.
  333. [self mockInstanceIDMethodForTokenAndIdentity:@"abcd"
  334. tokenError:nil
  335. identity:nil
  336. identityError:nil];
  337. expectations[i] =
  338. [self expectationWithDescription:
  339. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  340. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  341. FIRRemoteConfigFetchCompletion fetchCompletion =
  342. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  343. XCTAssertNotNil(error);
  344. [expectations[i] fulfill];
  345. };
  346. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  347. }
  348. [self waitForExpectationsWithTimeout:_expectationTimeout
  349. handler:^(NSError *error) {
  350. XCTAssertNil(error);
  351. }];
  352. }
  353. // Instance ID is not nil, but IID SDK returns an error. Also token is valid.
  354. - (void)testValidInstanceIDAndError {
  355. NSMutableArray<XCTestExpectation *> *expectations =
  356. [[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
  357. // Test for each RC FIRApp, namespace instance.
  358. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
  359. // Set the token as nil.
  360. NSError *identityError = [NSError errorWithDomain:kFirebaseInstallationsErrorDomain
  361. code:FIRInstallationsErrorCodeUnknown
  362. userInfo:nil];
  363. [self mockInstanceIDMethodForTokenAndIdentity:@"abcd"
  364. tokenError:nil
  365. identity:@"test-id"
  366. identityError:identityError];
  367. expectations[i] =
  368. [self expectationWithDescription:
  369. [NSString stringWithFormat:@"Test fetch configs successfully - instance %d", i]];
  370. XCTAssertEqual(_configInstances[i].lastFetchStatus, FIRRemoteConfigFetchStatusNoFetchYet);
  371. FIRRemoteConfigFetchCompletion fetchCompletion =
  372. ^void(FIRRemoteConfigFetchStatus status, NSError *error) {
  373. XCTAssertNotNil(error);
  374. [expectations[i] fulfill];
  375. };
  376. [_configInstances[i] fetchWithExpirationDuration:43200 completionHandler:fetchCompletion];
  377. }
  378. [self waitForExpectationsWithTimeout:_expectationTimeout
  379. handler:^(NSError *error) {
  380. XCTAssertNil(error);
  381. }];
  382. }
  383. #pragma mark - Test Helpers
  384. - (FIROptions *)firstAppOptions {
  385. // TODO: Evaluate if we want to hardcode things here instead.
  386. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
  387. GCMSenderID:@"correct_gcm_sender_id"];
  388. options.APIKey = @"correct_api_key";
  389. options.projectID = @"abc-xyz-123";
  390. return options;
  391. }
  392. - (FIROptions *)secondAppOptions {
  393. FIROptions *options =
  394. [[FIROptions alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]]
  395. pathForResource:@"SecondApp-GoogleService-Info"
  396. ofType:@"plist"]];
  397. XCTAssertNotNil(options);
  398. return options;
  399. }
  400. @end