From b9f392abafe824aa3a10ce41d0825dcd9b03a677 Mon Sep 17 00:00:00 2001 From: vahid torkaman Date: Mon, 31 Jul 2023 15:53:39 +0200 Subject: [PATCH] add tests for failure and not returning network requests --- .../providers/apply/FlagApplierWithRetries.kt | 39 +++--- .../ConfidenceFeatureProviderTests.kt | 121 +++++++++++++++++- 2 files changed, 133 insertions(+), 27 deletions(-) diff --git a/Provider/src/main/java/dev/openfeature/contrib/providers/apply/FlagApplierWithRetries.kt b/Provider/src/main/java/dev/openfeature/contrib/providers/apply/FlagApplierWithRetries.kt index 1b6cee7c..699f8544 100644 --- a/Provider/src/main/java/dev/openfeature/contrib/providers/apply/FlagApplierWithRetries.kt +++ b/Provider/src/main/java/dev/openfeature/contrib/providers/apply/FlagApplierWithRetries.kt @@ -45,7 +45,7 @@ enum class EventStatus { data class ApplyInstance( @Contextual val time: Date, - val sent: EventStatus + val eventStatus: EventStatus ) internal typealias FlagsAppliedMap = @@ -67,8 +67,9 @@ class FlagApplierWithRetries( onInitialised = { val data: FlagsAppliedMap = mutableMapOf() readFile(data) - setSendingEventsCreating(data) - data + // we consider all the sending events as not sent to not miss them + // there is a chance that they are sending status because the network response is dropped + changeSendingEventsToCreate(data) }, onApply = { flagApplierInput, mutableData -> val data = internalApply( @@ -111,20 +112,6 @@ class FlagApplierWithRetries( ) } - // we consider all the sending events as not sent to not miss them - // there is a chance that they are sending status because the network response is dropped - private fun setSendingEventsCreating(data: FlagsAppliedMap) { - data.entries.forEach { - it.value.mapValues { entry -> - if (entry.value.sent == EventStatus.SENDING) { - EventStatus.CREATED - } else { - entry.value.sent - } - } - } - } - init { eventProcessor.start() } @@ -152,7 +139,7 @@ class FlagApplierWithRetries( data.entries.forEach { (token, flagsForToken) -> val appliedFlagsKeyed: List = flagsForToken.entries .filter { e -> - e.value.sent == EventStatus.CREATED + e.value.eventStatus == EventStatus.CREATED } .map { e -> AppliedFlag(e.key, e.value.time) } // TODO chunk size 20 is an arbitrary value, replace with appropriate size @@ -181,19 +168,31 @@ class FlagApplierWithRetries( appliedFlag.forEach { applied -> data[token]?.let { map -> computeIfPresent(map, applied.flag) { _, v -> - v.copy(sent = eventStatus) + v.copy(eventStatus = eventStatus) } } } } + private fun changeSendingEventsToCreate(data: FlagsAppliedMap) = data.mapValues { entryValue -> + entryValue.value + .mapValues { entry -> + if (entry.value.eventStatus == EventStatus.SENDING) { + entry.value.copy(eventStatus = EventStatus.CREATED) + } else { + entry.value + } + } + .toMutableMap() + }.toMutableMap() + private fun writeToFile( data: Map> ) { // All apply events have been sent for this token, don't add this token to the file val toStoreData = data .filter { - !it.value.values.all { applyInstance -> applyInstance.sent == EventStatus.SENT } + !it.value.values.all { applyInstance -> applyInstance.eventStatus == EventStatus.SENT } } file.writeText(json.encodeToString(toStoreData)) } 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 8856de53..cba68118 100644 --- a/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt +++ b/Provider/src/test/java/dev/openfeature/contrib/providers/ConfidenceFeatureProviderTests.kt @@ -57,23 +57,23 @@ private const val cacheFileData = "{\n" + " \"token1\": {\n" + " \"fdema-kotlin-flag-0\": {\n" + " \"time\": \"2023-06-26T11:55:33.443Z\",\n" + - " \"sent\": \"SENT\"\n" + + " \"eventStatus\": \"SENT\"\n" + " }\n" + " },\n" + " \"token2\": {\n" + " \"fdema-kotlin-flag-2\": {\n" + " \"time\": \"2023-06-26T11:55:33.444Z\",\n" + - " \"sent\": \"SENT\"\n" + + " \"eventStatus\": \"SENT\"\n" + " },\n" + " \"fdema-kotlin-flag-3\": {\n" + " \"time\": \"2023-06-26T11:55:33.445Z\",\n" + - " \"sent\": \"CREATED\"\n" + + " \"eventStatus\": \"CREATED\"\n" + " }\n" + " },\n" + " \"token3\": {\n" + " \"fdema-kotlin-flag-4\": {\n" + " \"time\": \"2023-06-26T11:55:33.446Z\",\n" + - " \"sent\": \"CREATED\"\n" + + " \"eventStatus\": \"CREATED\"\n" + " }\n" + " }\n" + "}\n" @@ -251,7 +251,7 @@ internal class ConfidenceFeatureProviderTests { advanceUntilIdle() verify(mockClient, times(8)).apply(any(), eq("token1")) val expectedStatus = json.decodeFromString(cacheFile.readText())["token1"] - ?.get("fdema-kotlin-flag-1")?.sent + ?.get("fdema-kotlin-flag-1")?.eventStatus assertEquals(EventStatus.CREATED, expectedStatus) whenever(mockClient.apply(any(), any())).thenReturn(Result.Success) @@ -399,7 +399,7 @@ internal class ConfidenceFeatureProviderTests { fun testApplyFromStoredCache() = runTest { val cacheFile = File(mockContext.filesDir, APPLY_FILE_NAME) cacheFile.writeText( - "{\"token1\":{\"fdema-kotlin-flag-1\":{\"time\":\"2023-06-26T11:55:33.184774Z\",\"sent\":\"CREATED\"}}}" + "{\"token1\":{\"fdema-kotlin-flag-1\":{\"time\":\"2023-06-26T11:55:33.184774Z\",\"eventStatus\":\"CREATED\"}}}" ) val testDispatcher = UnconfinedTestDispatcher(testScheduler) @@ -430,6 +430,113 @@ internal class ConfidenceFeatureProviderTests { verify(mockClient, times(1)).apply(any(), eq("token1")) } + @Test + fun testApplyFromStoredCacheSendingStatus() = runTest { + val cacheFile = File(mockContext.filesDir, APPLY_FILE_NAME) + cacheFile.writeText( + "{\"token1\":{\"fdema-kotlin-flag-1\":{\"time\":\"2023-06-26T11:55:33.184774Z\",\"eventStatus\":\"SENDING\"}}}" + ) + whenever(mockClient.apply(any(), any())).thenReturn(Result.Success) + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + val confidenceFeatureProvider = ConfidenceFeatureProvider.create( + context = mockContext, + clientSecret = "", + cache = InMemoryCache(), + client = mockClient, + eventsPublisher = EventHandler.eventsPublisher(testDispatcher), + dispatcher = testDispatcher + ) + + whenever(mockClient.resolve(eq(listOf()), any())).thenReturn( + ResolveResponse.Resolved( + ResolveFlags(resolvedFlags, "token1") + ) + ) + + val evaluationContext = ImmutableContext("foo") + confidenceFeatureProvider.initialize(evaluationContext) + advanceUntilIdle() + + verify(mockClient, times(1)).resolve(any(), eq(evaluationContext)) + + confidenceFeatureProvider.getStringEvaluation("fdema-kotlin-flag-1.mystring", "empty", evaluationContext) + advanceUntilIdle() + verify(mockClient, times(1)).apply(any(), eq("token1")) + } + + @Test + fun testNotSendDuplicateWhileSending() = runTest { + val cacheFile = File(mockContext.filesDir, APPLY_FILE_NAME) + cacheFile.writeText( + "{\"token1\":{\"fdema-kotlin-flag-1\":{\"time\":\"2023-06-26T11:55:33.184774Z\",\"eventStatus\":\"CREATED\"}}}" + ) + whenever(mockClient.apply(any(), any())).then { } + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + val confidenceFeatureProvider = ConfidenceFeatureProvider.create( + context = mockContext, + clientSecret = "", + cache = InMemoryCache(), + client = mockClient, + eventsPublisher = EventHandler.eventsPublisher(testDispatcher), + dispatcher = testDispatcher + ) + + whenever(mockClient.resolve(eq(listOf()), any())).thenReturn( + ResolveResponse.Resolved( + ResolveFlags(resolvedFlags, "token1") + ) + ) + + val evaluationContext = ImmutableContext("foo") + confidenceFeatureProvider.initialize(evaluationContext) + advanceUntilIdle() + + verify(mockClient, times(1)).resolve(any(), eq(evaluationContext)) + + confidenceFeatureProvider.getStringEvaluation("fdema-kotlin-flag-1.mystring", "empty", evaluationContext) + advanceUntilIdle() + confidenceFeatureProvider.getStringEvaluation("fdema-kotlin-flag-1.myboolean", "false", evaluationContext) + advanceUntilIdle() + verify(mockClient, times(1)).apply(any(), eq("token1")) + } + + @Test + fun testDoSendAgainWhenNetworkRequestFailed() = runTest { + val cacheFile = File(mockContext.filesDir, APPLY_FILE_NAME) + cacheFile.writeText( + "{\"token1\":{\"fdema-kotlin-flag-1\":{\"time\":\"2023-06-26T11:55:33.184774Z\",\"eventStatus\":\"CREATED\"}}}" + ) + + whenever(mockClient.apply(any(), any())).thenReturn(Result.Failure) + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + val confidenceFeatureProvider = ConfidenceFeatureProvider.create( + context = mockContext, + clientSecret = "", + cache = InMemoryCache(), + client = mockClient, + eventsPublisher = EventHandler.eventsPublisher(testDispatcher), + dispatcher = testDispatcher + ) + + whenever(mockClient.resolve(eq(listOf()), any())).thenReturn( + ResolveResponse.Resolved( + ResolveFlags(resolvedFlags, "token1") + ) + ) + + val evaluationContext = ImmutableContext("foo") + confidenceFeatureProvider.initialize(evaluationContext) + advanceUntilIdle() + + verify(mockClient, times(1)).resolve(any(), eq(evaluationContext)) + + confidenceFeatureProvider.getStringEvaluation("fdema-kotlin-flag-1.mystring", "empty", evaluationContext) + advanceUntilIdle() + confidenceFeatureProvider.getStringEvaluation("fdema-kotlin-flag-1.myboolean", "false", evaluationContext) + advanceUntilIdle() + verify(mockClient, times(3)).apply(any(), eq("token1")) + } + @Test fun testOnProcessBatchOnInitAndEval() = runTest { val cacheFile = File(mockContext.filesDir, APPLY_FILE_NAME) @@ -474,7 +581,7 @@ internal class ConfidenceFeatureProviderTests { cacheFile.writeText(cacheFileData) whenever(mockClient.apply(any(), any())).thenReturn(Result.Success) val testDispatcher = UnconfinedTestDispatcher(testScheduler) - val test = ConfidenceFeatureProvider.create( + ConfidenceFeatureProvider.create( context = mockContext, clientSecret = "", cache = InMemoryCache(),