From 75912179128fd5e75ee17bf40c37cf35190cd791 Mon Sep 17 00:00:00 2001 From: CaitlynStocker Date: Mon, 25 May 2026 15:21:50 +1000 Subject: [PATCH 1/3] First pass with claude tests --- .../provider/OctopusContextProvider.java | 6 +- .../provider/OctopusContextProviderTests.java | 108 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java diff --git a/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java b/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java index 0f76e89..ca56e1f 100644 --- a/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java +++ b/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java @@ -52,7 +52,11 @@ void refresh() { if (client.haveFeatureTogglesChanged(currentContext.getContentHash())) { var toggles = client.getFeatureToggleEvaluationManifest(); - currentContext = toggles == null ? OctopusContext.empty() : new OctopusContext(toggles); + if (toggles != null) { + currentContext = new OctopusContext(toggles); + } else { + logger.log(System.Logger.Level.ERROR, "Failed to retrieve updated feature manifest. Retaining existing context which may be stale."); + } } delay = config.getCacheDuration(); diff --git a/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java new file mode 100644 index 0000000..a986e8c --- /dev/null +++ b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java @@ -0,0 +1,108 @@ +package com.octopus.openfeature.provider; + +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static org.assertj.core.api.Assertions.assertThat; + +class OctopusContextProviderTests { + + static class FakeClient extends OctopusClient { + private volatile FeatureToggles toggles; + + FakeClient(FeatureToggles toggles) { + super(null); + this.toggles = toggles; + } + + void changeToggles(FeatureToggles toggles) { + this.toggles = toggles; + } + + @Override + Boolean haveFeatureTogglesChanged(byte[] contentHash) { + return true; + } + + @Override + FeatureToggles getFeatureToggleEvaluationManifest() { + return toggles; + } + } + + private OctopusConfiguration fastConfig() { + var config = new OctopusConfiguration("token"); + config.setCacheDuration(Duration.ofMillis(100)); + return config; + } + + @Test + void whenInitialized_RefreshesCacheAfterCacheDurationExpires() throws InterruptedException { + byte[] initialHash = {0x01, 0x02, 0x03, 0x04}; + byte[] updatedHash = {0x01, 0x02, 0x03, 0x05}; + + var client = new FakeClient(new FeatureToggles( + List.of(new FeatureToggleEvaluation("test-feature", true, "evaluation-key", Collections.emptyList(), 100)), + initialHash + )); + var provider = new OctopusContextProvider(fastConfig(), client); + provider.initialize(); + + try { + assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(initialHash); + assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isTrue(); + + client.changeToggles(new FeatureToggles( + List.of(new FeatureToggleEvaluation("test-feature", false, "evaluation-key", Collections.emptyList(), 100)), + updatedHash + )); + Thread.sleep(500); + + assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(updatedHash); + assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isFalse(); + } finally { + provider.shutdown(); + } + } + + @Test + void whenInitialized_AndRefreshFails_RetainsExistingContextAndLogsError() throws InterruptedException { + byte[] contentHash = {0x01, 0x02, 0x03, 0x04}; + + var client = new FakeClient(new FeatureToggles( + List.of(new FeatureToggleEvaluation("test-feature", true, "evaluation-key", Collections.emptyList(), 100)), + contentHash + )); + + var logMessages = new ArrayList(); + var julLogger = Logger.getLogger(OctopusClient.class.getName()); + var handler = new Handler() { + @Override public void publish(LogRecord record) { logMessages.add(record.getMessage()); } + @Override public void flush() {} + @Override public void close() {} + }; + julLogger.addHandler(handler); + + var provider = new OctopusContextProvider(fastConfig(), client); + try { + provider.initialize(); + + client.changeToggles(null); + Thread.sleep(500); + + assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(contentHash); + assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isTrue(); + assertThat(logMessages).anyMatch(m -> m.startsWith("Failed to retrieve updated feature manifest")); + } finally { + julLogger.removeHandler(handler); + provider.shutdown(); + } + } +} From 724f9141aa04b21528cab78e02978c4b5a9078d3 Mon Sep 17 00:00:00 2001 From: CaitlynStocker Date: Mon, 25 May 2026 16:11:42 +1000 Subject: [PATCH 2/3] Clean up tests --- .../provider/OctopusContextProviderTests.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java index a986e8c..5ca085b 100644 --- a/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java +++ b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java @@ -1,7 +1,4 @@ package com.octopus.openfeature.provider; - -import org.junit.jupiter.api.Test; - import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -11,13 +8,15 @@ import java.util.logging.Logger; import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; class OctopusContextProviderTests { - static class FakeClient extends OctopusClient { + static class MockOctopusFeatureClient extends OctopusClient { + private volatile FeatureToggles toggles; - FakeClient(FeatureToggles toggles) { + MockOctopusFeatureClient(FeatureToggles toggles) { super(null); this.toggles = toggles; } @@ -37,7 +36,9 @@ FeatureToggles getFeatureToggleEvaluationManifest() { } } - private OctopusConfiguration fastConfig() { + private final OctopusConfiguration configuration = configure(); + + private static OctopusConfiguration configure() { var config = new OctopusConfiguration("token"); config.setCacheDuration(Duration.ofMillis(100)); return config; @@ -45,28 +46,36 @@ private OctopusConfiguration fastConfig() { @Test void whenInitialized_RefreshesCacheAfterCacheDurationExpires() throws InterruptedException { + byte[] initialHash = {0x01, 0x02, 0x03, 0x04}; byte[] updatedHash = {0x01, 0x02, 0x03, 0x05}; - var client = new FakeClient(new FeatureToggles( + var client = new MockOctopusFeatureClient(new FeatureToggles( List.of(new FeatureToggleEvaluation("test-feature", true, "evaluation-key", Collections.emptyList(), 100)), initialHash )); - var provider = new OctopusContextProvider(fastConfig(), client); + + var provider = new OctopusContextProvider(configuration, client); provider.initialize(); try { + // Validate the initial state assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(initialHash); assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isTrue(); + // Simulate a change in the available feature toggles client.changeToggles(new FeatureToggles( List.of(new FeatureToggleEvaluation("test-feature", false, "evaluation-key", Collections.emptyList(), 100)), updatedHash )); + + // Wait for the cache to expire Thread.sleep(500); + // Validate the updated toggles are available assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(updatedHash); assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isFalse(); + } finally { provider.shutdown(); } @@ -74,9 +83,10 @@ void whenInitialized_RefreshesCacheAfterCacheDurationExpires() throws Interrupte @Test void whenInitialized_AndRefreshFails_RetainsExistingContextAndLogsError() throws InterruptedException { + byte[] contentHash = {0x01, 0x02, 0x03, 0x04}; - var client = new FakeClient(new FeatureToggles( + var client = new MockOctopusFeatureClient(new FeatureToggles( List.of(new FeatureToggleEvaluation("test-feature", true, "evaluation-key", Collections.emptyList(), 100)), contentHash )); @@ -90,16 +100,22 @@ void whenInitialized_AndRefreshFails_RetainsExistingContextAndLogsError() throws }; julLogger.addHandler(handler); - var provider = new OctopusContextProvider(fastConfig(), client); + var provider = new OctopusContextProvider(configuration, client); + try { provider.initialize(); + // Simulate a failed fetch client.changeToggles(null); + + // Wait for the cache to expire Thread.sleep(500); + // Validate that the existing context is retained and an error was logged assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(contentHash); assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isTrue(); assertThat(logMessages).anyMatch(m -> m.startsWith("Failed to retrieve updated feature manifest")); + } finally { julLogger.removeHandler(handler); provider.shutdown(); From 0f33ca080cfd06f97937265d3e1d5ed6f338d0e4 Mon Sep 17 00:00:00 2001 From: CaitlynStocker Date: Tue, 26 May 2026 14:08:05 +1000 Subject: [PATCH 3/3] Small fix to test as per PR review --- .../openfeature/provider/OctopusContextProviderTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java index 5ca085b..8fee389 100644 --- a/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java +++ b/src/test/java/com/octopus/openfeature/provider/OctopusContextProviderTests.java @@ -74,7 +74,7 @@ void whenInitialized_RefreshesCacheAfterCacheDurationExpires() throws Interrupte // Validate the updated toggles are available assertThat(provider.getOctopusContext().getContentHash()).isEqualTo(updatedHash); - assertThat(provider.getOctopusContext().evaluate("test-feature", false, null).getValue()).isFalse(); + assertThat(provider.getOctopusContext().evaluate("test-feature", true, null).getValue()).isFalse(); } finally { provider.shutdown();