RCNInstanceIDTest.m 20 KB

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