From 5c2b080170d6f9eb1d103590eff545e4ffdf7819 Mon Sep 17 00:00:00 2001 From: CaitlynStocker Date: Wed, 3 Jun 2026 19:13:44 +1000 Subject: [PATCH 1/2] Just the error logging with no tests --- .../openfeature/provider/OctopusContext.java | 8 +++-- .../openfeature/provider/OctopusProvider.java | 36 +++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/octopus/openfeature/provider/OctopusContext.java b/src/main/java/com/octopus/openfeature/provider/OctopusContext.java index 19e35c8..2bd33a3 100644 --- a/src/main/java/com/octopus/openfeature/provider/OctopusContext.java +++ b/src/main/java/com/octopus/openfeature/provider/OctopusContext.java @@ -27,10 +27,14 @@ byte[] getContentHash() { return featureToggles.getContentHash(); } - ProviderEvaluation evaluate(String slug, Boolean defaultValue, EvaluationContext evaluationContext) { - var toggleValue = featureToggles.getEvaluations().stream() + FeatureToggleEvaluation findFeatureToggleBySlug(String slug) { + return featureToggles.getEvaluations().stream() .filter(f -> f.getSlug().equalsIgnoreCase(slug)) .findFirst().orElse(null); + } + + ProviderEvaluation evaluate(String slug, Boolean defaultValue, EvaluationContext evaluationContext) { + var toggleValue = findFeatureToggleBySlug(slug); if (toggleValue == null) { throw new FlagNotFoundError(); diff --git a/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java b/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java index 6a28bc3..197f133 100644 --- a/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java +++ b/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java @@ -1,6 +1,12 @@ package com.octopus.openfeature.provider; -import dev.openfeature.sdk.*; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.TypeMismatchError; public class OctopusProvider extends EventProvider { private static final String PROVIDER_NAME = "octopus-java-provider"; @@ -8,10 +14,10 @@ public class OctopusProvider extends EventProvider { private final OctopusContextProvider contextProvider; public OctopusProvider(OctopusConfiguration config) { - this.config = config; + this.config = config; this.contextProvider = new OctopusContextProvider(config, new OctopusClient(config)); } - + @Override public Metadata getMetadata() { return () -> PROVIDER_NAME; } @@ -33,22 +39,30 @@ public ProviderEvaluation getBooleanEvaluation(String flagKey, Boolean } @Override - public ProviderEvaluation getStringEvaluation(String s, String s1, EvaluationContext evaluationContext) { - throw new UnsupportedOperationException("Only boolean values are currently supported"); + public ProviderEvaluation getStringEvaluation(String flagKey, String defaultValue, EvaluationContext evaluationContext) { + throw rejectNonBooleanEvaluation(flagKey); } @Override - public ProviderEvaluation getIntegerEvaluation(String s, Integer integer, EvaluationContext evaluationContext) { - throw new UnsupportedOperationException("Only boolean values are currently supported"); + public ProviderEvaluation getIntegerEvaluation(String flagKey, Integer defaultValue, EvaluationContext evaluationContext) { + throw rejectNonBooleanEvaluation(flagKey); } @Override - public ProviderEvaluation getDoubleEvaluation(String s, Double aDouble, EvaluationContext evaluationContext) { - throw new UnsupportedOperationException("Only boolean values are currently supported"); + public ProviderEvaluation getDoubleEvaluation(String flagKey, Double defaultValue, EvaluationContext evaluationContext) { + throw rejectNonBooleanEvaluation(flagKey); } @Override - public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) { - throw new UnsupportedOperationException("Only boolean values are currently supported"); + public ProviderEvaluation getObjectEvaluation(String flagKey, Value defaultValue, EvaluationContext evaluationContext) { + throw rejectNonBooleanEvaluation(flagKey); + } + + private RuntimeException rejectNonBooleanEvaluation(String flagKey) { + var toggle = contextProvider.getOctopusContext().findFeatureToggleBySlug(flagKey); + if (toggle == null) { + return new FlagNotFoundError(flagKey); + } + return new TypeMismatchError("Octopus Feature Toggles only supports boolean toggles."); } } From 00d159ee248e33671820f442856db7f7aa151a06 Mon Sep 17 00:00:00 2001 From: CaitlynStocker Date: Wed, 3 Jun 2026 19:31:38 +1000 Subject: [PATCH 2/2] Add unit tests --- .../provider/OctopusContextProvider.java | 8 ++ .../openfeature/provider/OctopusProvider.java | 6 ++ .../provider/OctopusProviderTests.java | 83 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/test/java/com/octopus/openfeature/provider/OctopusProviderTests.java diff --git a/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java b/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java index c5c042a..157cc0b 100644 --- a/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java +++ b/src/main/java/com/octopus/openfeature/provider/OctopusContextProvider.java @@ -12,6 +12,14 @@ class OctopusContextProvider { this.config = config; this.client = client; } + + // For unit testing: skips HTTP fetch and background refresh by pre-loading a known context. + OctopusContextProvider(OctopusContext context) { + this.config = null; + this.client = null; + this.currentContext = context; + this.initialized = true; + } OctopusContext getOctopusContext() { return currentContext; } diff --git a/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java b/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java index 197f133..6ce4bf5 100644 --- a/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java +++ b/src/main/java/com/octopus/openfeature/provider/OctopusProvider.java @@ -18,6 +18,12 @@ public OctopusProvider(OctopusConfiguration config) { this.contextProvider = new OctopusContextProvider(config, new OctopusClient(config)); } + // For unit testing: accepts a pre-built context provider instead of constructing one from config. + OctopusProvider(OctopusContextProvider contextProvider) { + this.config = null; + this.contextProvider = contextProvider; + } + @Override public Metadata getMetadata() { return () -> PROVIDER_NAME; } diff --git a/src/test/java/com/octopus/openfeature/provider/OctopusProviderTests.java b/src/test/java/com/octopus/openfeature/provider/OctopusProviderTests.java new file mode 100644 index 0000000..126cb1d --- /dev/null +++ b/src/test/java/com/octopus/openfeature/provider/OctopusProviderTests.java @@ -0,0 +1,83 @@ +package com.octopus.openfeature.provider; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Value; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class OctopusProviderTests { + + private Client client; + + @BeforeEach + void setup() throws Exception { + var toggles = new FeatureToggles( + List.of(new FeatureToggleEvaluation("feature-a", true, "key", Collections.emptyList(), 100)), + new byte[0] + ); + var provider = new OctopusProvider(new OctopusContextProvider(new OctopusContext(toggles))); + OpenFeatureAPI.getInstance().setProviderAndWait(provider); + client = OpenFeatureAPI.getInstance().getClient(); + } + + @AfterEach + void teardown() { + OpenFeatureAPI.getInstance().shutdown(); + } + + @Test + void givenAKnownFlag_whenRequestedAsString_returnsTypeMismatch() { + assertThat(client.getStringDetails("feature-a", "default").getErrorCode()) + .isEqualTo(ErrorCode.TYPE_MISMATCH); + } + + @Test + void givenAnUnknownFlag_whenRequestedAsString_returnsFlagNotFound() { + assertThat(client.getStringDetails("nonexistent", "default").getErrorCode()) + .isEqualTo(ErrorCode.FLAG_NOT_FOUND); + } + + @Test + void givenAKnownFlag_whenRequestedAsInteger_returnsTypeMismatch() { + assertThat(client.getIntegerDetails("feature-a", 0).getErrorCode()) + .isEqualTo(ErrorCode.TYPE_MISMATCH); + } + + @Test + void givenAnUnknownFlag_whenRequestedAsInteger_returnsFlagNotFound() { + assertThat(client.getIntegerDetails("nonexistent", 0).getErrorCode()) + .isEqualTo(ErrorCode.FLAG_NOT_FOUND); + } + + @Test + void givenAKnownFlag_whenRequestedAsDouble_returnsTypeMismatch() { + assertThat(client.getDoubleDetails("feature-a", 0.0).getErrorCode()) + .isEqualTo(ErrorCode.TYPE_MISMATCH); + } + + @Test + void givenAnUnknownFlag_whenRequestedAsDouble_returnsFlagNotFound() { + assertThat(client.getDoubleDetails("nonexistent", 0.0).getErrorCode()) + .isEqualTo(ErrorCode.FLAG_NOT_FOUND); + } + + @Test + void givenAKnownFlag_whenRequestedAsObject_returnsTypeMismatch() { + assertThat(client.getObjectDetails("feature-a", new Value()).getErrorCode()) + .isEqualTo(ErrorCode.TYPE_MISMATCH); + } + + @Test + void givenAnUnknownFlag_whenRequestedAsObject_returnsFlagNotFound() { + assertThat(client.getObjectDetails("nonexistent", new Value()).getErrorCode()) + .isEqualTo(ErrorCode.FLAG_NOT_FOUND); + } +}