diff --git a/.github/workflows/test_ios_streaming_2.yaml b/.github/workflows/test_ios_streaming_2.yaml new file mode 100644 index 00000000..07ea9208 --- /dev/null +++ b/.github/workflows/test_ios_streaming_2.yaml @@ -0,0 +1,39 @@ +name: Build and Test iOS Streaming 1 + +on: + push: + branches: + - master + pull_request: + branches: + - master + - development + +jobs: + build: + runs-on: [macos-latest] + + steps: + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 13.2.1 + + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Test iOS Streaming integration + uses: sersoft-gmbh/xcodebuild-action@v1 + with: + action: build test + build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES + configuration: Debug + derived-data-path: "${{github.workspace}}/SplitApp" + destination: 'platform=iOS Simulator,OS=15.2,name=iPhone 12' + project: Split.xcodeproj + scheme: Split + sdk: 'iphonesimulator' + test-plan: 'SplitiOSStreaming_2' + use-xcpretty: true diff --git a/CHANGES.txt b/CHANGES.txt index 1c6bb787..afe79ee7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.24.0: (Dec 6, 2023) +- Added `prefix` configuration parameter, which allows to use a prefix when naming the SDK storage. Use this when using multiple `SplitFactory` instances with the same SDK key. + 2.23.0: (Nov 1, 2023) - Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation): - Added new variations of the get treatment methods to support evaluating flags in given flag set/s. diff --git a/Split.podspec b/Split.podspec index e58a05fb..16ea32af 100644 --- a/Split.podspec +++ b/Split.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Split' s.module_name = 'Split' - s.version = '2.23.0' + s.version = '2.24.0' s.summary = 'iOS SDK for Split' s.description = <<-DESC This SDK is designed to work with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 339081a8..c94680e8 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -825,6 +825,9 @@ 95880CFC2AEFF193000498A0 /* Array+asSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9572BA7C2AC37B7400C10FC1 /* Array+asSet.swift */; }; 95880CFD2AEFF1B2000498A0 /* Substring+asString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95737E0F2AD47A42007FD15C /* Substring+asString.swift */; }; 95880D092AF201DC000498A0 /* SplitiOSStreaming_1.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 95880D082AF201DC000498A0 /* SplitiOSStreaming_1.xctestplan */; }; + 958F98722B1124EC001F35B3 /* SplitBgSynchronizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 958F98712B1124EC001F35B3 /* SplitBgSynchronizerTests.swift */; }; + 958F98742B1129D7001F35B3 /* KeyValueStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 958F98732B1129D7001F35B3 /* KeyValueStorageMock.swift */; }; + 958F987B2B1669BC001F35B3 /* SplitiOSStreaming_2.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 958F987A2B1669BC001F35B3 /* SplitiOSStreaming_2.xctestplan */; }; 9595910526DFB1AB009E7944 /* DecompressionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9595910426DFB1AB009E7944 /* DecompressionTest.swift */; }; 9595910D26DFE149009E7944 /* lorem_ipsum_zlib.txt in Resources */ = {isa = PBXBuildFile; fileRef = 9595910C26DFE149009E7944 /* lorem_ipsum_zlib.txt */; }; 9595911126DFFF87009E7944 /* CompressionUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9595911026DFFF87009E7944 /* CompressionUtil.swift */; }; @@ -1829,6 +1832,9 @@ 95825C012721F0F800A0CDAD /* UserKeyEncondingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserKeyEncondingTest.swift; sourceTree = ""; }; 9583553F28464FD300014F0C /* SingleSyncTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSyncTest.swift; sourceTree = ""; }; 95880D082AF201DC000498A0 /* SplitiOSStreaming_1.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SplitiOSStreaming_1.xctestplan; sourceTree = ""; }; + 958F98712B1124EC001F35B3 /* SplitBgSynchronizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitBgSynchronizerTests.swift; sourceTree = ""; }; + 958F98732B1129D7001F35B3 /* KeyValueStorageMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueStorageMock.swift; sourceTree = ""; }; + 958F987A2B1669BC001F35B3 /* SplitiOSStreaming_2.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SplitiOSStreaming_2.xctestplan; sourceTree = ""; }; 9595910426DFB1AB009E7944 /* DecompressionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecompressionTest.swift; sourceTree = ""; }; 9595910C26DFE149009E7944 /* lorem_ipsum_zlib.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = lorem_ipsum_zlib.txt; sourceTree = ""; }; 9595911026DFFF87009E7944 /* CompressionUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompressionUtil.swift; sourceTree = ""; }; @@ -2044,6 +2050,7 @@ 950F72FD292E87A4008A0040 /* SplitiOSIntegration.xctestplan */, 956A7E1A2970B0F20080D53C /* SplitiOSStreaming.xctestplan */, 95880D082AF201DC000498A0 /* SplitiOSStreaming_1.xctestplan */, + 958F987A2B1669BC001F35B3 /* SplitiOSStreaming_2.xctestplan */, 952FA1682A44C0FB00264AB5 /* SplitStreamingUT.xctestplan */, 95D83A962A561CB000ADA55E /* SplitPushManagerUT.xctestplan */, 95B0307B28D275020030EC8B /* SplitiOSUnit.xctestplan */, @@ -2569,6 +2576,7 @@ 95C1600E27D28FD4008562E3 /* PersistentAttributesStorageTests.swift */, 9557C1C92614F67700CD9B5C /* DbForTwoDifferentApiKeyTest.swift */, 9572BA842AC7611400C10FC1 /* FlagSetsCacheTests.swift */, + 958F98712B1124EC001F35B3 /* SplitBgSynchronizerTests.swift */, ); path = Storage; sourceTree = ""; @@ -2673,6 +2681,7 @@ 594F5F5D253F641000A945B4 /* BackoffCounterTimerStub.swift */, 9573FB25273C50D400086DDE /* EvaluatorStub.swift */, 95E8193F2878922000D99093 /* LogPrinterStub.swift */, + 958F98732B1129D7001F35B3 /* KeyValueStorageMock.swift */, ); path = Fake; sourceTree = ""; @@ -3602,6 +3611,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 958F987B2B1669BC001F35B3 /* SplitiOSStreaming_2.xctestplan in Resources */, 95880D092AF201DC000498A0 /* SplitiOSStreaming_1.xctestplan in Resources */, 599EA57622666B84006CBA89 /* LICENSE in Resources */, 3B6DEF5C20EA6AE50067435E /* .gitkeep in Resources */, @@ -4092,6 +4102,7 @@ 9572BA872AC76EA800C10FC1 /* FlagSetsCacheMock.swift in Sources */, 9546079625BF3945007FFD35 /* ImpressionsLoggerStub.swift in Sources */, 95F3EFF8258D36F800084AF8 /* HttpEventsRecorderTests.swift in Sources */, + 958F98742B1129D7001F35B3 /* KeyValueStorageMock.swift in Sources */, 9519A92A27DA9AF000278AEC /* SynchronizerTest.swift in Sources */, 95623DA82757FDDE0006A8F1 /* TelemetryProducerStub.swift in Sources */, 95E819402878922000D99093 /* LogPrinterStub.swift in Sources */, @@ -4165,6 +4176,7 @@ 955428E02568155B00331356 /* PersistentImpressionsStorageTest.swift in Sources */, 5912D152219A022000BC698C /* CsvHelper.swift in Sources */, 9505682026825B53001D7B10 /* ImpressionsCounterTest.swift in Sources */, + 958F98722B1124EC001F35B3 /* SplitBgSynchronizerTests.swift in Sources */, 59F4AABF2513A97B00A1C69A /* PeriodicSplitsSyncWorkerTest.swift in Sources */, 5982D934219F3D2E00230F44 /* MatcherEvalTests.swift in Sources */, 95C0577C294A247D007E4D6A /* UserConsentModeNoneTest.swift in Sources */, diff --git a/Split.xcodeproj/xcshareddata/xcschemes/Split.xcscheme b/Split.xcodeproj/xcshareddata/xcschemes/Split.xcscheme index 00caa6f0..6f552e10 100644 --- a/Split.xcodeproj/xcshareddata/xcschemes/Split.xcscheme +++ b/Split.xcodeproj/xcshareddata/xcschemes/Split.xcscheme @@ -73,6 +73,9 @@ + + SplitEncryptionLevel { - let rawValue = GlobalSecureStorage.shared.getInt(item: .dbEncryptionLevel(apiKey)) + static func currentEncryptionLevel(dbKey: String) -> SplitEncryptionLevel { + let rawValue = GlobalSecureStorage.shared.getInt(item: .dbEncryptionLevel(dbKey)) ?? SplitEncryptionLevel.none.rawValue return SplitEncryptionLevel(rawValue: rawValue) ?? .none } @@ -23,23 +23,23 @@ struct SplitDatabaseHelper { GlobalSecureStorage.shared.set(item: level.rawValue, for: .dbEncryptionLevel(apiKey)) } - static func currentEncryptionKey(for apiKey: String) -> Data? { + static func currentEncryptionKey(for dbKey: String) -> Data? { // If there is a stored key, let's use it - if let encKey = GlobalSecureStorage.shared.getString(item: .dbEncryptionKey(apiKey)) { + if let encKey = GlobalSecureStorage.shared.getString(item: .dbEncryptionKey(dbKey)) { return Base64Utils.decodeBase64NoPadding(encKey) } // If not, try to create a new one if let newKey = DefaultKeyGenerator().generateKey(size: ServiceConstants.aes128KeyLength) { - setCurrentEncryptionKey(newKey, for: apiKey) + setCurrentEncryptionKey(newKey, for: dbKey) return newKey } // If creation fails (even thought it shouldn't) let's use the api key - if let newKey = apiKey.dataBytes { - setCurrentEncryptionKey(newKey, for: apiKey) - return apiKey.dataBytes + if let newKey = dbKey.dataBytes { + setCurrentEncryptionKey(newKey, for: dbKey) + return dbKey.dataBytes } // If everything fails @@ -57,7 +57,8 @@ struct SplitDatabaseHelper { telemetryStorage: TelemetryStorage?, testDatabase: SplitDatabase?) throws -> SplitStorageContainer { - let previousEncryptionLevel = currentEncryptionLevel(apiKey: apiKey) + let dbKey = buildDbKey(prefix: splitClientConfig.prefix, sdkKey: apiKey) + let previousEncryptionLevel = currentEncryptionLevel(dbKey: dbKey) var splitDatabase = testDatabase var dbHelper: CoreDataHelper? if let testDb = testDatabase as? TestSplitDatabase { @@ -74,17 +75,17 @@ struct SplitDatabaseHelper { let encryptionLevel: SplitEncryptionLevel = splitClientConfig.encryptionEnabled ? .aes128Cbc : .none var cipherKey: Data? if encryptionLevel != .none { - cipherKey = currentEncryptionKey(for: apiKey) + cipherKey = currentEncryptionKey(for: dbKey) } if previousEncryptionLevel != encryptionLevel, - let dbCipherKey = cipherKey ?? currentEncryptionKey(for: apiKey) { + let dbCipherKey = cipherKey ?? currentEncryptionKey(for: dbKey) { let dbCipher = try DbCipher(cipherKey: dbCipherKey, from: previousEncryptionLevel, to: encryptionLevel, coreDataHelper: dbHelper) dbCipher.apply() - setCurrentEncryptionLevel(encryptionLevel, for: apiKey) + setCurrentEncryptionLevel(encryptionLevel, for: dbKey) } if splitDatabase == nil { @@ -99,7 +100,7 @@ struct SplitDatabaseHelper { throw GenericError.couldNotCreateCache } - let flagSetsCache: FlagSetsCache = + let flagSetsCache: FlagSetsCache = DefaultFlagSetsCache(setsInFilter: splitClientConfig.bySetsFilter()?.values.asSet()) let persistentSplitsStorage = DefaultPersistentSplitsStorage(database: splitDatabase) let splitsStorage = openSplitsStorage(database: splitDatabase, flagSetsCache: flagSetsCache) @@ -151,8 +152,10 @@ struct SplitDatabaseHelper { return DefaultPersistentSplitsStorage(database: database) } - static func openSplitsStorage(database: SplitDatabase, flagSetsCache: FlagSetsCache) -> SplitsStorage { - return DefaultSplitsStorage(persistentSplitsStorage: openPersistentSplitsStorage(database: database), flagSetsCache: flagSetsCache) + static func openSplitsStorage(database: SplitDatabase, + flagSetsCache: FlagSetsCache) -> SplitsStorage { + return DefaultSplitsStorage(persistentSplitsStorage: openPersistentSplitsStorage(database: database), + flagSetsCache: flagSetsCache) } static func openPersistentMySegmentsStorage(database: SplitDatabase) -> PersistentMySegmentsStorage { @@ -199,11 +202,11 @@ struct SplitDatabaseHelper { return MainEventsStorage(persistentStorage: persistentStorage) } - static func databaseName(apiKey: String) -> String? { + static func databaseName(prefix: String?, apiKey: String) -> String? { if apiKey.count < kDbMagicCharsCount * 2 { return nil } - return "\(apiKey.prefix(kDbMagicCharsCount))\(apiKey.suffix(kDbMagicCharsCount))" + return "\(prefix ?? "")\(apiKey.prefix(kDbMagicCharsCount))\(apiKey.suffix(kDbMagicCharsCount))" } static func sanitizeForFolderName(_ string: String) -> String { @@ -232,4 +235,8 @@ struct SplitDatabaseHelper { return DefaultCipher(cipherKey: cipherKey) } + + static func buildDbKey(prefix: String?, sdkKey: String) -> String { + return "\(prefix ?? "")\(sdkKey)" + } } diff --git a/Split/Common/Utils/Version.swift b/Split/Common/Utils/Version.swift index 4f30d0e6..3470b2d2 100644 --- a/Split/Common/Utils/Version.swift +++ b/Split/Common/Utils/Version.swift @@ -9,7 +9,7 @@ import Foundation class Version { private static let kSdkPlatform: String = "ios" - private static let kVersion = "2.23.0" + private static let kVersion = "2.24.0" static var semantic: String { return kVersion diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index f86443e7..aed1498e 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -171,7 +171,7 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { if !isSdkReadyFired() { return } - let storedChangeNumber = splitsStorage.changeNumber + guard let result = try? syncHelper.sync(since: splitsStorage.changeNumber) else { return } diff --git a/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift b/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift index 3d0e7ec1..a3ccccda 100644 --- a/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift +++ b/Split/FetcherEngine/Refresh/SplitBgSynchronizer.swift @@ -14,38 +14,44 @@ import BackgroundTasks @objc public static let shared = SplitBgSynchronizer() - private struct SyncItem: Codable { + // Visible for testing + struct SyncItem: Codable { let apiKey: String var encryptionLevel: Int = 0 var userKeys: [String: Int64] = [:] // UserKey, Timestamp + var prefix: String? } - private typealias BgSyncSchedule = [String: SyncItem] + // Visible for testing + typealias BgSyncSchedule = [String: SyncItem] + private let taskId = "io.split.bg-sync.task" private static let kTimeInterval = ServiceConstants.backgroundSyncPeriod static let kRegistrationExpiration = 3600 * 24 * 90 // 90 days var globalStorage: KeyValueStorage = GlobalSecureStorage.shared - @objc public func register(apiKey: String, + @objc public func register(dbKey: String, // prefix + apiKey + prefix: String?, userKey: String, encryptionLevel: SplitEncryptionLevel = .none) { var syncMap = getSyncTaskMap() - var syncItem = syncMap[apiKey] ?? SyncItem(apiKey: apiKey) + var syncItem = syncMap[dbKey] ?? SyncItem(apiKey: dbKey) + syncItem.prefix = prefix syncItem.userKeys[userKey] = Date().unixTimestamp() syncItem.encryptionLevel = encryptionLevel.rawValue - syncMap[apiKey] = syncItem + syncMap[dbKey] = syncItem globalStorage.set(item: syncMap, for: .backgroundSyncSchedule) } - @objc public func unregister(apiKey: String, userKey: String) { + @objc public func unregister(dbKey: String, userKey: String) { var syncMap = getSyncTaskMap() - if var item = syncMap[apiKey], item.userKeys[userKey] != nil { + if var item = syncMap[dbKey], item.userKeys[userKey] != nil { item.userKeys.removeValue(forKey: userKey) if item.userKeys.count > 0 { - syncMap[apiKey] = item + syncMap[dbKey] = item } else { - syncMap.removeValue(forKey: apiKey) + syncMap.removeValue(forKey: dbKey) } globalStorage.set(item: syncMap, for: .backgroundSyncSchedule) } @@ -70,7 +76,9 @@ import BackgroundTasks } for item in syncList.values { do { - let executor = try BackgroundSyncExecutor(apiKey: item.apiKey, + // TODO: Create BGSyncExecutor using a factory to allow testing + let executor = try BackgroundSyncExecutor(prefix: item.prefix, + apiKey: item.apiKey, userKeys: item.userKeys, serviceEndpoints: serviceEndpoints) executor.execute(operationQueue: operationQueue) @@ -123,20 +131,21 @@ struct BackgroundSyncExecutor { private let splitsSyncWorker: BackgroundSyncWorker private let eventsRecorderWorker: RecorderWorker private let impressionsRecorderWorker: RecorderWorker - private let apiKey: String + private let mapKey: String private let userKeys: [String: Int64] private let mySegmentsFetcher: HttpMySegmentsFetcher - init(apiKey: String, userKeys: [String: Int64], + init(prefix: String?, apiKey: String, userKeys: [String: Int64], serviceEndpoints: ServiceEndpoints? = nil) throws { - self.apiKey = apiKey + self.mapKey = SplitDatabaseHelper.buildDbKey(prefix: prefix, sdkKey: apiKey) self.userKeys = userKeys - let cipherKey = SplitDatabaseHelper.currentEncryptionKey(for: apiKey) - let encryptionLevel = SplitDatabaseHelper.currentEncryptionLevel(apiKey: apiKey) + let cipherKey = SplitDatabaseHelper.currentEncryptionKey(for: mapKey) + let encryptionLevel = SplitDatabaseHelper.currentEncryptionLevel(dbKey: mapKey) - let databaseName = SplitDatabaseHelper.databaseName(apiKey: apiKey) ?? ServiceConstants.defaultDataFolder + let databaseName = SplitDatabaseHelper.databaseName(prefix: prefix, + apiKey: apiKey) ?? ServiceConstants.defaultDataFolder guard let dbHelper = CoreDataHelperBuilder.build(databaseName: databaseName) else { throw GenericError.couldNotCreateCache @@ -164,12 +173,12 @@ struct BackgroundSyncExecutor { self.mySegmentsFetcher = DefaultHttpMySegmentsFetcher(restClient: restClient, syncHelper: DefaultSyncHelper(telemetryProducer: nil)) - let bySetsFilter = splitsStorage.getBySetsFilter() let cacheExpiration = Int64(ServiceConstants.cacheExpirationInSeconds) + let changeProcessor = DefaultSplitChangeProcessor(filterBySet: bySetsFilter) self.splitsSyncWorker = BackgroundSplitsSyncWorker(splitFetcher: splitsFetcher, persistentSplitsStorage: splitsStorage, - splitChangeProcessor: DefaultSplitChangeProcessor(filterBySet: bySetsFilter), + splitChangeProcessor: changeProcessor, cacheExpiration: cacheExpiration, splitConfig: SplitClientConfig()) @@ -204,7 +213,7 @@ struct BackgroundSyncExecutor { operationQueue.addOperation { for (userKey, timestamp) in self.userKeys { if self.isExpired(timestamp: timestamp) { - SplitBgSynchronizer.shared.unregister(apiKey: self.apiKey, userKey: userKey) + SplitBgSynchronizer.shared.unregister(dbKey: self.mapKey, userKey: userKey) return } @@ -227,5 +236,9 @@ struct BackgroundSyncExecutor { func isExpired(timestamp: Int64) -> Bool { return Date().unixTimestamp() - timestamp > SplitBgSynchronizer.kRegistrationExpiration } + + private func buildMapKey(prefix: String?, apiKey: String) -> String { + return "\(prefix ?? "")_\(apiKey)" + } } #endif diff --git a/Split/Models/Key.swift b/Split/Models/Key.swift index 11d8b01a..ca00a80c 100644 --- a/Split/Models/Key.swift +++ b/Split/Models/Key.swift @@ -10,8 +10,8 @@ import Foundation public class Key: NSObject { - let matchingKey: String - let bucketingKey: String? + public let matchingKey: String + public let bucketingKey: String? @objc(initWithMatchingKey:bucketingKey:) public init(matchingKey: String, bucketingKey: String? = nil) { self.matchingKey = matchingKey diff --git a/SplitTests/Fake/KeyValueStorageMock.swift b/SplitTests/Fake/KeyValueStorageMock.swift new file mode 100644 index 00000000..d435d02d --- /dev/null +++ b/SplitTests/Fake/KeyValueStorageMock.swift @@ -0,0 +1,42 @@ +// +// KeyValueStorageMock.swift +// SplitTests +// +// Created by Javier Avrudsky on 24/11/2023. +// Copyright © 2023 Split. All rights reserved. +// + +import Foundation +@testable import Split + +class KeyValueStorageMock: KeyValueStorage { + private var storage: [String: Any] = [:] + + func set(item: T, for key: SecureItem) { + storage[key.toString()] = item + } + + func getInt(item: SecureItem) -> Int? { + return storage[item.toString()] as? Int + } + + func get(item: SecureItem, type: T.Type) -> T? { + return storage[item.toString()] as? T + } + + func getString(item: SecureItem) -> String? { + return storage[item.toString()] as? String + } + + func remove(item: SecureItem) { + storage.removeValue(forKey: item.toString()) + } + + func set(item: String, for key: SecureItem) { + storage[key.toString()] = item + } + + func set(item: Int, for key: SecureItem) { + storage[key.toString()] = item + } +} diff --git a/SplitTests/Helpers/TestSplitFactory.swift b/SplitTests/Helpers/TestSplitFactory.swift index d176b0b2..922c3846 100644 --- a/SplitTests/Helpers/TestSplitFactory.swift +++ b/SplitTests/Helpers/TestSplitFactory.swift @@ -221,11 +221,12 @@ class TestSplitFactory: SplitFactory { } private func setupBgSync(config: SplitClientConfig, apiKey: String, userKey: String) { + let dbKey = SplitDatabaseHelper.buildDbKey(prefix: config.prefix, sdkKey: apiKey) #if os(iOS) || os(tvOS) if config.synchronizeInBackground { - SplitBgSynchronizer.shared.register(apiKey: apiKey, userKey: userKey) + SplitBgSynchronizer.shared.register(dbKey: dbKey, prefix: config.prefix, userKey: userKey) } else { - SplitBgSynchronizer.shared.unregister(apiKey: apiKey, userKey: userKey) + SplitBgSynchronizer.shared.unregister(dbKey: dbKey, userKey: userKey) } #endif } diff --git a/SplitTests/Storage/SplitBgSynchronizerTests.swift b/SplitTests/Storage/SplitBgSynchronizerTests.swift new file mode 100644 index 00000000..8698fd35 --- /dev/null +++ b/SplitTests/Storage/SplitBgSynchronizerTests.swift @@ -0,0 +1,89 @@ +// +// SplitBgSynchronizerTests.swift +// SplitTests +// +// Created by Javier Avrudsky on 24/11/2023. +// Copyright © 2023 Split. All rights reserved. +// + +import XCTest +@testable import Split + +private typealias SyncItem = SplitBgSynchronizer.SyncItem +private typealias BgSyncSchedule = SplitBgSynchronizer.BgSyncSchedule + +class SplitBgSynchronizerTest: XCTestCase { + let bgSync = SplitBgSynchronizer.shared + let storage = KeyValueStorageMock() + + override func setUp() { + bgSync.globalStorage = storage + } + + func testRegister() { + register() + + let data = getSyncTaskMap() + + let d1 = data["dbKey1"] + let d2 = data["dbKey2"] + + XCTAssertEqual(d1?.apiKey, "dbKey1") + XCTAssertNil(d1?.prefix) + XCTAssertEqual(d1?.encryptionLevel, SplitEncryptionLevel.none.rawValue) + XCTAssertEqual(d1?.userKeys.keys.sorted(), ["key1", "key2"]) + + XCTAssertEqual(d2?.apiKey, "dbKey2") + XCTAssertEqual(d2?.prefix, "pref") + XCTAssertEqual(d2?.encryptionLevel, SplitEncryptionLevel.aes128Cbc.rawValue) + XCTAssertEqual(d2?.userKeys.keys.sorted(), ["key2"]) + } + + func testRegisterRemove() { + register() + bgSync.unregister(dbKey: "dbKey1", userKey: "key1") + bgSync.unregister(dbKey: "dbKey2", userKey: "key2") + let data = getSyncTaskMap() + + let d1 = data["dbKey1"] + let d2 = data["dbKey2"] + + XCTAssertEqual(d1?.apiKey, "dbKey1") + XCTAssertNil(d1?.prefix) + XCTAssertEqual(d1?.encryptionLevel, SplitEncryptionLevel.none.rawValue) + XCTAssertEqual(d1?.userKeys.keys.sorted(), ["key2"]) + + XCTAssertNil(d2) + + } + + func testRemoveAll() { + register() + bgSync.unregisterAll() + + let data = getSyncTaskMap() + + XCTAssertEqual(data.count, 0) + + } + + private func register() { + bgSync.register(dbKey: "dbKey1", + prefix: nil, + userKey: "key1", + encryptionLevel: SplitEncryptionLevel.none) + + bgSync.register(dbKey: "dbKey1", + prefix: nil, + userKey: "key2", + encryptionLevel: SplitEncryptionLevel.none) + + bgSync.register(dbKey: "dbKey2", + prefix: "pref", + userKey: "key2", + encryptionLevel: SplitEncryptionLevel.aes128Cbc) + } + private func getSyncTaskMap() -> [String: SplitBgSynchronizer.SyncItem] { + return storage.get(item: .backgroundSyncSchedule, type: BgSyncSchedule.self) ?? [String: SyncItem]() + } +} diff --git a/SplitiOSStreaming_1.xctestplan b/SplitiOSStreaming_1.xctestplan index 0d1a0336..759afa15 100644 --- a/SplitiOSStreaming_1.xctestplan +++ b/SplitiOSStreaming_1.xctestplan @@ -159,6 +159,7 @@ "ReconnectBackoffCounterTest", "RecorderFlusherCheckerTests", "RegexTest", + "SdkUpdateStreamingTest", "SingleSyncTest", "SplitChangeProcessorTests", "SplitChangesServerErrorTest", @@ -187,6 +188,7 @@ "SseClientTest", "SseHandlerTest", "SseNotificationProcessorTest", + "StreamingControlTest", "StreamingDelaytTest", "StreamingDisabledTest", "StreamingInitTest", diff --git a/SplitiOSStreaming_2.xctestplan b/SplitiOSStreaming_2.xctestplan new file mode 100644 index 00000000..223d21fa --- /dev/null +++ b/SplitiOSStreaming_2.xctestplan @@ -0,0 +1,242 @@ +{ + "configurations" : [ + { + "id" : "58DE4B58-E2CE-4405-9907-6EEAB7349405", + "name" : "Common", + "options" : { + + } + }, + { + "id" : "EDD654B9-59DA-4614-A00A-565A27E9CBB5", + "name" : "WatchOS", + "options" : { + "commandLineArgumentEntries" : [ + { + "argument" : " com.apple.CoreData.ConcurrencyDebug" + } + ], + "targetForVariableExpansion" : { + "containerPath" : "container:Split.xcodeproj", + "identifier" : "95B02CA328D0BD790030EC8B", + "name" : "SplitWatchOS" + } + } + } + ], + "defaultOptions" : { + "testTimeoutsEnabled" : true + }, + "testTargets" : [ + { + "skippedTests" : [ + "AnyValueValidatorTests", + "ApiKeyValidatorTests", + "ArrayBlockingQueueTests", + "AttributesDaoTest", + "AttributesDaoTests", + "AttributesEvaluationTest", + "AttributesStorageTests", + "BCryptTests", + "Base64UtilsTest", + "BlockingQueueTest", + "BucketSplitTest", + "ByKeyAttributesStorageTests", + "ByKeyFacadeTest", + "ByKeyMySegmentsStorageTests", + "CdnByPassTest", + "CipherTest", + "ComputeProcessTest", + "ConcurrentSetTests", + "ConfigObjcTest", + "ConfigTest", + "CountsRecorderCountWorkerTests", + "DatesTest", + "DbCipherTest", + "DbForDifferentApiKeysTest", + "DecompressionTest", + "DestroyTests", + "EndpointFactoryTest", + "EndpointTest", + "EvaluatorTests", + "EventDTOJsonTest", + "EventDaoTest", + "EventDaoTests", + "EventStreamParserTest", + "EventValidatorTests", + "EventsRecorderWorkerTests", + "EventsStorageTest", + "EventsSynchronizerTest", + "EventsTrackerTest", + "FactoryMonitorTest", + "FactoryRegistryTest", + "FeatureFlagsPayloadDecoderTest", + "FeatureFlagsSynchronizerTest", + "FetchSpecificSplitsTest", + "FilterBuilderTest", + "FlagSetValidatorTests", + "FlagSetsCacheTests", + "FlagSetsIntegrationTests", + "FlushTests", + "FolderApiKeyTests", + "GeneralInfoDaoTest", + "HttpClientTest", + "HttpDataRequestTest", + "HttpEventsRecorderTests", + "HttpImpressionsCountRecorderTests", + "HttpImpressionsRecorderTests", + "HttpMySegmentsFetcherTest", + "HttpMySegmentsFetcherTests", + "HttpRequestListTest", + "HttpResponseTest", + "HttpSplitFetcherTests", + "HttpStreamRequestTest", + "HttpTelemetryConfigRecorderTest", + "HttpTelemetryStatsRecorderTest", + "HttpUniqueKeyRecorderTests", + "ImpressionDaoTest", + "ImpressionDaoTests", + "ImpressionHasherTest", + "ImpressionsCountDaoTest", + "ImpressionsCountDaoTests", + "ImpressionsCounterTest", + "ImpressionsDedupTest", + "ImpressionsModeTypeWrapperTest", + "ImpressionsNoneTest", + "ImpressionsObserverTest", + "ImpressionsRecorderWorkerTests", + "ImpressionsStorageTest", + "ImpressionsTrackerTest", + "InMemoryTelemetryStorageTest", + "InitDbCipherTest", + "InitialCacheTest", + "InstantFeatureFlagsUpdateTest", + "JwtTokenParserTest", + "KeyGeneratorTest", + "KeyValidatorTests", + "LRUCacheTest", + "LatencyCounterTests", + "LegacyHashingTest", + "LocalhostManagerTests", + "LocalhostParserTests", + "LocalhostSplitClientTests", + "LocalhostSplitFetcherTests", + "LocalhostTests", + "LocalhostYamlParserTest", + "LoggerTest", + "MatcherEvalTests", + "MatcherTests", + "MultiClientEvaluationTest", + "MultiClientStreamingResetTest", + "Murmur3HashingTest", + "MySegmentServerErrorTest", + "MySegmentUpdateV2Test", + "MySegmentUpdatedTest", + "MySegmentsBgSyncWorkerTest", + "MySegmentsChangesCheckerTest", + "MySegmentsDaoTest", + "MySegmentsDaoTests", + "MySegmentsPayloadDecoderTest", + "MySegmentsStorageTests", + "MySegmentsSyncWorkerTest", + "MySegmentsSynchronizerTest", + "MySegmentsV2PayloaDecoderTest", + "NotificationManagerKeeperTest", + "NotificationParserTest", + "PeriodicMySegmentsSyncWorkerTest", + "PeriodicRecorderWorkerTests", + "PeriodicSplitsSyncWorkerTest", + "PersistentAttributesStorageTests", + "PersistentEventsStorageTests", + "PersistentImpressionsCountStorageTests", + "PersistentImpressionsStorageTests", + "PersistentMySegmentsStorageTests", + "PersistentSplitsStorageTest", + "PersistentUniqueKeysStorageTests", + "PushManagerEventBroadcasterTest", + "PushNotificationManagerTest", + "ReadyFromCacheTest", + "ReconnectBackoffCounterTest", + "RecorderFlusherCheckerTests", + "RegexTest", + "SingleSyncTest", + "SplitBgSynchronizerTest", + "SplitChangeProcessorTests", + "SplitChangesServerErrorTest", + "SplitChangesTest", + "SplitClientManagerTest", + "SplitConfigurationsParsingTest", + "SplitDaoTest", + "SplitDaoTests", + "SplitEventsCoordinatorTest", + "SplitEventsManagerTest", + "SplitFactoryBuilderTests", + "SplitIntegrationTests", + "SplitManagerTest", + "SplitSdkTimeoutTests", + "SplitSdkUpdatePollingTest", + "SplitValidatorTests", + "SplitsBgSyncWorkerTest", + "SplitsChangesCheckerTest", + "SplitsDecoderTest", + "SplitsEncoderTest", + "SplitsStorageTest", + "SplitsStorageTrafficTypesTests", + "SplitsSyncWorkerTest", + "SplitsUpdateWorkerTest", + "SseAuthenticatorTest", + "SseClientTest", + "SseHandlerTest", + "SseNotificationProcessorTest", + "StreamingAuthFail4xxTest", + "StreamingAuthFail5xxTest", + "StreamingBgReconnectTest", + "StreamingConnFail5xxTest", + "StreamingControlResetTest", + "StreamingDelaytTest", + "StreamingDisabledTest", + "StreamingInitTest", + "StreamingMySegmentsSyncTest", + "StreamingNoReconectWhenPollingTest", + "StreamingOccupancyTest", + "StreamingSplitKillTest", + "StreamingSplitsSyncTest", + "SyncConfigTest", + "SyncDictionaryCollectionWrapperTest", + "SyncDictionarySingleWrapperTest", + "SyncGuardianTest", + "SyncManagerTest", + "SyncPostBgTest", + "SyncUpdateWorker", + "SyncUpdateWorkerTest", + "SynchronizerTest", + "TelemetryConfigRecorderWorkerTests", + "TelemetryIntegrationTest", + "TelemetryStatsRecorderWorkerTests", + "TelemetryTest", + "TelmetrySynchronizerTest", + "TimersManagerTest", + "TrackTest", + "TreatmentManagerTest", + "UniqueKeyDaoTest", + "UniqueKeyDaoTests", + "UniqueKeyTrackerTest", + "UniqueKeysRecorderCountWorkerTests", + "UserConsentManagerTest", + "UserConsentModeDebugTest", + "UserConsentModeNoneTest", + "UserConsentModeOptimizedTest", + "UserConsentTypeWrapperTest", + "UserKeyEncondingTest", + "VersionTest", + "countsRecorderCountWorkerTests" + ], + "target" : { + "containerPath" : "container:Split.xcodeproj", + "identifier" : "592C6AA4211B6C99002D120C", + "name" : "SplitTests" + } + } + ], + "version" : 1 +}