Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ byte[] getContentHash() {
return featureToggles.getContentHash();
}

ProviderEvaluation<Boolean> 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<Boolean> evaluate(String slug, Boolean defaultValue, EvaluationContext evaluationContext) {
var toggleValue = findFeatureToggleBySlug(slug);

if (toggleValue == null) {
throw new FlagNotFoundError();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels weird to me, but I don't know Java and I've had some long convos with Claude insisting it's correct.


OctopusContext getOctopusContext() { return currentContext; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
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";
private final OctopusConfiguration config;
private final OctopusContextProvider contextProvider;

public OctopusProvider(OctopusConfiguration config) {
this.config = config;
this.config = 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; }

Expand All @@ -33,22 +45,30 @@ public ProviderEvaluation<Boolean> getBooleanEvaluation(String flagKey, Boolean
}

@Override
public ProviderEvaluation<String> getStringEvaluation(String s, String s1, EvaluationContext evaluationContext) {
throw new UnsupportedOperationException("Only boolean values are currently supported");
public ProviderEvaluation<String> getStringEvaluation(String flagKey, String defaultValue, EvaluationContext evaluationContext) {
throw rejectNonBooleanEvaluation(flagKey);
}

@Override
public ProviderEvaluation<Integer> getIntegerEvaluation(String s, Integer integer, EvaluationContext evaluationContext) {
throw new UnsupportedOperationException("Only boolean values are currently supported");
public ProviderEvaluation<Integer> getIntegerEvaluation(String flagKey, Integer defaultValue, EvaluationContext evaluationContext) {
throw rejectNonBooleanEvaluation(flagKey);
}

@Override
public ProviderEvaluation<Double> getDoubleEvaluation(String s, Double aDouble, EvaluationContext evaluationContext) {
throw new UnsupportedOperationException("Only boolean values are currently supported");
public ProviderEvaluation<Double> getDoubleEvaluation(String flagKey, Double defaultValue, EvaluationContext evaluationContext) {
throw rejectNonBooleanEvaluation(flagKey);
}

@Override
public ProviderEvaluation<Value> getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) {
throw new UnsupportedOperationException("Only boolean values are currently supported");
public ProviderEvaluation<Value> 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.");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading