FIRCLSSettingsTests.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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 "Crashlytics/Crashlytics/Models/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 "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
  23. #import "Crashlytics/UnitTests/Mocks/FABMockApplicationIdentifierModel.h"
  24. #import "Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.h"
  25. const NSString *FIRCLSTestSettingsActivated =
  26. @"{\"settings_version\":3,\"cache_duration\":60,\"features\":{\"collect_logged_exceptions\":"
  27. @"true,\"collect_reports\":true},"
  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},"
  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 *FIRCLSDefaultMockAppDisplayVersion = @"1.2.3-beta.2";
  41. NSString *FIRCLSDifferentMockAppDisplayVersion = @"1.2.3-beta.3";
  42. NSString *FIRCLSDefaultMockAppBuildVersion = @"1024";
  43. NSString *FIRCLSDifferentMockAppBuildVersion = @"2048";
  44. NSString *const TestGoogleAppID = @"1:test:google:app:id";
  45. NSString *const TestChangedGoogleAppID = @"2:changed:google:app:id";
  46. @interface FIRCLSSettings (Testing)
  47. @property(nonatomic, strong) NSDictionary<NSString *, id> *settingsDictionary;
  48. @end
  49. @interface FIRCLSSettingsTests : XCTestCase
  50. @property(nonatomic, retain) FIRCLSMockFileManager *fileManager;
  51. @property(nonatomic, retain) FABMockApplicationIdentifierModel *appIDModel;
  52. @property(nonatomic, retain) FIRCLSSettings *settings;
  53. @end
  54. @implementation FIRCLSSettingsTests
  55. - (void)setUp {
  56. [super setUp];
  57. _fileManager = [[FIRCLSMockFileManager alloc] init];
  58. _appIDModel = [[FABMockApplicationIdentifierModel alloc] init];
  59. _appIDModel.buildInstanceID = FIRCLSDefaultMockBuildInstanceID;
  60. _appIDModel.displayVersion = FIRCLSDefaultMockAppDisplayVersion;
  61. _appIDModel.buildVersion = FIRCLSDefaultMockAppBuildVersion;
  62. _settings = [[FIRCLSSettings alloc] initWithFileManager:_fileManager appIDModel:_appIDModel];
  63. }
  64. - (void)testDefaultSettings {
  65. XCTAssertEqual(self.settings.isCacheExpired, YES);
  66. // Default to an hour
  67. XCTAssertEqual(self.settings.cacheDurationSeconds, 60 * 60);
  68. XCTAssertTrue(self.settings.collectReportsEnabled);
  69. XCTAssertTrue(self.settings.errorReportingEnabled);
  70. XCTAssertTrue(self.settings.customExceptionsEnabled);
  71. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  72. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  73. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  74. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  75. }
  76. - (BOOL)writeSettings:(const NSString *)settings error:(NSError **)error {
  77. return [self writeSettings:settings error:error isCacheKey:NO];
  78. }
  79. - (BOOL)writeSettings:(const NSString *)settings
  80. error:(NSError **)error
  81. isCacheKey:(BOOL)isCacheKey {
  82. NSString *path = _fileManager.settingsFilePath;
  83. if (isCacheKey) {
  84. path = _fileManager.settingsCacheKeyPath;
  85. }
  86. return [self.fileManager createFileAtPath:path
  87. contents:[settings dataUsingEncoding:NSUTF8StringEncoding]
  88. attributes:nil];
  89. }
  90. - (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID
  91. currentTimestamp:(NSTimeInterval)currentTimestamp
  92. expectedRemoveCount:(NSInteger)expectedRemoveCount {
  93. self.fileManager.removeExpectation = [[XCTestExpectation alloc]
  94. initWithDescription:@"FIRCLSMockFileManager.removeExpectation.cache"];
  95. self.fileManager.removeCount = 0;
  96. self.fileManager.expectedRemoveCount = expectedRemoveCount;
  97. [self.settings cacheSettingsWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];
  98. [self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:1];
  99. }
  100. - (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID
  101. currentTimestamp:(NSTimeInterval)currentTimestamp
  102. expectedRemoveCount:(NSInteger)expectedRemoveCount {
  103. self.fileManager.removeExpectation = [[XCTestExpectation alloc]
  104. initWithDescription:@"FIRCLSMockFileManager.removeExpectation.reload"];
  105. self.fileManager.removeCount = 0;
  106. self.fileManager.expectedRemoveCount = expectedRemoveCount;
  107. [self.settings reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];
  108. [self waitForExpectations:@[ self.fileManager.removeExpectation ] timeout:1];
  109. }
  110. - (void)testActivatedSettingsCached {
  111. NSError *error = nil;
  112. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  113. XCTAssertNil(error, "%@", error);
  114. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  115. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  116. XCTAssertEqual(self.settings.isCacheExpired, NO);
  117. XCTAssertEqual(self.settings.cacheDurationSeconds, 60);
  118. XCTAssertTrue(self.settings.collectReportsEnabled);
  119. XCTAssertTrue(self.settings.errorReportingEnabled);
  120. XCTAssertTrue(self.settings.customExceptionsEnabled);
  121. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  122. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  123. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  124. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  125. }
  126. - (void)testInverseDefaultSettingsCached {
  127. NSError *error = nil;
  128. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  129. XCTAssertNil(error, "%@", error);
  130. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  131. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  132. XCTAssertEqual(self.settings.isCacheExpired, NO);
  133. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  134. XCTAssertFalse(self.settings.collectReportsEnabled);
  135. XCTAssertFalse(self.settings.errorReportingEnabled);
  136. XCTAssertFalse(self.settings.customExceptionsEnabled);
  137. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  138. XCTAssertEqual(self.settings.logBufferSize, 128000);
  139. XCTAssertEqual(self.settings.maxCustomExceptions, 1000);
  140. XCTAssertEqual(self.settings.maxCustomKeys, 2000);
  141. }
  142. - (void)testCacheExpiredFromTTL {
  143. NSError *error = nil;
  144. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  145. XCTAssertNil(error, "%@", error);
  146. // 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
  147. // cache and cache key
  148. self.fileManager.expectedRemoveCount = 3;
  149. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  150. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  151. // Go forward in time by 2x the cache duration
  152. NSTimeInterval futureTimestamp = currentTimestamp + (2 * self.settings.cacheDurationSeconds);
  153. [self.settings reloadFromCacheWithGoogleAppID:TestGoogleAppID currentTimestamp:futureTimestamp];
  154. XCTAssertEqual(self.settings.isCacheExpired, YES);
  155. // Since the TTL just expired, do not clear settings
  156. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  157. // Pretend we fetched settings again, but they had different values
  158. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  159. XCTAssertNil(error, "%@", error);
  160. // Cache the settings
  161. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  162. // We should have the updated values that were fetched, and should not be expired
  163. XCTAssertEqual(self.settings.isCacheExpired, NO);
  164. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  165. }
  166. - (void)testCacheExpiredFromBuildInstanceID {
  167. NSError *error = nil;
  168. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  169. XCTAssertNil(error, "%@", error);
  170. // 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
  171. // cache and cache key
  172. self.fileManager.expectedRemoveCount = 3;
  173. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  174. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  175. // Change the Build Instance ID
  176. self.appIDModel.buildInstanceID = FIRCLSDifferentMockBuildInstanceID;
  177. [self.settings reloadFromCacheWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  178. XCTAssertEqual(self.settings.isCacheExpired, YES);
  179. // Since the TTL just expired, do not clear settings
  180. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  181. // Pretend we fetched settings again, but they had different values
  182. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  183. XCTAssertNil(error, "%@", error);
  184. // Cache the settings
  185. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  186. // We should have the updated values that were fetched, and should not be expired
  187. XCTAssertEqual(self.settings.isCacheExpired, NO);
  188. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  189. }
  190. - (void)testCacheExpiredFromAppVersion {
  191. NSError *error = nil;
  192. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  193. XCTAssertNil(error, "%@", error);
  194. // 1 delete for clearing the cache key, plus 2 for the deletes from reloading and clearing the
  195. // cache and cache key
  196. self.fileManager.expectedRemoveCount = 3;
  197. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  198. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  199. // Change the App Version
  200. self.appIDModel.displayVersion = FIRCLSDifferentMockAppDisplayVersion;
  201. self.appIDModel.buildVersion = FIRCLSDifferentMockAppBuildVersion;
  202. [self.settings reloadFromCacheWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  203. XCTAssertEqual(self.settings.isCacheExpired, YES);
  204. // Since the TTL just expired, do not clear settings
  205. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  206. // Pretend we fetched settings again, but they had different values
  207. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  208. XCTAssertNil(error, "%@", error);
  209. // Cache the settings
  210. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  211. // We should have the updated values that were fetched, and should not be expired
  212. XCTAssertEqual(self.settings.isCacheExpired, NO);
  213. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  214. }
  215. - (void)testGoogleAppIDChanged {
  216. NSError *error = nil;
  217. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  218. XCTAssertNil(error, "%@", error);
  219. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  220. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  221. // Different Google App ID
  222. [self reloadFromCacheWithGoogleAppID:TestChangedGoogleAppID
  223. currentTimestamp:currentTimestamp
  224. expectedRemoveCount:2];
  225. XCTAssertEqual(self.settings.isCacheExpired, YES);
  226. // Clear the settings because they were for a different Google App ID
  227. // Pretend we fetched settings again, but they had different values
  228. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  229. XCTAssertNil(error, "%@", error);
  230. // Cache the settings with the new Google App ID
  231. [self.settings cacheSettingsWithGoogleAppID:TestChangedGoogleAppID
  232. currentTimestamp:currentTimestamp];
  233. // Should have new values and not expired
  234. XCTAssertEqual(self.settings.isCacheExpired, NO);
  235. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  236. }
  237. // This is a weird case where we got settings, but never created a cache key for it. We are
  238. // treating this as if the cache was invalid and re-fetching in this case.
  239. - (void)testActivatedSettingsMissingCacheKey {
  240. NSError *error = nil;
  241. [self writeSettings:FIRCLSTestSettingsActivated error:&error];
  242. XCTAssertNil(error, "%@", error);
  243. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  244. // We only expect 1 removal because the cache key doesn't exist,
  245. // and deleteCachedSettings deletes the cache and the cache key
  246. [self reloadFromCacheWithGoogleAppID:TestGoogleAppID
  247. currentTimestamp:currentTimestamp
  248. expectedRemoveCount:1];
  249. XCTAssertEqual(self.settings.isCacheExpired, YES);
  250. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  251. XCTAssertTrue(self.settings.collectReportsEnabled);
  252. XCTAssertTrue(self.settings.errorReportingEnabled);
  253. XCTAssertTrue(self.settings.customExceptionsEnabled);
  254. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  255. XCTAssertEqual(self.settings.logBufferSize, 64 * 1000);
  256. XCTAssertEqual(self.settings.maxCustomExceptions, 8);
  257. XCTAssertEqual(self.settings.maxCustomKeys, 64);
  258. }
  259. // These tests are partially to make sure the SDK doesn't crash when it
  260. // has corrupted settings.
  261. - (void)testCorruptCache {
  262. // First write and load a good settings file
  263. NSError *error = nil;
  264. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  265. XCTAssertNil(error, "%@", error);
  266. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  267. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  268. // Should have "Inverse" values
  269. XCTAssertEqual(self.settings.isCacheExpired, NO);
  270. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  271. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  272. // Then write a corrupted one and cache + reload it
  273. [self writeSettings:FIRCLSTestSettingsCorrupted error:&error];
  274. XCTAssertNil(error, "%@", error);
  275. // Cache them, and reload. Since it's corrupted we should delete it all
  276. [self cacheSettingsWithGoogleAppID:TestGoogleAppID
  277. currentTimestamp:currentTimestamp
  278. expectedRemoveCount:2];
  279. // Should have default values because we deleted the cache and settingsDictionary
  280. XCTAssertEqual(self.settings.isCacheExpired, YES);
  281. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  282. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  283. }
  284. - (void)testCorruptCacheKey {
  285. // First write and load a good settings file
  286. NSError *error = nil;
  287. [self writeSettings:FIRCLSTestSettingsInverse error:&error];
  288. XCTAssertNil(error, "%@", error);
  289. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  290. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  291. // Should have "Inverse" values
  292. XCTAssertEqual(self.settings.isCacheExpired, NO);
  293. XCTAssertEqual(self.settings.cacheDurationSeconds, 12345);
  294. XCTAssertEqual(self.settings.errorLogBufferSize, 128000);
  295. // Then pretend we wrote a corrupted cache key and just reload it
  296. [self writeSettings:FIRCLSTestSettingsCorrupted error:&error isCacheKey:YES];
  297. XCTAssertNil(error, "%@", error);
  298. // Since settings themselves are corrupted, delete it all
  299. [self reloadFromCacheWithGoogleAppID:TestGoogleAppID
  300. currentTimestamp:currentTimestamp
  301. expectedRemoveCount:2];
  302. // Should have default values because we deleted the cache and settingsDictionary
  303. XCTAssertEqual(self.settings.isCacheExpired, YES);
  304. XCTAssertEqual(self.settings.cacheDurationSeconds, 3600);
  305. XCTAssertEqual(self.settings.errorLogBufferSize, 64 * 1000);
  306. }
  307. - (void)testNewReportEndpointSettings {
  308. NSString *settingsJSON =
  309. @"{\"settings_version\":3,\"cache_duration\":60,\"app\":{\"report_upload_variant\":2}}";
  310. NSError *error = nil;
  311. [self writeSettings:settingsJSON error:&error];
  312. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  313. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  314. XCTAssertNil(error, "%@", error);
  315. XCTAssertNotNil(self.settings.settingsDictionary);
  316. NSLog(@"[Debug Log] %@", self.settings.settingsDictionary);
  317. }
  318. - (void)testLegacyReportEndpointSettings {
  319. NSString *settingsJSON =
  320. @"{\"settings_version\":3,\"cache_duration\":60,\"app\":{\"report_upload_variant\":1}}";
  321. NSError *error = nil;
  322. [self writeSettings:settingsJSON error:&error];
  323. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  324. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  325. XCTAssertNil(error, "%@", error);
  326. }
  327. - (void)testLegacyReportEndpointSettingsWithNonExistentKey {
  328. NSString *settingsJSON = @"{\"settings_version\":3,\"cache_duration\":60}";
  329. NSError *error = nil;
  330. [self writeSettings:settingsJSON error:&error];
  331. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  332. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  333. XCTAssertNil(error, "%@", error);
  334. }
  335. - (void)testLegacyReportEndpointSettingsWithUnknownValue {
  336. NSString *newEndpointJSON =
  337. @"{\"settings_version\":3,\"cache_duration\":60,\"app\":{\"report_upload_variant\":xyz}}";
  338. NSError *error = nil;
  339. [self writeSettings:newEndpointJSON error:&error];
  340. NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
  341. [self.settings cacheSettingsWithGoogleAppID:TestGoogleAppID currentTimestamp:currentTimestamp];
  342. XCTAssertNil(error, "%@", error);
  343. }
  344. @end