FIRCLSSettingsTests.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "FIRCLSSettings.h"
  15. #import <Foundation/Foundation.h>
  16. #import <XCTest/XCTest.h>
  17. #if __has_include(<FBLPromises/FBLPromises.h>)
  18. #import <FBLPromises/FBLPromises.h>
  19. #else
  20. #import "FBLPromises.h"
  21. #endif
  22. #import "FABMockApplicationIdentifierModel.h"
  23. #import "FIRCLSFileManager.h"
  24. #import "FIRCLSMockFileManager.h"
  25. const NSString *FIRCLSTestSettingsActivated =
  26. @"{\"settings_version\":3,\"cache_duration\":60,\"features\":{\"collect_logged_exceptions\":"
  27. @"true,\"collect_reports\":true},\"app\":{\"status\":\"activated\",\"update_required\":false},"
  28. @"\"fabric\":{\"org_id\":\"010101000000111111111111\",\"bundle_id\":\"com.lets.test."
  29. @"crashlytics\"}}";
  30. const NSString *FIRCLSTestSettingsInverse =
  31. @"{\"settings_version\":3,\"cache_duration\":12345,\"features\":{\"collect_logged_exceptions\":"
  32. @"false,\"collect_reports\":false},\"app\":{\"status\":\"new\",\"update_required\":true},"
  33. @"\"fabric\":{\"org_id\":\"01e101a0000011b113115111\",\"bundle_id\":\"im.from.the.server\"},"
  34. @"\"session\":{\"log_buffer_size\":128000,\"max_chained_exception_depth\":32,\"max_complete_"
  35. @"sessions_count\":4,\"max_custom_exception_events\":1000,\"max_custom_key_value_pairs\":2000,"
  36. @"\"identifier_mask\":255}}";
  37. const NSString *FIRCLSTestSettingsCorrupted = @"{{{{ non_key: non\"value {}";
  38. NSString *FIRCLSDefaultMockBuildInstanceID = @"12345abcdef";
  39. NSString *FIRCLSDifferentMockBuildInstanceID = @"98765zyxwv";
  40. NSString *const TestGoogleAppID = @"1:test:google:app:id";
  41. NSString *const TestChangedGoogleAppID = @"2:changed:google:app:id";
  42. @interface FIRCLSSettingsTests : XCTestCase
  43. @property(nonatomic, retain) FIRCLSMockFileManager *fileManager;
  44. @property(nonatomic, retain) FABMockApplicationIdentifierModel *appIDModel;
  45. @property(nonatomic, retain) FIRCLSSettings *settings;
  46. @end
  47. @implementation FIRCLSSettingsTests
  48. - (void)setUp {
  49. [super setUp];
  50. _fileManager = [[FIRCLSMockFileManager alloc] init];
  51. // Delete the cache
  52. [_fileManager removeItemAtPath:_fileManager.settingsFilePath];
  53. [_fileManager removeItemAtPath:_fileManager.settingsCacheKeyPath];
  54. _appIDModel = [[FABMockApplicationIdentifierModel alloc] init];
  55. _appIDModel.buildInstanceID = FIRCLSDefaultMockBuildInstanceID;
  56. _settings = [[FIRCLSSettings alloc] initWithFileManager:_fileManager appIDModel:_appIDModel];
  57. }
  58. - (void)testDefaultSettings {
  59. XCTAssertEqual(self.settings.isCacheExpired, YES);
  60. // Default to an hour
  61. XCTAssertEqual(self.settings.cacheDurationSeconds, 60 * 60);
  62. XCTAssertEqualObjects(self.settings.orgID, nil);
  63. XCTAssertEqualObjects(self.settings.fetchedBundleID, nil);
  64. XCTAssertFalse(self.settings.appNeedsOnboarding);
  65. XCTAssertFalse(self.settings.appUpdateRequired);
  66. XCTAssertTrue(self.settings.crashReportingEnabled);
  67. XCTAssertTrue(self.settings.errorReportingEnabled);
  68. XCTAssertTrue(self.settings.customExceptionsEnabled);
  69. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  70. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  71. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  72. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  73. }
  74. - (BOOL)writeSettings:(const NSString *)settings error:(NSError **)error {
  75. return [self writeSettings:settings error:error isCacheKey:NO];
  76. }
  77. - (BOOL)writeSettings:(const NSString *)settings
  78. error:(NSError **)error
  79. isCacheKey:(BOOL)isCacheKey {
  80. NSString *path = _fileManager.settingsFilePath;
  81. if (isCacheKey) {
  82. path = _fileManager.settingsCacheKeyPath;
  83. }
  84. // Create the directory.
  85. [[NSFileManager defaultManager] createDirectoryAtPath:path.stringByDeletingLastPathComponent
  86. withIntermediateDirectories:YES
  87. attributes:nil
  88. error:error];
  89. if (*error != nil) {
  90. return NO;
  91. }
  92. // Create the file.
  93. [settings writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:error];
  94. return YES;
  95. }
  96. - (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID
  97. currentTimestamp:(NSTimeInterval)currentTimestamp
  98. expectedRemoveCount:(NSInteger)expectedRemoveCount {
  99. self.fileManager.removeExpectation = [[XCTestExpectation alloc]
  100. initWithDescription:@"FIRCLSMockFileManager.removeExpectation.cache"];
  101. self.fileManager.removeCount = 0;
  102. self.fileManager.expectedRemoveCount = expectedRemoveCount;
  103. [self.settings cacheSettingsWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];
  104. [self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:1];
  105. }
  106. - (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID
  107. currentTimestamp:(NSTimeInterval)currentTimestamp
  108. expectedRemoveCount:(NSInteger)expectedRemoveCount {
  109. self.fileManager.removeExpectation = [[XCTestExpectation alloc]
  110. initWithDescription:@"FIRCLSMockFileManager.removeExpectation.reload"];
  111. self.fileManager.removeCount = 0;
  112. self.fileManager.expectedRemoveCount = expectedRemoveCount;
  113. [self.settings reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];
  114. [self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:1];
  115. }
  116. - (void)testActivatedSettingsCached {
  117. NSError *error = nil;
  118. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  119. XCTAssertNil(error, "%@", error);
  120. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  121. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  122. XCTAssertEqual(self.settings.isCacheExpired, NO);
  123. XCTAssertEqual(self.settings.cacheDurationSeconds, 60);
  124. XCTAssertEqualObjects(self.settings.orgID, @"010101000000111111111111");
  125. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"com.lets.test.crashlytics");
  126. XCTAssertFalse(self.settings.appNeedsOnboarding);
  127. XCTAssertFalse(self.settings.appUpdateRequired);
  128. XCTAssertTrue(self.settings.crashReportingEnabled);
  129. XCTAssertTrue(self.settings.errorReportingEnabled);
  130. XCTAssertTrue(self.settings.customExceptionsEnabled);
  131. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  132. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  133. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  134. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  135. }
  136. - (void)testInverseDefaultSettingsCached {
  137. NSError *error = nil;
  138. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  139. XCTAssertNil(error, "%@", error);
  140. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  141. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  142. XCTAssertEqual(self.settings.isCacheExpired, NO);
  143. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  144. XCTAssertEqualObjects(self.settings.orgID, @"01e101a0000011b113115111");
  145. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"im.from.the.server");
  146. XCTAssertTrue(self.settings.appNeedsOnboarding);
  147. XCTAssertTrue(self.settings.appUpdateRequired);
  148. XCTAssertFalse(self.settings.crashReportingEnabled);
  149. XCTAssertFalse(self.settings.errorReportingEnabled);
  150. XCTAssertFalse(self.settings.customExceptionsEnabled);
  151. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  152. XCTAssertEqual(self.settings.logBufferSize, 128000);
  153. XCTAssertEqual(self.settings.maxCustomExceptions, 1000);
  154. XCTAssertEqual(self.settings.maxCustomKeys, 2000);
  155. }
  156. - (void)testCacheExpiredFromTTL {
  157. NSError *error = nil;
  158. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  159. XCTAssertNil(error, "%@", error);
  160. // 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
  161. // cache and cache key
  162. self.fileManager.expectedRemoveCount = 3;
  163. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  164. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  165. // Go forward in time by 2x the cache duration
  166. NSTimeInterval futureTimestamp = currentTimestamp + (2 * self.settings.cacheDurationSeconds);
  167. [self.settings reloadFromCacheWithGoogleAppID:TestGoogleAppID currentTimestamp:futureTimestamp];
  168. XCTAssertEqual(self.settings.isCacheExpired, YES);
  169. // Since the TTL just expired, do not clear settings
  170. XCTAssertEqualObjects(self.settings.orgID, @"010101000000111111111111");
  171. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"com.lets.test.crashlytics");
  172. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  173. // Pretend we fetched settings again, but they had different values
  174. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  175. XCTAssertNil(error, "%@", error);
  176. // Cache the settings
  177. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  178. // We should have the updated values that were fetched, and should not be expired
  179. XCTAssertEqual(self.settings.isCacheExpired, NO);
  180. XCTAssertEqualObjects(self.settings.orgID, @"01e101a0000011b113115111");
  181. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"im.from.the.server");
  182. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  183. }
  184. - (void)testCacheExpiredFromBuildInstanceID {
  185. NSError *error = nil;
  186. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  187. XCTAssertNil(error, "%@", error);
  188. // 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
  189. // cache and cache key
  190. self.fileManager.expectedRemoveCount = 3;
  191. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  192. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  193. // Change the Build Instance ID
  194. self.appIDModel.buildInstanceID = FIRCLSDifferentMockBuildInstanceID;
  195. [self.settings reloadFromCacheWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  196. XCTAssertEqual(self.settings.isCacheExpired, YES);
  197. // Since the TTL just expired, do not clear settings
  198. XCTAssertEqualObjects(self.settings.orgID, @"010101000000111111111111");
  199. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"com.lets.test.crashlytics");
  200. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  201. // Pretend we fetched settings again, but they had different values
  202. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  203. XCTAssertNil(error, "%@", error);
  204. // Cache the settings
  205. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  206. // We should have the updated values that were fetched, and should not be expired
  207. XCTAssertEqual(self.settings.isCacheExpired, NO);
  208. XCTAssertEqualObjects(self.settings.orgID, @"01e101a0000011b113115111");
  209. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"im.from.the.server");
  210. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  211. }
  212. - (void)testGoogleAppIDChanged {
  213. NSError *error = nil;
  214. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  215. XCTAssertNil(error, "%@", error);
  216. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  217. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  218. // Different Google App ID
  219. [self reloadFromCacheWithGoogleAppID:TestChangedGoogleAppID
  220. currentTimestamp:currentTimestamp
  221. expectedRemoveCount:2];
  222. XCTAssertEqual(self.settings.isCacheExpired, YES);
  223. // Clear the settings because they were for a different Google App ID
  224. XCTAssertEqualObjects(self.settings.orgID, nil);
  225. XCTAssertEqualObjects(self.settings.fetchedBundleID, nil);
  226. // Pretend we fetched settings again, but they had different values
  227. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  228. XCTAssertNil(error, "%@", error);
  229. // Cache the settings with the new Google App ID
  230. [self.settings cacheSettingsWithGoogleAppID:TestChangedGoogleAppID
  231. currentTimestamp:currentTimestamp];
  232. // Should have new values and not expired
  233. XCTAssertEqual(self.settings.isCacheExpired, NO);
  234. XCTAssertEqualObjects(self.settings.orgID, @"010101000000111111111111");
  235. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"com.lets.test.crashlytics");
  236. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  237. }
  238. // This is a weird case where we got settings, but never created a cache key for it. We are treating
  239. // this as if the cache was invalid and re-fetching in this case.
  240. - (void)testActivatedSettingsMissingCacheKey {
  241. NSError *error = nil;
  242. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  243. XCTAssertNil(error, "%@", error);
  244. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  245. // We only expect 1 removal because the cache key doesn't exist,
  246. // and deleteCachedSettings deletes the cache and the cache key
  247. [self reloadFromCacheWithGoogleAppID:TestGoogleAppID
  248. currentTimestamp:currentTimestamp
  249. expectedRemoveCount:1];
  250. XCTAssertEqual(self.settings.isCacheExpired, YES);
  251. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  252. XCTAssertEqualObjects(self.settings.orgID, nil);
  253. XCTAssertEqualObjects(self.settings.fetchedBundleID, nil);
  254. XCTAssertFalse(self.settings.appNeedsOnboarding);
  255. XCTAssertFalse(self.settings.appUpdateRequired);
  256. XCTAssertTrue(self.settings.crashReportingEnabled);
  257. XCTAssertTrue(self.settings.errorReportingEnabled);
  258. XCTAssertTrue(self.settings.customExceptionsEnabled);
  259. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  260. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  261. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  262. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  263. }
  264. // These tests are partially to make sure the SDK doesn't crash when it
  265. // has corrupted settings.
  266. - (void)testCorruptCache {
  267. // First write and load a good settings file
  268. NSError *error = nil;
  269. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  270. XCTAssertNil(error, "%@", error);
  271. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  272. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  273. // Should have "Inverse" values
  274. XCTAssertEqual(self.settings.isCacheExpired, NO);
  275. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  276. XCTAssertEqualObjects(self.settings.orgID, @"01e101a0000011b113115111");
  277. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"im.from.the.server");
  278. XCTAssertTrue(self.settings.appNeedsOnboarding);
  279. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  280. // Then write a corrupted one and cache + reload it
  281. [self writeSettings:FIRCLSTestSettingsCorrupted error:&error];
  282. XCTAssertNil(error, "%@", error);
  283. // Cache them, and reload. Since it's corrupted we should delete it all
  284. [self cacheSettingsWithGoogleAppID:TestGoogleAppID
  285. currentTimestamp:currentTimestamp
  286. expectedRemoveCount:2];
  287. // Should have default values because we deleted the cache and settingsDictionary
  288. XCTAssertEqual(self.settings.isCacheExpired, YES);
  289. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  290. XCTAssertEqualObjects(self.settings.orgID, nil);
  291. XCTAssertEqualObjects(self.settings.fetchedBundleID, nil);
  292. XCTAssertFalse(self.settings.appNeedsOnboarding);
  293. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  294. }
  295. - (void)testCorruptCacheKey {
  296. // First write and load a good settings file
  297. NSError *error = nil;
  298. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  299. XCTAssertNil(error, "%@", error);
  300. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  301. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  302. // Should have "Inverse" values
  303. XCTAssertEqual(self.settings.isCacheExpired, NO);
  304. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  305. XCTAssertEqualObjects(self.settings.orgID, @"01e101a0000011b113115111");
  306. XCTAssertEqualObjects(self.settings.fetchedBundleID, @"im.from.the.server");
  307. XCTAssertTrue(self.settings.appNeedsOnboarding);
  308. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  309. // Then pretend we wrote a corrupted cache key and just reload it
  310. [self writeSettings:FIRCLSTestSettingsCorrupted error:&error isCacheKey:YES];
  311. XCTAssertNil(error, "%@", error);
  312. // Since settings themselves are corrupted, delete it all
  313. [self reloadFromCacheWithGoogleAppID:TestGoogleAppID
  314. currentTimestamp:currentTimestamp
  315. expectedRemoveCount:2];
  316. // Should have default values because we deleted the cache and settingsDictionary
  317. XCTAssertEqual(self.settings.isCacheExpired, YES);
  318. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  319. XCTAssertEqualObjects(self.settings.orgID, nil);
  320. XCTAssertEqualObjects(self.settings.fetchedBundleID, nil);
  321. XCTAssertFalse(self.settings.appNeedsOnboarding);
  322. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  323. }
  324. @end