diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 346a8d13..192b90b9 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -1114,6 +1114,12 @@ C5E967432D36CB2200112DAC /* GeneralInfoStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967422D36CB2200112DAC /* GeneralInfoStorageTest.swift */; }; C5E9674C2D36E87600112DAC /* GeneralInfoStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9674B2D36E87600112DAC /* GeneralInfoStorage.swift */; }; C5E9674D2D36E87600112DAC /* GeneralInfoStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9674B2D36E87600112DAC /* GeneralInfoStorage.swift */; }; + C5E9674F2D37098000112DAC /* RolloutCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9674E2D37098000112DAC /* RolloutCacheManager.swift */; }; + C5E967502D37098000112DAC /* RolloutCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E9674E2D37098000112DAC /* RolloutCacheManager.swift */; }; + C5E967522D37FA2000112DAC /* RolloutCacheConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967512D37FA2000112DAC /* RolloutCacheConfiguration.swift */; }; + C5E967532D37FA2000112DAC /* RolloutCacheConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967512D37FA2000112DAC /* RolloutCacheConfiguration.swift */; }; + C5E967552D37FDD500112DAC /* RolloutCacheConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967542D37FDD500112DAC /* RolloutCacheConfigurationTest.swift */; }; + C5E967572D38013000112DAC /* GeneralInfoStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1931,6 +1937,10 @@ C5BD1E512D130FB6008EF198 /* splitchanges_toggle.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = splitchanges_toggle.json; sourceTree = ""; }; C5E967422D36CB2200112DAC /* GeneralInfoStorageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoStorageTest.swift; sourceTree = ""; }; C5E9674B2D36E87600112DAC /* GeneralInfoStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoStorage.swift; sourceTree = ""; }; + C5E9674E2D37098000112DAC /* RolloutCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheManager.swift; sourceTree = ""; }; + C5E967512D37FA2000112DAC /* RolloutCacheConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheConfiguration.swift; sourceTree = ""; }; + C5E967542D37FDD500112DAC /* RolloutCacheConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RolloutCacheConfigurationTest.swift; sourceTree = ""; }; + C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralInfoStorageMock.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2150,6 +2160,7 @@ 95715A8029D22C1B00A1B2F9 /* SplitEncryptionLevel.swift */, 95DAF21A29DDFAF4001C7BBE /* SplitHttpsAuthenticator.swift */, 95F7BC222C3876C800C5F2E4 /* SplitCertPinningAuthenticator.swift */, + C5E967512D37FA2000112DAC /* RolloutCacheConfiguration.swift */, ); path = Api; sourceTree = ""; @@ -2590,6 +2601,7 @@ 954F9C6F25795CD300140B81 /* SplitsStorageStub.swift */, 95B17FF4275EAA6F002DC9DF /* InMemoryTelemetryStorageTest.swift */, 95715A9629DB16A000A1B2F9 /* SecureStorageStub.swift */, + C5E967562D38013000112DAC /* GeneralInfoStorageMock.swift */, ); path = Storage; sourceTree = ""; @@ -3077,6 +3089,7 @@ children = ( 95EEF7C6270E16ED00761B9D /* SplitComponentFactory.swift */, 952FA12A2A2E593900264AB5 /* SplitComponentCatalog.swift */, + C5E9674E2D37098000112DAC /* RolloutCacheManager.swift */, ); path = Initialization; sourceTree = ""; @@ -3367,6 +3380,7 @@ 95ABF4B7292FF451006ED016 /* ConfigObjcTest.m */, 95ABF4B9293003C1006ED016 /* ConfigTest.swift */, 95B0A99F2BC6B72200C31A9E /* SplitClientTests.swift */, + C5E967542D37FDD500112DAC /* RolloutCacheConfigurationTest.swift */, ); path = Init; sourceTree = ""; @@ -3929,6 +3943,7 @@ 95CED0362B459A10005E3C34 /* LocalhostSynchronizer.swift in Sources */, 9505682426836B20001D7B10 /* ImpressionsCountRecorder.swift in Sources */, 3B6DEF3020EA6AE50067435E /* Evaluator.swift in Sources */, + C5E967502D37098000112DAC /* RolloutCacheManager.swift in Sources */, 598EDE78224BB3E9005D4762 /* InternalSplitClient.swift in Sources */, 95D9446D283BF76A00D7FFED /* UniqueKeysRecorderWorker.swift in Sources */, 952FA1242A2A5D4D00264AB5 /* FeatureFlagsSynchronizer.swift in Sources */, @@ -3944,6 +3959,7 @@ 955E123F2BFE1E7A00AE6D10 /* HashedImpressionStorage.swift in Sources */, 3B6DEF3A20EA6AE50067435E /* Dictionary+Extensions.swift in Sources */, 95125625276B72B60091895B /* Stopwatch.swift in Sources */, + C5E967522D37FA2000112DAC /* RolloutCacheConfiguration.swift in Sources */, 957E67B025F6B3E1006F5B19 /* BackgroundSyncWorker.swift in Sources */, 3B6DEF2620EA6AE50067435E /* SplitClientConfig.swift in Sources */, 95825BD8271F4E3700A0CDAD /* Bundle+Name.swift in Sources */, @@ -4223,6 +4239,7 @@ 59B2043924F5667A0092F2E9 /* SseNotificationProcessorTest.swift in Sources */, 592C6AC6211B718E002D120C /* SplitEventsManagerTest.swift in Sources */, 95C7569D2696457500696148 /* NotificationHelperStub.swift in Sources */, + C5E967572D38013000112DAC /* GeneralInfoStorageMock.swift in Sources */, 952FA12F2A2F6E4D00264AB5 /* SyncGuardianStub.swift in Sources */, 95F3EFFE258D3C8600084AF8 /* EventsRecorderWorkerTests.swift in Sources */, 955B5960280DF75E00D105CD /* MySegmentsPayloadDecoderTest.swift in Sources */, @@ -4277,6 +4294,7 @@ 59F4AAA82508120500A1C69A /* SyncManagerTest.swift in Sources */, 5919017B24A27629005BD12A /* EndpointTest.swift in Sources */, 5959C479227B89980064F968 /* FactoryRegistryTest.swift in Sources */, + C5E967552D37FDD500112DAC /* RolloutCacheConfigurationTest.swift in Sources */, 595AD21524E1CD4700A7B750 /* JwtTokenParserTest.swift in Sources */, 9573FB37273D762200086DDE /* AnyValueValidatorTests.swift in Sources */, 95ED4A9826497EEA00FD3569 /* TestSplitFactory.swift in Sources */, @@ -4642,6 +4660,7 @@ 95B02CED28D0BDC20030EC8B /* MatcherGroup.swift in Sources */, 95CED0502B4DCDB1005E3C34 /* LocalhostClientManager.swift in Sources */, 95B02CEE28D0BDC20030EC8B /* MatcherProtocol.swift in Sources */, + C5E9674F2D37098000112DAC /* RolloutCacheManager.swift in Sources */, 95B02CEF28D0BDC20030EC8B /* MatcherType.swift in Sources */, 95B02CF028D0BDC20030EC8B /* Segment.swift in Sources */, 95B02CF128D0BDC20030EC8B /* Status.swift in Sources */, @@ -4663,6 +4682,7 @@ 95B02CFF28D0BDC20030EC8B /* SplitClientManager.swift in Sources */, C5E9674D2D36E87600112DAC /* GeneralInfoStorage.swift in Sources */, 95B02D0028D0BDC20030EC8B /* SplitComponentFactory.swift in Sources */, + C5E967532D37FA2000112DAC /* RolloutCacheConfiguration.swift in Sources */, 95B02D0128D0BDC20030EC8B /* SplitApiFacade.swift in Sources */, 95B02D0228D0BDC20030EC8B /* SplitFactory.swift in Sources */, 952FA1392A33778500264AB5 /* SyncGuardian.swift in Sources */, diff --git a/Split/Api/RolloutCacheConfiguration.swift b/Split/Api/RolloutCacheConfiguration.swift new file mode 100644 index 00000000..f318c3b4 --- /dev/null +++ b/Split/Api/RolloutCacheConfiguration.swift @@ -0,0 +1,57 @@ +import Foundation + +/// Configuration class for rollout cache. +@objc public class RolloutCacheConfiguration: NSObject { + private(set) var expirationDays: Int + private(set) var clearOninit: Bool + + init(expirationDays: Int, clearOnInit: Bool) { + self.expirationDays = expirationDays + self.clearOninit = clearOnInit + } + + /// Provides abuilder for RolloutCacheConfiguration. + @objc(builder) + public static func builder() -> Builder { + return Builder() + } + + @objc(RolloutCacheConfigurationBuilder) + public class Builder: NSObject { + private let kMinExpirationDays = 1 + + private var expiration = ServiceConstants.defaultRolloutCacheExpiration + private var clearOnInit = false + + @objc + public func build() -> RolloutCacheConfiguration { + return RolloutCacheConfiguration(expirationDays: expiration, clearOnInit: clearOnInit) + } + + /// Set the expiration time for the rollout definitions cache, in days. Default is 10 days. + /// - Parameter expirationDays: The expiration time in days. + /// - Returns: This builder. + @discardableResult + @objc(setExpirationDays:) + public func set(expirationDays: Int) -> Builder { + if expirationDays < kMinExpirationDays { + Logger.w("Cache expiration must be at least 1 day. Using default value.") + expiration = ServiceConstants.defaultRolloutCacheExpiration + } else { + expiration = expirationDays + } + + return self + } + + /// Set if the rollout definitions cache should be cleared on initialization. Default is false. + /// - Parameter clearOnInit: If the cache should be cleared on initialization. + /// - Returns: This builder. + @discardableResult + @objc(setClearOnInit:) + public func set(clearOnInit: Bool) -> Builder { + self.clearOnInit = clearOnInit + return self + } + } +} diff --git a/Split/Common/ServiceConstants.swift b/Split/Common/ServiceConstants.swift index 2b948642..98bd1934 100644 --- a/Split/Common/ServiceConstants.swift +++ b/Split/Common/ServiceConstants.swift @@ -42,7 +42,7 @@ struct ServiceConstants { static var maxSyncPeriodInMillis: Int64 { return values?.maxSyncPeriodInMillis ?? (defaultSseConnectionDelayInSecs * 1000) } - + static let defaultSegmentsChangeNumber: Int64 = -1 // Created for testing purposes only struct Values { @@ -51,4 +51,5 @@ struct ServiceConstants { static let defaultMlsTimeMillis: Int64 = 60000 static let defaultMlsHash = 1 static let defaultMlsSeed = 0 + static let defaultRolloutCacheExpiration = 10 // days } diff --git a/Split/Initialization/RolloutCacheManager.swift b/Split/Initialization/RolloutCacheManager.swift new file mode 100644 index 00000000..c6f45d3d --- /dev/null +++ b/Split/Initialization/RolloutCacheManager.swift @@ -0,0 +1,24 @@ +import Foundation + +protocol RolloutCacheManager { + func validateCache(listener: (() -> Void)?) +} + +class DefaultRolloutCacheManager: RolloutCacheManager { + private let kMinCacheClearDays = 1 + private let generalInfoStorage: GeneralInfoStorage + private let splitsStorage: SplitsStorage + private let mySegmentsStorage: MySegmentsStorage + private let myLargeSegmentsStorage: MySegmentsStorage + + init(generalInfoStorage: GeneralInfoStorage, splitsStorage: SplitsStorage, mySegmentsStorage: MySegmentsStorage, myLargeSegmentsStorage: MySegmentsStorage) { + self.generalInfoStorage = generalInfoStorage + self.splitsStorage = splitsStorage + self.mySegmentsStorage = mySegmentsStorage + self.myLargeSegmentsStorage = myLargeSegmentsStorage + } + + func validateCache(listener: (() -> Void)?) { + // TODO: implementation + } +} diff --git a/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift b/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift new file mode 100644 index 00000000..66151819 --- /dev/null +++ b/SplitTests/Fake/Storage/GeneralInfoStorageMock.swift @@ -0,0 +1,25 @@ +import Foundation +@testable import Split + +class GeneralInfoStorageMock: GeneralInfoStorage { + + let queue = DispatchQueue(label: "test", target: .global()) + var updateTimestamp: Int64 = 0 + var rolloutCacheLastClearTimestamp: Int64 = 0 + + func getUpdateTimestamp() -> Int64 { + return updateTimestamp + } + + func setUpdateTimestamp(timestamp: Int64) { + updateTimestamp = timestamp + } + + func getRolloutCacheLastClearTimestamp() -> Int64 { + return rolloutCacheLastClearTimestamp + } + + func setRolloutCacheLastClearTimestamp(timestamp: Int64) { + rolloutCacheLastClearTimestamp = timestamp + } +} diff --git a/SplitTests/Helpers/TestingHelper.swift b/SplitTests/Helpers/TestingHelper.swift index 68169e89..6c3e5aa3 100644 --- a/SplitTests/Helpers/TestingHelper.swift +++ b/SplitTests/Helpers/TestingHelper.swift @@ -195,7 +195,8 @@ struct TestingHelper { uniqueKeyStorage: PersistentUniqueKeyStorageStub(), flagSetsCache: FlagSetsCacheMock(), persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), - hashedImpressionsStorage: HashedImpressionsStorageMock()) + hashedImpressionsStorage: HashedImpressionsStorageMock(), + generalInfoStorage: GeneralInfoStorageMock()) } static func createApiFacade() -> SplitApiFacade { diff --git a/SplitTests/Init/RolloutCacheConfigurationTest.swift b/SplitTests/Init/RolloutCacheConfigurationTest.swift new file mode 100644 index 00000000..5c21dbf7 --- /dev/null +++ b/SplitTests/Init/RolloutCacheConfigurationTest.swift @@ -0,0 +1,26 @@ +import XCTest +@testable import Split + +final class RolloutCacheConfigurationTest: XCTestCase { + + func testDefaultValues() throws { + let config = RolloutCacheConfiguration.builder().build() + XCTAssertEqual(config.expirationDays, 10) + XCTAssertFalse(config.clearOninit) + } + + func testExpirationIsCorrectlySet() throws { + let config = RolloutCacheConfiguration.builder().set(expirationDays: 1).build() + XCTAssertEqual(config.expirationDays, 1) + } + + func testClearOnInitIsCorrectlySet() throws { + let config = RolloutCacheConfiguration.builder().set(clearOnInit: true).build() + XCTAssertTrue(config.clearOninit) + } + + func testNegativeExpirationIsSetToDefault() throws { + let config = RolloutCacheConfiguration.builder().set(expirationDays: -1).build() + XCTAssertEqual(config.expirationDays, 10) + } +} diff --git a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift index bc1550ad..66cffb8c 100644 --- a/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift +++ b/SplitTests/Streaming/FeatureFlagsSynchronizerTest.swift @@ -63,7 +63,8 @@ class FeatureFlagsSynchronizerTest: XCTestCase { uniqueKeyStorage: PersistentUniqueKeyStorageStub(), flagSetsCache: FlagSetsCacheMock(), persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), - hashedImpressionsStorage: HashedImpressionsStorageMock()) + hashedImpressionsStorage: HashedImpressionsStorageMock(), + generalInfoStorage: GeneralInfoStorageMock()) splitConfig = SplitClientConfig() splitConfig.syncEnabled = syncEnabled diff --git a/SplitTests/Streaming/ImpressionsTrackerTest.swift b/SplitTests/Streaming/ImpressionsTrackerTest.swift index fb579d7d..ed0473cf 100644 --- a/SplitTests/Streaming/ImpressionsTrackerTest.swift +++ b/SplitTests/Streaming/ImpressionsTrackerTest.swift @@ -358,7 +358,8 @@ class ImpressionsTrackerTest: XCTestCase { uniqueKeyStorage: PersistentUniqueKeyStorageStub(), flagSetsCache: flagSetsCache, persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), - hashedImpressionsStorage: HashedImpressionsStorageMock()) + hashedImpressionsStorage: HashedImpressionsStorageMock(), + generalInfoStorage: GeneralInfoStorageMock()) let apiFacade = try! SplitApiFacade.builder() .setUserKey("userKey") diff --git a/SplitTests/Streaming/SynchronizerTest.swift b/SplitTests/Streaming/SynchronizerTest.swift index 64630eb9..54c7bed6 100644 --- a/SplitTests/Streaming/SynchronizerTest.swift +++ b/SplitTests/Streaming/SynchronizerTest.swift @@ -66,7 +66,8 @@ class SynchronizerTest: XCTestCase { uniqueKeyStorage: PersistentUniqueKeyStorageStub(), flagSetsCache: flagSetsCache, persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), - hashedImpressionsStorage: HashedImpressionsStorageMock()) + hashedImpressionsStorage: HashedImpressionsStorageMock(), + generalInfoStorage: GeneralInfoStorageMock()) splitConfig = SplitClientConfig() splitConfig.syncEnabled = syncEnabled diff --git a/SplitTests/TreatmentManagerTest.swift b/SplitTests/TreatmentManagerTest.swift index 6ba3cd1a..ad0886cd 100644 --- a/SplitTests/TreatmentManagerTest.swift +++ b/SplitTests/TreatmentManagerTest.swift @@ -68,7 +68,8 @@ class TreatmentManagerTest: XCTestCase { uniqueKeyStorage: PersistentUniqueKeyStorageStub(), flagSetsCache: flagSetsCache, persistentHashedImpressionsStorage: PersistentHashedImpressionStorageMock(), - hashedImpressionsStorage: HashedImpressionsStorageMock()) + hashedImpressionsStorage: HashedImpressionsStorageMock(), + generalInfoStorage: GeneralInfoStorageMock()) } }