diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index fd33a8b3..db16e0ed 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -1122,6 +1122,7 @@ C5E967572D38013000112DAC /* GeneralInfoStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */; }; C5E9675F2D3849BE00112DAC /* RolloutDefinitionsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9675E2D3849BE00112DAC /* RolloutDefinitionsCache.swift */; }; C5E967602D3849BE00112DAC /* RolloutDefinitionsCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9675E2D3849BE00112DAC /* RolloutDefinitionsCache.swift */; }; + C5E967622D395DAA00112DAC /* RolloutCacheManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967612D395DAA00112DAC /* RolloutCacheManagerTest.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1944,6 +1945,7 @@ C5E967542D37FDD500112DAC /* RolloutCacheConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheConfigurationTest.swift; sourceTree = ""; }; C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoStorageMock.swift; sourceTree = ""; }; C5E9675E2D3849BE00112DAC /* RolloutDefinitionsCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutDefinitionsCache.swift; sourceTree = ""; }; + C5E967612D395DAA00112DAC /* RolloutCacheManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheManagerTest.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -3385,6 +3387,7 @@ 95ABF4B9293003C1006ED016 /* ConfigTest.swift */, 95B0A99F2BC6B72200C31A9E /* SplitClientTests.swift */, C5E967542D37FDD500112DAC /* RolloutCacheConfigurationTest.swift */, + C5E967612D395DAA00112DAC /* RolloutCacheManagerTest.swift */, ); path = Init; sourceTree = ""; @@ -4339,6 +4342,7 @@ 954F9B072570309400140B81 /* SplitDaoTest.swift in Sources */, 95F20F4A27EB533000B5B30A /* TreatmentManagerTest.swift in Sources */, 59F4AAA324FFCD5000A1C69A /* SyncEventBroadcasterStub.swift in Sources */, + C5E967622D395DAA00112DAC /* RolloutCacheManagerTest.swift in Sources */, 95ABF4C029314D3D006ED016 /* ImpressionsStorageTest.swift in Sources */, 5921ED3125378ECD000D6C8B /* StreamingInitTest.swift in Sources */, 95C717DE268CE137002ADB83 /* ImpressionsCountDaoStub.swift in Sources */, diff --git a/SplitTests/DatesTests.swift b/SplitTests/DatesTests.swift index f31990ad..f11c0f7e 100644 --- a/SplitTests/DatesTests.swift +++ b/SplitTests/DatesTests.swift @@ -15,7 +15,7 @@ import Foundation import XCTest -//@testable import Split +@testable import Split class DatesTest: XCTestCase { @@ -47,4 +47,14 @@ class DatesTest: XCTestCase { return calendar.date(from: dateComponents)! } + + func testSecondsToDays() { + let seconds: Int64 = 86400 + let days = Date.secondsToDays(seconds: seconds) + XCTAssertEqual(days, 1, "1 day should be the result") + + let seconds2: Int64 = 172800 + let days2 = Date.secondsToDays(seconds: seconds2) + XCTAssertEqual(days2, 2, "2 days should be the result") + } } diff --git a/SplitTests/Fake/Storage/MySegmentsStorageStub.swift b/SplitTests/Fake/Storage/MySegmentsStorageStub.swift index 97ed1dea..977eed9a 100644 --- a/SplitTests/Fake/Storage/MySegmentsStorageStub.swift +++ b/SplitTests/Fake/Storage/MySegmentsStorageStub.swift @@ -78,9 +78,14 @@ class MySegmentsStorageStub: MySegmentsStorage { return count } - var clearCalled = false + var clearCalledTimes = 0 + var clearCalled: Bool { + get { + return clearCalledTimes > 0 + } + } func clear() { - clearCalled = true + clearCalledTimes+=1 segments.removeAll() } } diff --git a/SplitTests/Fake/Storage/SplitsStorageStub.swift b/SplitTests/Fake/Storage/SplitsStorageStub.swift index 69976b36..65e5a460 100644 --- a/SplitTests/Fake/Storage/SplitsStorageStub.swift +++ b/SplitTests/Fake/Storage/SplitsStorageStub.swift @@ -23,7 +23,12 @@ class SplitsStorageStub: SplitsStorage { var flagsSpec: String = "" var loadLocalCalled = false - var clearCalled = false + var clearCalledTimes = 0 + var clearCalled: Bool { + get { + return clearCalledTimes > 0 + } + } var updatedWithoutChecksSplit: Split? var updatedWithoutChecksExp: XCTestExpectation? @@ -92,7 +97,7 @@ class SplitsStorageStub: SplitsStorage { } func clear() { - clearCalled = true + clearCalledTimes+=1 inMemorySplits.removeAll() } diff --git a/SplitTests/Init/RolloutCacheManagerTest.swift b/SplitTests/Init/RolloutCacheManagerTest.swift new file mode 100644 index 00000000..4493bdc4 --- /dev/null +++ b/SplitTests/Init/RolloutCacheManagerTest.swift @@ -0,0 +1,142 @@ +import XCTest +@testable import Split + +final class RolloutCacheManagerTest: XCTestCase { + + private var rolloutCacheManager: RolloutCacheManager! + private var splitsStorage: SplitsStorageStub! + private var segmentsStorage: MySegmentsStorageStub! + private var generalInfoStorage: GeneralInfoStorageMock! + + override func setUpWithError() throws { + splitsStorage = SplitsStorageStub() + segmentsStorage = MySegmentsStorageStub() + generalInfoStorage = GeneralInfoStorageMock() + } + + func testValidateCacheCallsListener() throws { + rolloutCacheManager = getCacheManager(expiration: 10) + + let result = validateCache() + + XCTAssertTrue(result) + } + + func testValidateCacheCallsClearOnStoragesWhenExpirationIsSurpassed() throws { + rolloutCacheManager = getCacheManager(expiration: 9) + generalInfoStorage.setUpdateTimestamp(timestamp: getTimestamp(plusDays: -10)) + + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertTrue(splitsStorage.clearCalled) + XCTAssertTrue(segmentsStorage.clearCalled) + } + + func testValidateCacheDoesNotCallClearOnStoragesWhenExpirationIsNotSurpassedAndClearOnInitIsFalse() throws { + rolloutCacheManager = getCacheManager(expiration: 10) + generalInfoStorage.setUpdateTimestamp(timestamp: getTimestamp(plusDays: -1)) + + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertFalse(splitsStorage.clearCalled) + XCTAssertFalse(segmentsStorage.clearCalled) + } + + func testValidateCacheCallsClearOnStoragesWhenExpirationIsNotSurpassedAndClearOnInitIsTrue() throws { + rolloutCacheManager = getCacheManager(expiration: 10, clearOnInit: true) + generalInfoStorage.setUpdateTimestamp(timestamp: getTimestamp(plusDays: -1)) + + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertTrue(splitsStorage.clearCalled) + XCTAssertTrue(segmentsStorage.clearCalled) + } + + func testValidateCacheCallsClearOnStorageOnlyOnceWhenExecutedConsecutively() throws { + rolloutCacheManager = getCacheManager(expiration: 10, clearOnInit: true) + generalInfoStorage.setUpdateTimestamp(timestamp: getTimestamp(plusDays: -1)) + + let clearCalledTimes = segmentsStorage.clearCalledTimes + let result = validateCache() + let result2 = validateCache() + + XCTAssertTrue(result) + XCTAssertTrue(result2) + XCTAssertEqual(clearCalledTimes, 0) + XCTAssertEqual(splitsStorage.clearCalledTimes, 1) + XCTAssertEqual(segmentsStorage.clearCalledTimes, 1) + } + + func testValidateCacheUpdatesLastClearTimestampWhenStoragesAreCleared() throws { + rolloutCacheManager = getCacheManager(expiration: 10, clearOnInit: true) + generalInfoStorage.setUpdateTimestamp(timestamp: getTimestamp(plusDays: -1)) + let initialLastClearTimestamp = generalInfoStorage.getRolloutCacheLastClearTimestamp() + + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertEqual(splitsStorage.clearCalledTimes, 1) + XCTAssertEqual(segmentsStorage.clearCalledTimes, 1) + XCTAssertGreaterThan(generalInfoStorage.getRolloutCacheLastClearTimestamp(), initialLastClearTimestamp) + } + + func testValidateCacheDoesNotUpdateLastClearTimestampWhenStoragesAreNotCleared() throws { + rolloutCacheManager = getCacheManager(expiration: 10) + let updateTimestamp = getTimestamp(plusDays: -1) + generalInfoStorage.setUpdateTimestamp(timestamp: updateTimestamp) + generalInfoStorage.rolloutCacheLastClearTimestamp = 123456 + + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertFalse(splitsStorage.clearCalled) + XCTAssertFalse(segmentsStorage.clearCalled) + XCTAssertEqual(generalInfoStorage.getRolloutCacheLastClearTimestamp(), 123456) + } + + func testDefaultValueForUpdateTimestampDoesNotClearCache() throws { + rolloutCacheManager = getCacheManager(expiration: 10) + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertFalse(splitsStorage.clearCalled) + XCTAssertFalse(segmentsStorage.clearCalled) + } + + func testDefaultValueForLastClearTimestampClearsCacheWhenClearOnInitIsTrue() throws { + rolloutCacheManager = getCacheManager(expiration: 10, clearOnInit: true) + let result = validateCache() + + XCTAssertTrue(result) + XCTAssertEqual(splitsStorage.clearCalledTimes, 1) + XCTAssertEqual(segmentsStorage.clearCalledTimes, 1) + } + + private func getCacheManager(expiration: Int, clearOnInit: Bool = false) -> RolloutCacheManager { + return DefaultRolloutCacheManager( + generalInfoStorage: generalInfoStorage, + rolloutCacheConfiguration: RolloutCacheConfiguration.builder() + .set(expirationDays: expiration) + .set(clearOnInit: clearOnInit) + .build(), + storages: splitsStorage, segmentsStorage) + } + + private func getTimestamp(plusDays: Int) -> Int64 { + return Date.now() + Int64(plusDays * 86400) + } + + /// Runs validate cache and waits for it to finish + /// Returns true if it was successful + private func validateCache() -> Bool { + let latch = DispatchSemaphore(value: 0) + rolloutCacheManager.validateCache { + latch.signal() + } + + return latch.wait(timeout: DispatchTime.now() + 5) == .success + } +} diff --git a/SplitTests/Storage/MyLargeSegmentsStorageTests.swift b/SplitTests/Storage/MyLargeSegmentsStorageTests.swift index d3b0106e..ee6a6959 100644 --- a/SplitTests/Storage/MyLargeSegmentsStorageTests.swift +++ b/SplitTests/Storage/MyLargeSegmentsStorageTests.swift @@ -148,4 +148,37 @@ class MyLargeSegmentsStorageTests: XCTestCase { XCTAssertEqual(100, cn1) XCTAssertEqual(200, cn2) } + + func testClearAll() { + let otherKey = "otherKey" + persistentStorage.persistedSegments = [userKey : dummyChange, + otherKey: SegmentChange(segments: ["s1"], changeNumber: 44) + ] + mySegmentsStorage.loadLocal(forKey: userKey) + mySegmentsStorage.loadLocal(forKey: otherKey) + + let changeNum = mySegmentsStorage.changeNumber(forKey: userKey) + let segments = mySegmentsStorage.getAll(forKey: userKey) + let otherChangeNum = mySegmentsStorage.changeNumber(forKey: otherKey) + let otherSegments = mySegmentsStorage.getAll(forKey: otherKey) + + mySegmentsStorage.clear() + + let newChangeNum = mySegmentsStorage.changeNumber(forKey: userKey) + let newSegments = mySegmentsStorage.getAll(forKey: userKey) + let newOtherChangeNum = mySegmentsStorage.changeNumber(forKey: otherKey) + let newOtherSegments = mySegmentsStorage.getAll(forKey: otherKey) + + XCTAssertEqual(100, changeNum) + XCTAssertEqual(3, segments.count) + XCTAssertTrue(segments.contains("s1")) + XCTAssertTrue(segments.contains("s3")) + XCTAssertEqual(44, otherChangeNum) + XCTAssertEqual(1, otherSegments.count) + XCTAssertTrue(otherSegments.contains("s1")) + XCTAssertEqual(newChangeNum, -1) + XCTAssertEqual(newSegments.count, 0) + XCTAssertEqual(newOtherChangeNum, -1) + XCTAssertEqual(newOtherSegments.count, 0) + } } diff --git a/SplitTests/Storage/MySegmentsDaoTest.swift b/SplitTests/Storage/MySegmentsDaoTest.swift index 930846a6..d60b8dc1 100644 --- a/SplitTests/Storage/MySegmentsDaoTest.swift +++ b/SplitTests/Storage/MySegmentsDaoTest.swift @@ -89,7 +89,26 @@ class MySegmentsDaoTest: XCTestCase { XCTAssertNotEqual(userKey, segment.userKey) XCTAssertFalse(segment.segmentList?.contains("s1") ?? true) -} + } + + func testDeleteAll() { + let helper = IntegrationCoreDataHelper.get(databaseName: "test", + dispatchQueue: DispatchQueue(label: "impression dao test")) + mySegmentsDao = CoreDataMySegmentsDao(coreDataHelper: helper, + entity: .mySegment) + let userKey = "ukey" + let change = SegmentChange(segments: ["s1", "s2"]) + mySegmentsDao.update(userKey: userKey, change: change) + + let initialResultFromDB = mySegmentsDao.getBy(userKey: userKey) + + mySegmentsDao.deleteAll() + + let finalResultFromDB = mySegmentsDao.getBy(userKey: userKey) + + XCTAssertEqual(2, initialResultFromDB?.segments.count) + XCTAssertNil(finalResultFromDB) + } func getBy(userKey: String, coreDataHelper: CoreDataHelper) -> (userKey: String?, segmentList: String?) { var loadedUserKey: String? = nil diff --git a/SplitTests/Storage/MySegmentsStorageTests.swift b/SplitTests/Storage/MySegmentsStorageTests.swift index 3d8c6ee3..fe099931 100644 --- a/SplitTests/Storage/MySegmentsStorageTests.swift +++ b/SplitTests/Storage/MySegmentsStorageTests.swift @@ -119,7 +119,39 @@ class MySegmentsStorageTests: XCTestCase { XCTAssertEqual(-1, cn1) XCTAssertEqual(-1, cn2) - } + func testClearAll() { + let otherKey = "otherKey" + persistentStorage.persistedSegments = [userKey : dummySegments, + otherKey: SegmentChange(segments: ["s1"], changeNumber: 44) + ] + mySegmentsStorage.loadLocal(forKey: userKey) + mySegmentsStorage.loadLocal(forKey: otherKey) + + let changeNum = mySegmentsStorage.changeNumber(forKey: userKey) + let segments = mySegmentsStorage.getAll(forKey: userKey) + let otherChangeNum = mySegmentsStorage.changeNumber(forKey: otherKey) + let otherSegments = mySegmentsStorage.getAll(forKey: otherKey) + + mySegmentsStorage.clear() + + let newChangeNum = mySegmentsStorage.changeNumber(forKey: userKey) + let newSegments = mySegmentsStorage.getAll(forKey: userKey) + let newOtherChangeNum = mySegmentsStorage.changeNumber(forKey: otherKey) + let newOtherSegments = mySegmentsStorage.getAll(forKey: otherKey) + + // for now, CN is always -1 for regular my segments + XCTAssertEqual(-1, changeNum) + XCTAssertEqual(3, segments.count) + XCTAssertTrue(segments.contains("s1")) + XCTAssertTrue(segments.contains("s3")) + XCTAssertEqual(-1, otherChangeNum) + XCTAssertEqual(1, otherSegments.count) + XCTAssertTrue(otherSegments.contains("s1")) + XCTAssertEqual(newChangeNum, -1) + XCTAssertEqual(newSegments.count, 0) + XCTAssertEqual(newOtherChangeNum, -1) + XCTAssertEqual(newOtherSegments.count, 0) + } } diff --git a/SplitTests/Storage/PersistentMySegmentsStorageTests.swift b/SplitTests/Storage/PersistentMySegmentsStorageTests.swift index 17069030..46bc109d 100644 --- a/SplitTests/Storage/PersistentMySegmentsStorageTests.swift +++ b/SplitTests/Storage/PersistentMySegmentsStorageTests.swift @@ -73,5 +73,13 @@ class PersistentMySegmentsStorageTests: XCTestCase { XCTAssertNil(segments?.segments.count) } + + func testDeleteAllCallsDeleteAllOnDao() { + let initialDeleteAllCalled = mySegmentsDao.deleteAllCalled + mySegmentsStorage.deleteAll() + let finalDeleteAllCalled = mySegmentsDao.deleteAllCalled + XCTAssertFalse(initialDeleteAllCalled) + XCTAssertTrue(finalDeleteAllCalled) + } }