Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Issues with context/secret trigger error #137

Merged
merged 10 commits into from
Jun 17, 2024
28 changes: 25 additions & 3 deletions Sources/Confidence/Confidence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,33 @@ public class Confidence: ConfidenceEventSender {
try await self.fetchAndActivate()
self.contextReconciliatedChanges.send(context.hash())
} catch {
// TODO: Log errors for debugging
}
}
}
.store(in: &cancellables)
}

/**
Activating the cache means that the flag data on disk is loaded into memory, so consumers can access flag values.
Errors can be thrown if something goes wrong access data on disk.
*/
public func activate() throws {
let savedFlags = try storage.load(defaultValue: FlagResolution.EMPTY)
self.cache = savedFlags
}

/**
Fetches latest flag evaluations and store them on disk. Regardless of the fetch outcome (success or failure), this
function activates the cache after the fetch.
Activating the cache means that the flag data on disk is loaded into memory, so consumers can access flag values.
Fetching is best-effort, so no error is propagated. Errors can still be thrown if something goes wrong access data on disk.
*/
public func fetchAndActivate() async throws {
try await internalFetch()
do {
try await internalFetch()
} catch {
// TODO: Log errors for debugging
}
try activate()
}

Expand All @@ -80,9 +94,17 @@ public class Confidence: ConfidenceEventSender {
try storage.save(data: resolution)
}

/**
Fetches latest flag evaluations and store them on disk. Note that "activate" must be called for this data to be
made available in the app session.
*/
public func asyncFetch() {
Task {
try await internalFetch()
do {
try await internalFetch()
} catch {
// TODO: Log errors for debugging
}
}
}

Expand Down
14 changes: 7 additions & 7 deletions Sources/ConfidenceProvider/ConfidenceFeatureProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ public class ConfidenceFeatureProvider: FeatureProvider {
}

public func initialize(initialContext: OpenFeature.EvaluationContext?) {
guard let initialContext = initialContext else {
return
}

self.updateConfidenceContext(context: initialContext)
self.updateConfidenceContext(context: initialContext ?? MutableContext(attributes: [:]))
if self.initializationStrategy == .activateAndFetchAsync {
eventHandler.send(.ready)
}
Expand All @@ -55,8 +51,12 @@ public class ConfidenceFeatureProvider: FeatureProvider {
confidence.asyncFetch()
} else {
Task {
try await confidence.fetchAndActivate()
eventHandler.send(.ready)
do {
try await confidence.fetchAndActivate()
eventHandler.send(.ready)
} catch {
eventHandler.send(.error)
}
}
}
} catch {
Expand Down
77 changes: 77 additions & 0 deletions Tests/ConfidenceProviderTests/ConfidenceProviderTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Foundation
import ConfidenceProvider
import Combine
import OpenFeature
import XCTest

@testable import Confidence

class ConfidenceProviderTest: XCTestCase {
private var readyExpectation = XCTestExpectation(description: "Ready")
private var errorExpectation = XCTestExpectation(description: "Error")

func testErrorFetchOnInit() async throws {
class FakeClient: ConfidenceResolveClient {
func resolve(ctx: ConfidenceStruct) async throws -> ResolvesResult {
throw ConfidenceError.internalError(message: "test")
}
}

let client = FakeClient()
let confidence = Confidence.Builder(clientSecret: "test")
.withContext(initialContext: ["targeting_key": .init(string: "user1")])
.withFlagResolverClient(flagResolver: client)
.build()

let provider = ConfidenceFeatureProvider(confidence: confidence, initializationStrategy: .activateAndFetchAsync)
OpenFeatureAPI.shared.setProvider(provider: provider)

let cancellable = OpenFeatureAPI.shared.observe().sink { event in
if event == .ready {
self.readyExpectation.fulfill()
} else {
print(event)
}
}
await fulfillment(of: [readyExpectation], timeout: 5.0)
cancellable.cancel()
}

func testErrorStorageOnInit() async throws {
class FakeStorage: Storage {
func save(data: Encodable) throws {
// no-op
}

func load<T>(defaultValue: T) throws -> T where T: Decodable {
throw ConfidenceError.internalError(message: "test")
}

func clear() throws {
// no-op
}

func isEmpty() -> Bool {
return false
}
}

let confidence = Confidence.Builder(clientSecret: "test")
.withContext(initialContext: ["targeting_key": .init(string: "user1")])
.withStorage(storage: FakeStorage())
.build()

let provider = ConfidenceFeatureProvider(confidence: confidence, initializationStrategy: .activateAndFetchAsync)
OpenFeatureAPI.shared.setProvider(provider: provider)

let cancellable = OpenFeatureAPI.shared.observe().sink { event in
if event == .error {
self.errorExpectation.fulfill()
} else {
print(event)
}
}
await fulfillment(of: [errorExpectation], timeout: 5.0)
cancellable.cancel()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest
@testable import Confidence

// swiftlint:disable type_body_length
final class ConfidenceTests: XCTestCase {
final class ConfidenceContextTests: XCTestCase {
func testWithContext() {
let client = RemoteConfidenceResolveClient(
options: ConfidenceClientOptions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import XCTest
@testable import Confidence

@available(macOS 13.0, iOS 16.0, *)
class ConfidenceFeatureProviderTest: XCTestCase {
class ConfidenceTest: XCTestCase {
private var flagApplier = FlagApplierMock()
private let storage = StorageMock()
private var readyExpectation = XCTestExpectation(description: "Ready")
Expand Down
Loading