From 0904cd6205312cab822b0eb8ecc39e64923a534e Mon Sep 17 00:00:00 2001 From: vahidlazio Date: Tue, 12 Sep 2023 07:52:13 -0400 Subject: [PATCH] fix: On context change (#69) * add the eventing onContext change to set the provider values as stale, fetch the values and update the cache * add a test scenario for checking if we get new values after the context update * docs: update comment --------- Co-authored-by: Nicklas Lundin --- .../providers/ConfidenceFeatureProvider.kt | 25 ++++++++-- .../ConfidenceFeatureProviderTests.kt | 50 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Provider/src/main/java/dev/openfeature/contrib/providers/ConfidenceFeatureProvider.kt b/Provider/src/main/java/dev/openfeature/contrib/providers/ConfidenceFeatureProvider.kt index d6d1bc63..78cdf8d4 100644 --- a/Provider/src/main/java/dev/openfeature/contrib/providers/ConfidenceFeatureProvider.kt +++ b/Provider/src/main/java/dev/openfeature/contrib/providers/ConfidenceFeatureProvider.kt @@ -60,12 +60,22 @@ class ConfidenceFeatureProvider private constructor( } override fun initialize(initialContext: EvaluationContext?) { - if (initialContext == null) return + initialContext?.let { + internalInitialize( + initialContext, + initialisationStrategy + ) + } + } + private fun internalInitialize( + initialContext: EvaluationContext, + strategy: InitialisationStrategy + ) { // refresh cache with the last stored data storage.read()?.let(cache::refresh) - if (initialisationStrategy == InitialisationStrategy.ActivateAndFetchAsync) { + if (strategy == InitialisationStrategy.ActivateAndFetchAsync) { eventsPublisher.publish(OpenFeatureEvents.ProviderReady) } @@ -84,7 +94,7 @@ class ConfidenceFeatureProvider private constructor( initialContext ) - when (initialisationStrategy) { + when (strategy) { InitialisationStrategy.FetchAndActivate -> { // refresh the cache from the stored data cache.refresh(data = storedData) @@ -108,7 +118,14 @@ class ConfidenceFeatureProvider private constructor( newContext: EvaluationContext ) { if (newContext != oldContext) { - initialize(newContext) + eventsPublisher.publish(OpenFeatureEvents.ProviderStale) + + // on the new context we want to fetch new values and update + // the storage & cache right away which is why we pass `InitialisationStrategy.FetchAndActivate` + internalInitialize( + newContext, + InitialisationStrategy.FetchAndActivate + ) } } diff --git a/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt b/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt index 50495433..d14ee2c7 100644 --- a/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt +++ b/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt @@ -85,6 +85,9 @@ internal class ConfidenceFeatureProviderTests { private val mockClient: ConfidenceClient = mock() private val mockContext: Context = mock() private val instant = Instant.parse("2023-03-01T14:01:46.645Z") + private val blueStringValues = mutableMapOf( + "mystring" to Value.String("blue") + ) private val resolvedValueAsMap = mutableMapOf( "mystring" to Value.String("red"), "myboolean" to Value.Boolean(false), @@ -313,6 +316,53 @@ internal class ConfidenceFeatureProviderTests { assertNull(evalNull.errorCode) } + @Test + fun testNewContextFetchValuesAgain() = runTest { + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + val confidenceFeatureProvider = ConfidenceFeatureProvider.create( + context = mockContext, + clientSecret = "", + cache = InMemoryCache(), + client = mockClient, + eventsPublisher = EventHandler.eventsPublisher(testDispatcher), + dispatcher = testDispatcher + ) + + val evaluationContext1 = ImmutableContext("foo") + val evaluationContext2 = ImmutableContext("bar") + + whenever(mockClient.resolve(eq(listOf()), eq(evaluationContext1))).thenReturn( + ResolveResponse.Resolved( + ResolveFlags(resolvedFlags, "token1") + ) + ) + + val newExpectedValue = resolvedFlags.list[0].copy(value = ImmutableStructure(blueStringValues)) + whenever(mockClient.resolve(eq(listOf()), eq(evaluationContext2))).thenReturn( + ResolveResponse.Resolved( + ResolveFlags(Flags(listOf(newExpectedValue)), "token1") + ) + ) + + confidenceFeatureProvider.initialize(evaluationContext1) + advanceUntilIdle() + + val evalString1 = confidenceFeatureProvider.getStringEvaluation( + "test-kotlin-flag-1.mystring", + "default", + evaluationContext1 + ) + assertEquals("red", evalString1.value) + confidenceFeatureProvider.onContextSet(evaluationContext1, evaluationContext2) + advanceUntilIdle() + val evalString2 = confidenceFeatureProvider.getStringEvaluation( + "test-kotlin-flag-1.mystring", + "default", + evaluationContext2 + ) + assertEquals("blue", evalString2.value) + } + @Test fun testApplyOnMultipleEvaluations() = runTest { val testDispatcher = UnconfinedTestDispatcher(testScheduler)