From 2c8c7f147d90e71cf9a547df2d729c680e58114c Mon Sep 17 00:00:00 2001 From: vahidlazio Date: Thu, 14 Sep 2023 09:08:16 -0400 Subject: [PATCH] feat: Initialization strategy (#55) * add the initialization strategy, seprate the memory and storage * add activate and fetch to the demo app --- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../ConfidenceDemoApp/ConfidenceDemoApp.swift | 1 + .../Cache/InMemoryProviderCache.swift | 67 ++++++++ .../Cache/PersistentProviderCache.swift | 124 --------------- .../Cache/ProviderCache.swift | 2 - .../ConfidenceFeatureProvider.swift | 147 +++++++++++++----- .../InitializationStrategy.swift | 6 + .../ConfidenceProvider/Utils/Extensions.swift | 29 ++++ .../ConfidenceFeatureProviderTest.swift | 35 +++-- .../ConfidenceIntegrationTest.swift | 9 +- .../PersistentProviderCacheTest.swift | 18 ++- 11 files changed, 252 insertions(+), 192 deletions(-) create mode 100644 Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift delete mode 100644 Sources/ConfidenceProvider/Cache/PersistentProviderCache.swift create mode 100644 Sources/ConfidenceProvider/InitializationStrategy.swift diff --git a/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 994546ec..cbd92139 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ConfidenceDemoApp/ConfidenceDemoApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -3,11 +3,11 @@ "pins": [ { "package": "OpenFeature", - "repositoryURL": "git@github.com:spotify/openfeature-swift-sdk.git", + "repositoryURL": "git@github.com:open-feature/swift-sdk.git", "state": { "branch": null, - "revision": "ef8dd46fb9623fc85b0b75788def006f7b59132c", - "version": "0.2.5" + "revision": "114321e376b3d80e6623c5cbd7e8bcef9c6a86d2", + "version": "0.0.2" } } ] diff --git a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift index 47c75442..3b3eb586 100644 --- a/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift +++ b/ConfidenceDemoApp/ConfidenceDemoApp/ConfidenceDemoApp.swift @@ -21,6 +21,7 @@ extension ConfidenceDemoApp { } let provider = ConfidenceFeatureProvider .Builder(credentials: .clientSecret(secret: secret)) + .with(initializationStrategy: .activateAndFetchAsync) .build() let ctx = MutableContext(targetingKey: UUID.init().uuidString, structure: MutableStructure()) OpenFeatureAPI.shared.setProvider(provider: provider, initialContext: ctx) diff --git a/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift b/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift new file mode 100644 index 00000000..8e703b71 --- /dev/null +++ b/Sources/ConfidenceProvider/Cache/InMemoryProviderCache.swift @@ -0,0 +1,67 @@ +import Combine +import Foundation +import OpenFeature +import os + +public class InMemoryProviderCache: ProviderCache { + private var rwCacheQueue = DispatchQueue(label: "com.confidence.cache.rw", attributes: .concurrent) + static let currentVersion = "0.0.1" + private let cache: [String: ResolvedValue] + + private var storage: Storage + private var curResolveToken: String? + private var curEvalContextHash: String? + + init(storage: Storage, cache: [String: ResolvedValue], curResolveToken: String?, curEvalContextHash: String?) { + self.storage = storage + self.cache = cache + self.curResolveToken = curResolveToken + self.curEvalContextHash = curEvalContextHash + } + + public func getValue(flag: String, ctx: EvaluationContext) throws -> CacheGetValueResult? { + if let value = self.cache[flag] { + guard let curResolveToken = curResolveToken else { + throw ConfidenceError.noResolveTokenFromCache + } + return .init( + resolvedValue: value, needsUpdate: curEvalContextHash != ctx.hash(), resolveToken: curResolveToken) + } else { + return nil + } + } + + public static func from(storage: Storage) -> InMemoryProviderCache { + do { + let storedCache = try storage.load( + defaultValue: StoredCacheData( + version: currentVersion, cache: [:], curResolveToken: nil, curEvalContextHash: nil)) + return InMemoryProviderCache( + storage: storage, + cache: storedCache.cache, + curResolveToken: storedCache.curResolveToken, + curEvalContextHash: storedCache.curEvalContextHash) + } catch { + Logger(subsystem: "com.confidence.cache", category: "storage").error( + "Error when trying to load resolver cache, clearing cache: \(error)") + + if case .corruptedCache = error as? ConfidenceError { + try? storage.clear() + } + + return InMemoryProviderCache(storage: storage, cache: [:], curResolveToken: nil, curEvalContextHash: nil) + } + } +} + +public struct ResolvedKey: Hashable, Codable { + var flag: String + var targetingKey: String +} + +struct StoredCacheData: Codable { + var version: String + var cache: [String: ResolvedValue] + var curResolveToken: String? + var curEvalContextHash: String? +} diff --git a/Sources/ConfidenceProvider/Cache/PersistentProviderCache.swift b/Sources/ConfidenceProvider/Cache/PersistentProviderCache.swift deleted file mode 100644 index 303d37bf..00000000 --- a/Sources/ConfidenceProvider/Cache/PersistentProviderCache.swift +++ /dev/null @@ -1,124 +0,0 @@ -import Combine -import Foundation -import OpenFeature -import os - -public class PersistentProviderCache: ProviderCache { - private var rwCacheQueue = DispatchQueue(label: "com.confidence.cache.rw", attributes: .concurrent) - private var persistQueue = DispatchQueue(label: "com.confidence.cache.persist") - private static let currentVersion = "0.0.1" - - private var _cache: [String: ResolvedValue] - private var cache: [String: ResolvedValue] { - get { - return rwCacheQueue.sync { _cache } - } - - set(newCache) { - rwCacheQueue.async(flags: .barrier) { self._cache = newCache } - } - } - - private var storage: Storage - private var curResolveToken: String? - private var curEvalContextHash: String? - private var persistPublisher = PassthroughSubject() - private var cancellable = Set() - - init(storage: Storage, cache: [String: ResolvedValue], curResolveToken: String?, curEvalContextHash: String?) { - self.storage = storage - self._cache = cache - self.curResolveToken = curResolveToken - self.curEvalContextHash = curEvalContextHash - - persistPublisher - .throttle(for: 30.0, scheduler: persistQueue, latest: true) - .sink { [weak self] _ in - guard let self else { return } - do { - try self.persist() - } catch { - Logger(subsystem: "com.confidence.cache", category: "persist") - .error("Unable to persist cache: \(error)") - } - } - .store(in: &cancellable) - } - - public func getValue(flag: String, ctx: EvaluationContext) throws -> CacheGetValueResult? { - if let value = self.cache[flag] { - guard let curResolveToken = curResolveToken else { - throw ConfidenceError.noResolveTokenFromCache - } - return .init( - resolvedValue: value, needsUpdate: curEvalContextHash != ctx.hash(), resolveToken: curResolveToken) - } else { - return nil - } - } - - public func clearAndSetValues(values: [ResolvedValue], ctx: EvaluationContext, resolveToken: String) throws { - self.cache = [:] - self.curResolveToken = resolveToken - self.curEvalContextHash = ctx.hash() - values.forEach { value in - self.cache[value.flag] = value - } - self.persistPublisher.send(.persist) - } - - public func clear() throws { - try self.storage.clear() - self.cache = [:] - self.curResolveToken = nil - } - - public static func from(storage: Storage) -> PersistentProviderCache { - do { - let storedCache = try storage.load( - defaultValue: StoredCacheData( - version: currentVersion, cache: [:], curResolveToken: nil, curEvalContextHash: nil)) - return PersistentProviderCache( - storage: storage, - cache: storedCache.cache, - curResolveToken: storedCache.curResolveToken, - curEvalContextHash: storedCache.curEvalContextHash) - } catch { - Logger(subsystem: "com.confidence.cache", category: "storage").error( - "Error when trying to load resolver cache, clearing cache: \(error)") - - if case .corruptedCache = error as? ConfidenceError { - try? storage.clear() - } - - return PersistentProviderCache(storage: storage, cache: [:], curResolveToken: nil, curEvalContextHash: nil) - } - } -} - -extension PersistentProviderCache { - struct StoredCacheData: Codable { - var version: String - var cache: [String: ResolvedValue] - var curResolveToken: String? - var curEvalContextHash: String? - } - - enum CacheEvent { - case persist - } - - func persist() throws { - try self.storage.save( - data: StoredCacheData( - version: PersistentProviderCache.currentVersion, - cache: self.cache, - curResolveToken: self.curResolveToken, - curEvalContextHash: self.curEvalContextHash)) - } - - public struct ResolvedKey: Hashable, Codable { - var flag: String - var targetingKey: String - } -} diff --git a/Sources/ConfidenceProvider/Cache/ProviderCache.swift b/Sources/ConfidenceProvider/Cache/ProviderCache.swift index d2014891..da623458 100644 --- a/Sources/ConfidenceProvider/Cache/ProviderCache.swift +++ b/Sources/ConfidenceProvider/Cache/ProviderCache.swift @@ -3,8 +3,6 @@ import OpenFeature public protocol ProviderCache { func getValue(flag: String, ctx: EvaluationContext) throws -> CacheGetValueResult? - - func clearAndSetValues(values: [ResolvedValue], ctx: EvaluationContext, resolveToken: String) throws } public struct CacheGetValueResult { diff --git a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift index dd1df564..74bb31e1 100644 --- a/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift +++ b/Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift @@ -12,26 +12,32 @@ public class ConfidenceFeatureProvider: FeatureProvider { public var hooks: [any Hook] = [] public let metadata: ProviderMetadata = ConfidenceMetadata() private let lock = UnfairLock() - private let resolver: Resolver + private var resolver: Resolver private let client: ConfidenceClient - private let cache: ProviderCache + private var cache: ProviderCache private var overrides: [String: LocalOverride] private let flagApplier: FlagApplier + private let initializationStrategy: InitializationStrategy + private let storage: Storage /// Should not be called externally, use `ConfidenceFeatureProvider.Builder` instead. init( - resolver: Resolver, client: RemoteConfidenceClient, cache: ProviderCache, + storage: Storage, overrides: [String: LocalOverride] = [:], flagApplier: FlagApplier, - applyStorage: Storage + applyStorage: Storage, + initializationStrategy: InitializationStrategy ) { - self.resolver = resolver self.client = client self.cache = cache self.overrides = overrides self.flagApplier = flagApplier + self.initializationStrategy = initializationStrategy + self.storage = storage + + resolver = LocalStorageResolver(cache: cache) } public func initialize(initialContext: OpenFeature.EvaluationContext?) { @@ -39,18 +45,50 @@ public class ConfidenceFeatureProvider: FeatureProvider { return } + // signal the provider is ready right away + if self.initializationStrategy == .activateAndFetchAsync { + OpenFeatureAPI.shared.emitEvent(.ready, provider: self) + } + Task { do { - try await processNewContext(context: initialContext) - OpenFeatureAPI.shared.emitEvent(.ready, provider: self) + let resolveResult = try await resolve(context: initialContext) + + // update cache with stored values + try await store( + with: initialContext, + resolveResult: resolveResult, + refreshCache: self.initializationStrategy == .fetchAndActivate + ) + + // signal the provider is ready after the network request is done + if self.initializationStrategy == .fetchAndActivate { + OpenFeatureAPI.shared.emitEvent(.ready, provider: self) + } } catch { // We emit a ready event as the provider is ready, but is using default / cache values. - // TODO: Confirm this is the correct way to handle this. OpenFeatureAPI.shared.emitEvent(.ready, provider: self) } } } + private func store( + with context: OpenFeature.EvaluationContext, + resolveResult result: ResolvesResult, + refreshCache: Bool + ) async throws { + guard let resolveToken = result.resolveToken else { + throw ConfidenceError.noResolveTokenFromServer + } + + try self.storage.save(data: result.resolvedValues.toCacheData(context: context, resolveToken: resolveToken)) + + if refreshCache { + self.cache = InMemoryProviderCache.from(storage: self.storage) + resolver = LocalStorageResolver(cache: cache) + } + } + public func onContextSet( oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext @@ -61,12 +99,14 @@ public class ConfidenceFeatureProvider: FeatureProvider { Task { do { - try await processNewContext(context: newContext) + let resolveResult = try await resolve(context: newContext) + + // update the storage + try await store(with: newContext, resolveResult: resolveResult, refreshCache: true) OpenFeatureAPI.shared.emitEvent(.ready, provider: self) } catch { - // We emit a ready event as the provider is ready, but is using default / cache values. - // TODO: Confirm this is the correct way to handle this. OpenFeatureAPI.shared.emitEvent(.ready, provider: self) + // do nothing } } } @@ -135,15 +175,12 @@ public class ConfidenceFeatureProvider: FeatureProvider { } } - private func processNewContext(context: OpenFeature.EvaluationContext) async throws { + private func resolve(context: OpenFeature.EvaluationContext) async throws -> ResolvesResult { // Racy: eval ctx and ctx in cache might differ until the latter is updated, resulting in STALE evaluations + OpenFeatureAPI.shared.emitEvent(.stale, provider: self) do { let resolveResult = try await client.resolve(ctx: context) - guard let resolveToken = resolveResult.resolveToken else { - throw ConfidenceError.noResolveTokenFromServer - } - try cache.clearAndSetValues( - values: resolveResult.resolvedValues, ctx: context, resolveToken: resolveToken) + return resolveResult } catch { Logger(subsystem: "com.confidence.provider", category: "initialize").error( "Error while executing \"initialize\": \(error)") @@ -346,9 +383,10 @@ extension ConfidenceFeatureProvider { var options: ConfidenceClientOptions var session: URLSession? var localOverrides: [String: LocalOverride] = [:] - var cache: ProviderCache = PersistentProviderCache.from( - storage: DefaultStorage(filePath: "resolver.flags.cache")) + var storage: Storage = DefaultStorage(filePath: "resolver.flags.cache") + var cache: ProviderCache? var flagApplier: (any FlagApplier)? + var initializationStrategy: InitializationStrategy = .fetchAndActivate var applyStorage: Storage = DefaultStorage(filePath: "resolver.apply.cache") /// Initializes the builder with the given credentails. @@ -365,14 +403,18 @@ extension ConfidenceFeatureProvider { session: URLSession? = nil, localOverrides: [String: LocalOverride] = [:], flagApplier: FlagApplier?, - cache: ProviderCache, + storage: Storage, + cache: ProviderCache?, + initializationStrategy: InitializationStrategy, applyStorage: Storage ) { self.options = options self.session = session self.localOverrides = localOverrides self.flagApplier = flagApplier + self.storage = storage self.cache = cache + self.initializationStrategy = initializationStrategy self.applyStorage = applyStorage } @@ -387,7 +429,9 @@ extension ConfidenceFeatureProvider { session: session, localOverrides: localOverrides, flagApplier: flagApplier, + storage: storage, cache: cache, + initializationStrategy: initializationStrategy, applyStorage: applyStorage ) } @@ -402,7 +446,26 @@ extension ConfidenceFeatureProvider { session: session, localOverrides: localOverrides, flagApplier: flagApplier, + storage: storage, cache: cache, + initializationStrategy: initializationStrategy, + applyStorage: applyStorage + ) + } + + /// Inject custom storage, useful for testing + /// + /// - Parameters: + /// - cache: cache for the provider to use. + public func with(storage: Storage) -> Builder { + return Builder( + options: options, + session: session, + localOverrides: localOverrides, + flagApplier: flagApplier, + storage: storage, + cache: cache, + initializationStrategy: initializationStrategy, applyStorage: applyStorage ) } @@ -417,7 +480,9 @@ extension ConfidenceFeatureProvider { session: session, localOverrides: localOverrides, flagApplier: flagApplier, + storage: storage, cache: cache, + initializationStrategy: initializationStrategy, applyStorage: applyStorage ) } @@ -432,7 +497,26 @@ extension ConfidenceFeatureProvider { session: session, localOverrides: localOverrides, flagApplier: flagApplier, + storage: storage, cache: cache, + initializationStrategy: initializationStrategy, + applyStorage: applyStorage + ) + } + + /// Inject custom initialization strategy + /// + /// - Parameters: + /// - storage: apply storage for the provider to use. + public func with(initializationStrategy: InitializationStrategy) -> Builder { + return Builder( + options: options, + session: session, + localOverrides: localOverrides, + flagApplier: flagApplier, + storage: storage, + cache: cache, + initializationStrategy: initializationStrategy, applyStorage: applyStorage ) } @@ -464,7 +548,9 @@ extension ConfidenceFeatureProvider { session: session, localOverrides: self.localOverrides.merging(localOverrides) { _, new in new }, flagApplier: flagApplier, + storage: storage, cache: cache, + initializationStrategy: initializationStrategy, applyStorage: applyStorage ) } @@ -479,35 +565,26 @@ extension ConfidenceFeatureProvider { options: options ) + let cache = cache ?? InMemoryProviderCache.from(storage: storage) + let client = RemoteConfidenceClient( options: options, session: self.session, applyOnResolve: false, flagApplier: flagApplier ) - let resolver = LocalStorageResolver(cache: cache) + return ConfidenceFeatureProvider( - resolver: resolver, client: client, cache: cache, + storage: storage, overrides: localOverrides, flagApplier: flagApplier, - applyStorage: applyStorage + applyStorage: applyStorage, + initializationStrategy: initializationStrategy ) } } } - -/// Used for testing -public protocol DispatchQueueType { - func async(execute work: @escaping @convention(block) () -> Void) -} - -extension DispatchQueue: DispatchQueueType { - public func async(execute work: @escaping @convention(block) () -> Void) { - async(group: nil, qos: .unspecified, flags: [], execute: work) - } -} - // swiftlint:enable type_body_length // swiftlint:enable file_length diff --git a/Sources/ConfidenceProvider/InitializationStrategy.swift b/Sources/ConfidenceProvider/InitializationStrategy.swift new file mode 100644 index 00000000..d3220e77 --- /dev/null +++ b/Sources/ConfidenceProvider/InitializationStrategy.swift @@ -0,0 +1,6 @@ +import Foundation + +public enum InitializationStrategy { + case fetchAndActivate, activateAndFetchAsync +} + diff --git a/Sources/ConfidenceProvider/Utils/Extensions.swift b/Sources/ConfidenceProvider/Utils/Extensions.swift index 0db02130..47b35cad 100644 --- a/Sources/ConfidenceProvider/Utils/Extensions.swift +++ b/Sources/ConfidenceProvider/Utils/Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import OpenFeature /// Used to default an enum to the last value if none matches, this should respresent unknown protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable @@ -11,3 +12,31 @@ extension CaseIterableDefaultsLast { self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.allCases.last! } } + +extension [ResolvedValue] { + func toCacheData(context: EvaluationContext, resolveToken: String) -> StoredCacheData { + var cacheValues: [String: ResolvedValue] = [:] + + forEach { value in + cacheValues[value.flag] = value + } + + return StoredCacheData( + version: InMemoryProviderCache.currentVersion, + cache: cacheValues, + curResolveToken: resolveToken, + curEvalContextHash: context.hash() + ) + } +} + +/// Used for testing +public protocol DispatchQueueType { + func async(execute work: @escaping @convention(block) () -> Void) +} + +extension DispatchQueue: DispatchQueueType { + public func async(execute work: @escaping @convention(block) () -> Void) { + async(group: nil, qos: .unspecified, flags: [], execute: work) + } +} diff --git a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift index 015d9bc2..894fbbdf 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceFeatureProviderTest.swift @@ -12,11 +12,11 @@ class ConfidenceFeatureProviderTest: XCTestCase { private let builder = ConfidenceFeatureProvider .Builder(credentials: .clientSecret(secret: "test")) - private let cache = PersistentProviderCache.from( - storage: StorageMock()) + private let storage = StorageMock() override func setUp() { - try? cache.clear() + try? storage.clear() + MockedConfidenceClientURLProtocol.reset() flagApplier = FlagApplierMock() @@ -32,6 +32,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) @@ -91,6 +92,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) @@ -125,7 +127,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) @@ -160,7 +162,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .with(flagApplier: flagApplier) .build() @@ -197,7 +199,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) @@ -237,7 +239,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) @@ -273,20 +275,21 @@ class ConfidenceFeatureProviderTest: XCTestCase { ] let session = MockedConfidenceClientURLProtocol.mockedSession(flags: flags) + // Simulating a cache with an old evaluation context + + let data = [ResolvedValue(flag: "flag", resolveReason: .match)] + .toCacheData(context: MutableContext(targetingKey: "user0"), resolveToken: "token0") + + let storage = try StorageMock(data: data) + let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .build() - provider.initialize(initialContext: MutableContext(targetingKey: "user1")) + provider.initialize(initialContext: MutableContext(targetingKey: "user0")) wait(for: [readyExpectation], timeout: 5) - // Simulating a cache with an old evaluation context - try cache.clearAndSetValues( - values: [ResolvedValue(flag: "flag", resolveReason: .match)], - ctx: MutableContext(targetingKey: "user0"), - resolveToken: "token0") - let evaluation = try provider.getIntegerEvaluation( key: "flag.size", defaultValue: 0, @@ -448,7 +451,7 @@ class ConfidenceFeatureProviderTest: XCTestCase { let provider = builder .with(session: session) - .with(cache: cache) + .with(storage: storage) .with(flagApplier: flagApplier) .build() provider.initialize(initialContext: MutableContext(targetingKey: "user1")) diff --git a/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift b/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift index 066b6794..df62254b 100644 --- a/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift +++ b/Tests/ConfidenceProviderTests/ConfidenceIntegrationTest.swift @@ -7,7 +7,7 @@ import XCTest class ConfidenceIntegrationTests: XCTestCase { let clientToken: String? = ProcessInfo.processInfo.environment["CLIENT_TOKEN"] let resolveFlag = setResolveFlag() - let cache = PersistentProviderCache.from(storage: StorageMock()) + let storage: Storage = StorageMock() private static func setResolveFlag() -> String { if let flag = ProcessInfo.processInfo.environment["TEST_FLAG_NAME"], !flag.isEmpty { @@ -17,7 +17,6 @@ class ConfidenceIntegrationTests: XCTestCase { } override func setUp() async throws { - try cache.clear() OpenFeatureAPI.shared.clearProvider() OpenFeatureAPI.shared.setEvaluationContext(evaluationContext: MutableContext()) @@ -73,7 +72,7 @@ class ConfidenceIntegrationTests: XCTestCase { credentials: .clientSecret(secret: clientToken) ) .with(flagApplier: flagApplier) - .with(cache: cache) + .with(storage: storage) .build() OpenFeatureAPI.shared.setProvider(provider: confidenceFeatureProvider) @@ -111,7 +110,7 @@ class ConfidenceIntegrationTests: XCTestCase { credentials: .clientSecret(secret: clientToken) ) .with(flagApplier: flagApplier) - .with(cache: cache) + .with(storage: storage) .build() OpenFeatureAPI.shared.setProvider(provider: confidenceFeatureProvider) @@ -156,7 +155,7 @@ class ConfidenceIntegrationTests: XCTestCase { credentials: .clientSecret(secret: clientToken) ) .with(flagApplier: flagApplier) - .with(cache: cache) + .with(storage: storage) .build() OpenFeatureAPI.shared.setProvider(provider: confidenceFeatureProvider) diff --git a/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift b/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift index 20e4aeca..bd74baef 100644 --- a/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift +++ b/Tests/ConfidenceProviderTests/PersistentProviderCacheTest.swift @@ -5,11 +5,11 @@ import XCTest @testable import ConfidenceProvider class PersistentProviderCacheTest: XCTestCase { - lazy var cache = PersistentProviderCache.from(storage: storage) + lazy var cache = InMemoryProviderCache.from(storage: storage) let storage = DefaultStorage(filePath: "resolver.flags.cache") override func setUp() { - try? cache.clear() + try? storage.clear() super.setUp() } @@ -22,7 +22,9 @@ class PersistentProviderCacheTest: XCTestCase { value: Value.double(3.14), flag: flag, resolveReason: .match) - try cache.clearAndSetValues(values: [value], ctx: ctx, resolveToken: resolveToken) + + try storage.save(data: [value].toCacheData(context: ctx, resolveToken: resolveToken)) + cache = InMemoryProviderCache.from(storage: storage) let cachedValue = try cache.getValue(flag: flag, ctx: ctx) XCTAssertEqual(cachedValue?.resolvedValue, value) @@ -46,12 +48,13 @@ class PersistentProviderCacheTest: XCTestCase { resolveReason: .match) XCTAssertFalse(try FileManager.default.fileExists(atPath: storage.getConfigUrl().backport.path)) - try cache.clearAndSetValues(values: [value1, value2], ctx: ctx, resolveToken: resolveToken) + try storage.save(data: [value1, value2].toCacheData(context: ctx, resolveToken: resolveToken)) + cache = InMemoryProviderCache.from(storage: storage) expectToEventually( (try? FileManager.default.fileExists(atPath: storage.getConfigUrl().backport.path)) ?? false) - let newCache = PersistentProviderCache.from( + let newCache = InMemoryProviderCache.from( storage: DefaultStorage(filePath: "resolver.flags.cache")) let cachedValue1 = try newCache.getValue(flag: flag1, ctx: ctx) let cachedValue2 = try newCache.getValue(flag: flag2, ctx: ctx) @@ -66,7 +69,7 @@ class PersistentProviderCacheTest: XCTestCase { func testNoValueFound() throws { let ctx = MutableContext(targetingKey: "key", structure: MutableStructure()) - try cache.clear() + try storage.clear() let cachedValue = try cache.getValue(flag: "flag", ctx: ctx) XCTAssertNil(cachedValue?.resolvedValue.value) @@ -82,7 +85,8 @@ class PersistentProviderCacheTest: XCTestCase { value: Value.double(3.14), flag: flag, resolveReason: .match) - try cache.clearAndSetValues(values: [value], ctx: ctx1, resolveToken: resolveToken) + try storage.save(data: [value].toCacheData(context: ctx1, resolveToken: resolveToken)) + cache = InMemoryProviderCache.from(storage: storage) let cachedValue = try cache.getValue(flag: flag, ctx: ctx2) XCTAssertEqual(cachedValue?.resolvedValue, value)