Skip to content

Commit

Permalink
fix: Issues with context/secret trigger error (#137)
Browse files Browse the repository at this point in the history
* fix: Missing context emits an error local-event

* fix: Non-authenticated fetches trigger error

* refactor: no OF context init is allowed

* refactor: Renaming test classes

* test: [OF] Test errors on init

* fix: todo comments

* feat: Add docs for public functions

* fix: Remove unnecessary check

* fix: Rename test class

* fix: Formatting
  • Loading branch information
fabriziodemaria authored Jun 17, 2024
1 parent d295ffa commit 54a674e
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 12 deletions.
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

0 comments on commit 54a674e

Please sign in to comment.