diff --git a/Sources/OpenFeature/OpenFeatureAPI.swift b/Sources/OpenFeature/OpenFeatureAPI.swift index a671157..829af43 100644 --- a/Sources/OpenFeature/OpenFeatureAPI.swift +++ b/Sources/OpenFeature/OpenFeatureAPI.swift @@ -59,12 +59,17 @@ public class OpenFeatureAPI { } public func setEvaluationContext(evaluationContext: EvaluationContext) { - let oldContext = self._context - self._context = evaluationContext + providerStatus = .reconciling + eventHandler.send(.reconciling) do { + let oldContext = self._context + self._context = evaluationContext try getProvider()?.onContextSet(oldContext: oldContext, newContext: evaluationContext) + providerStatus = .ready + eventHandler.send(.contextChanged) } catch { - // TODO Handle errors + providerStatus = .error + eventHandler.send(.error(message: error.localizedDescription)) } } diff --git a/Tests/OpenFeatureTests/DeveloperExperienceTests.swift b/Tests/OpenFeatureTests/DeveloperExperienceTests.swift index 1feb9e7..c4fb15b 100644 --- a/Tests/OpenFeatureTests/DeveloperExperienceTests.swift +++ b/Tests/OpenFeatureTests/DeveloperExperienceTests.swift @@ -43,6 +43,32 @@ final class DeveloperExperienceTests: XCTestCase { XCTAssertNotNil(eventState) } + func testSetEvaluationContext() { + let contextChangedExpectation = XCTestExpectation(description: "Context Changed") + let reconcilingExpectation = XCTestExpectation(description: "Reconciling") + let observer = OpenFeatureAPI.shared.observe().sink { event in + switch event { + case .reconciling: + reconcilingExpectation.fulfill() + case .ready: + break + case .contextChanged: + contextChangedExpectation.fulfill() + default: + XCTFail("Unexpected event") + } + } + let semaphore = DispatchSemaphore(value: 0) + OpenFeatureAPI.shared.setProvider(provider: StaggeredProvider(onContextSetSemaphore: semaphore)) + Task { + OpenFeatureAPI.shared.setEvaluationContext(evaluationContext: MutableContext(attributes: [:])) + } + wait(for: [reconcilingExpectation], timeout: 2) + semaphore.signal() + wait(for: [contextChangedExpectation], timeout: 2) + XCTAssertNotNil(observer) + } + func testSetProviderAndWait() async { let readyExpectation = XCTestExpectation(description: "Ready") let errorExpectation = XCTestExpectation(description: "Error") diff --git a/Tests/OpenFeatureTests/Helpers/DoSomethingProvider.swift b/Tests/OpenFeatureTests/Helpers/DoSomethingProvider.swift index 8ab2361..b2d339c 100644 --- a/Tests/OpenFeatureTests/Helpers/DoSomethingProvider.swift +++ b/Tests/OpenFeatureTests/Helpers/DoSomethingProvider.swift @@ -5,7 +5,6 @@ import OpenFeature class DoSomethingProvider: FeatureProvider { public static let name = "Something" private let eventHandler = EventHandler() - private var holdit: AnyCancellable? func onContextSet(oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext) { } diff --git a/Tests/OpenFeatureTests/Helpers/StaggeredProvider.swift b/Tests/OpenFeatureTests/Helpers/StaggeredProvider.swift new file mode 100644 index 0000000..4a572af --- /dev/null +++ b/Tests/OpenFeatureTests/Helpers/StaggeredProvider.swift @@ -0,0 +1,81 @@ +import Combine +import Foundation +import OpenFeature + +class StaggeredProvider: FeatureProvider { + public static let name = "Something" + private let eventHandler = EventHandler() + private let onContextSetSemaphore: DispatchSemaphore? + + init(onContextSetSemaphore: DispatchSemaphore?) { + self.onContextSetSemaphore = onContextSetSemaphore + } + + func onContextSet(oldContext: OpenFeature.EvaluationContext?, newContext: OpenFeature.EvaluationContext) { + print(">> Waiting") + onContextSetSemaphore?.wait() + print(">> Moving") + } + + func initialize(initialContext: OpenFeature.EvaluationContext?) { + } + + var hooks: [any OpenFeature.Hook] = [] + var metadata: OpenFeature.ProviderMetadata = DoMetadata() + + func getBooleanEvaluation(key: String, defaultValue: Bool, context: EvaluationContext?) throws + -> ProviderEvaluation< + Bool + > + { + return ProviderEvaluation(value: !defaultValue, flagMetadata: DoSomethingProvider.flagMetadataMap) + } + + func getStringEvaluation(key: String, defaultValue: String, context: EvaluationContext?) throws + -> ProviderEvaluation< + String + > + { + return ProviderEvaluation( + value: String(defaultValue.reversed()), flagMetadata: DoSomethingProvider.flagMetadataMap) + } + + func getIntegerEvaluation(key: String, defaultValue: Int64, context: EvaluationContext?) throws + -> ProviderEvaluation< + Int64 + > + { + return ProviderEvaluation(value: defaultValue * 100, flagMetadata: DoSomethingProvider.flagMetadataMap) + } + + func getDoubleEvaluation(key: String, defaultValue: Double, context: EvaluationContext?) throws + -> ProviderEvaluation< + Double + > + { + return ProviderEvaluation(value: defaultValue * 100, flagMetadata: DoSomethingProvider.flagMetadataMap) + } + + func getObjectEvaluation(key: String, defaultValue: Value, context: EvaluationContext?) throws + -> ProviderEvaluation< + Value + > + { + return ProviderEvaluation(value: .null, flagMetadata: DoSomethingProvider.flagMetadataMap) + } + + func observe() -> AnyPublisher { + eventHandler.observe() + } + + public struct DoMetadata: ProviderMetadata { + public var name: String? = DoSomethingProvider.name + } + + public static let flagMetadataMap = [ + "int-metadata": FlagMetadataValue.integer(99), + "double-metadata": FlagMetadataValue.double(98.4), + "string-metadata": FlagMetadataValue.string("hello-world"), + "boolean-metadata": FlagMetadataValue.boolean(true), + ] +}