From 516c6a02312e6b5536938744024941f6fbf88cec Mon Sep 17 00:00:00 2001 From: Liam Hughes Date: Thu, 19 Mar 2026 13:18:20 +1100 Subject: [PATCH 1/5] JSON deserialisation tests --- ...eToggleEvaluationDeserializationTests.java | 71 +++++++++++++++++++ .../openfeature/provider/toggle-disabled.json | 6 ++ .../provider/toggle-enabled-no-segments.json | 6 ++ .../openfeature/provider/toggle-list.json | 14 ++++ .../provider/toggle-missing-segments.json | 5 ++ .../provider/toggle-with-segments.json | 9 +++ 6 files changed, 111 insertions(+) create mode 100644 src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-disabled.json create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-enabled-no-segments.json create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-list.json create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-missing-segments.json create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json diff --git a/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java new file mode 100644 index 0000000..7263dac --- /dev/null +++ b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java @@ -0,0 +1,71 @@ +package com.octopus.openfeature.provider; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class FeatureToggleEvaluationDeserializationTests { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private InputStream resource(String name) { + return getClass().getResourceAsStream(name); + } + + @Test + void shouldDeserializeEnabledToggle() throws Exception { + FeatureToggleEvaluation result = objectMapper.readValue( + resource("toggle-enabled-no-segments.json"), FeatureToggleEvaluation.class); + + assertThat(result.getName()).isEqualTo("My Feature"); + assertThat(result.getSlug()).isEqualTo("my-feature"); + assertThat(result.isEnabled()).isTrue(); + assertThat(result.getSegments()).isEmpty(); + } + + @Test + void shouldDeserializeDisabledToggle() throws Exception { + FeatureToggleEvaluation result = objectMapper.readValue( + resource("toggle-disabled.json"), FeatureToggleEvaluation.class); + + assertThat(result.isEnabled()).isFalse(); + } + + @Test + void shouldDeserializeToggleWithMissingSegmentsField() throws Exception { + FeatureToggleEvaluation result = objectMapper.readValue( + resource("toggle-missing-segments.json"), FeatureToggleEvaluation.class); + + assertThat(result.getSegments()).isNotNull().isEmpty(); + } + + @Test + void shouldDeserializeToggleWithSegments() throws Exception { + FeatureToggleEvaluation result = objectMapper.readValue( + resource("toggle-with-segments.json"), FeatureToggleEvaluation.class); + + assertThat(result.getSegments()).hasSize(2); + assertThat(result.getSegments()).contains( + Map.entry("license-type", "free"), + Map.entry("country", "au") + ); + } + + @Test + void shouldDeserializeListOfToggles() throws Exception { + List result = objectMapper.readValue( + resource("toggle-list.json"), new TypeReference>() {}); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getSlug()).isEqualTo("feature-a"); + assertThat(result.get(0).isEnabled()).isTrue(); + assertThat(result.get(1).getSlug()).isEqualTo("feature-b"); + assertThat(result.get(1).isEnabled()).isFalse(); + } +} diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-disabled.json b/src/test/resources/com/octopus/openfeature/provider/toggle-disabled.json new file mode 100644 index 0000000..f09370d --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-disabled.json @@ -0,0 +1,6 @@ +{ + "name": "My Feature", + "slug": "my-feature", + "isEnabled": false, + "segments": null +} diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-enabled-no-segments.json b/src/test/resources/com/octopus/openfeature/provider/toggle-enabled-no-segments.json new file mode 100644 index 0000000..e9d17e9 --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-enabled-no-segments.json @@ -0,0 +1,6 @@ +{ + "name": "My Feature", + "slug": "my-feature", + "isEnabled": true, + "segments": null +} diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-list.json b/src/test/resources/com/octopus/openfeature/provider/toggle-list.json new file mode 100644 index 0000000..ea6d15f --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-list.json @@ -0,0 +1,14 @@ +[ + { + "name": "Feature A", + "slug": "feature-a", + "isEnabled": true, + "segments": null + }, + { + "name": "Feature B", + "slug": "feature-b", + "isEnabled": false, + "segments": null + } +] diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-missing-segments.json b/src/test/resources/com/octopus/openfeature/provider/toggle-missing-segments.json new file mode 100644 index 0000000..285d13e --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-missing-segments.json @@ -0,0 +1,5 @@ +{ + "name": "My Feature", + "slug": "my-feature", + "isEnabled": true +} diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json b/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json new file mode 100644 index 0000000..d377118 --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json @@ -0,0 +1,9 @@ +{ + "name": "My Feature", + "slug": "my-feature", + "isEnabled": true, + "segments": [ + { "license-type": "free" }, + { "country": "au" } + ] +} From 70387dcbb6c4d09185784ddc7fb5c0946aee6433 Mon Sep 17 00:00:00 2001 From: Liam Hughes Date: Thu, 19 Mar 2026 14:40:18 +1100 Subject: [PATCH 2/5] Update with OctoToggle response shape --- .../openfeature/provider/toggle-with-segments.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json b/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json index d377118..88ae4f0 100644 --- a/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-with-segments.json @@ -3,7 +3,13 @@ "slug": "my-feature", "isEnabled": true, "segments": [ - { "license-type": "free" }, - { "country": "au" } + { + "key": "license-type", + "value": "free" + }, + { + "key": "country", + "value": "au" + } ] -} +} \ No newline at end of file From 45a7d62146f1b09416c3b3c4651e789713618481 Mon Sep 17 00:00:00 2001 From: Liam Hughes Date: Fri, 20 Mar 2026 13:49:26 +1100 Subject: [PATCH 3/5] Add shouldIgnoreExtraneousProperties test --- .../FeatureToggleEvaluationDeserializationTests.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java index 7263dac..6039425 100644 --- a/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java +++ b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java @@ -68,4 +68,15 @@ void shouldDeserializeListOfToggles() throws Exception { assertThat(result.get(1).getSlug()).isEqualTo("feature-b"); assertThat(result.get(1).isEnabled()).isFalse(); } + + @Test + void shouldIgnoreExtraneousProperties() throws Exception { + FeatureToggleEvaluation result = objectMapper.readValue( + resource("toggle-with-extraneous-properties.json"), FeatureToggleEvaluation.class); + + assertThat(result.getName()).isEqualTo("My Feature"); + assertThat(result.getSlug()).isEqualTo("my-feature"); + assertThat(result.isEnabled()).isTrue(); + assertThat(result.getSegments()).isEmpty(); + } } From c79de1deaa40b30f31e4a047d35bdd9c732881b5 Mon Sep 17 00:00:00 2001 From: Liam Hughes Date: Thu, 26 Mar 2026 09:26:34 +1100 Subject: [PATCH 4/5] Add test JSON --- .../toggle-with-extraneous-properties.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/resources/com/octopus/openfeature/provider/toggle-with-extraneous-properties.json diff --git a/src/test/resources/com/octopus/openfeature/provider/toggle-with-extraneous-properties.json b/src/test/resources/com/octopus/openfeature/provider/toggle-with-extraneous-properties.json new file mode 100644 index 0000000..39b78d1 --- /dev/null +++ b/src/test/resources/com/octopus/openfeature/provider/toggle-with-extraneous-properties.json @@ -0,0 +1,17 @@ +{ + "name": "My Feature", + "slug": "my-feature", + "isEnabled": true, + "segments": [ + { + "key": "license-type", + "value": "free", + "more": "data" + } + ], + "foo": "bar", + "qux": 123, + "wux": { + "nested": "value" + } +} \ No newline at end of file From 0426655e41f174f984bd9f4187ab1bcefe1b51d9 Mon Sep 17 00:00:00 2001 From: Liam Hughes Date: Thu, 26 Mar 2026 09:36:50 +1100 Subject: [PATCH 5/5] Apply fixes --- .../provider/FeatureToggleEvaluation.java | 13 +++++++++--- .../provider/SegmentDeserializer.java | 20 +++++++++++++++++++ ...eToggleEvaluationDeserializationTests.java | 16 +++++++-------- 3 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/octopus/openfeature/provider/SegmentDeserializer.java diff --git a/src/main/java/com/octopus/openfeature/provider/FeatureToggleEvaluation.java b/src/main/java/com/octopus/openfeature/provider/FeatureToggleEvaluation.java index d9db7be..b43f99c 100644 --- a/src/main/java/com/octopus/openfeature/provider/FeatureToggleEvaluation.java +++ b/src/main/java/com/octopus/openfeature/provider/FeatureToggleEvaluation.java @@ -1,12 +1,15 @@ package com.octopus.openfeature.provider; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.ArrayList; import java.util.List; import java.util.Map; +@JsonIgnoreProperties(ignoreUnknown = true) class FeatureToggleEvaluation { private final String name; private final String slug; @@ -14,12 +17,16 @@ class FeatureToggleEvaluation { private final List> segments; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - FeatureToggleEvaluation(@JsonProperty("name") String name, @JsonProperty("slug")String slug, @JsonProperty("isEnabled") boolean isEnabled, - @JsonProperty("segments") List> segments) { + FeatureToggleEvaluation( + @JsonProperty("name") String name, + @JsonProperty("slug") String slug, + @JsonProperty("isEnabled") boolean isEnabled, + @JsonDeserialize(contentUsing = SegmentDeserializer.class) @JsonProperty("segments") List> segments + ) { this.name = name; this.slug = slug; this.isEnabled = isEnabled; - + this.segments = new ArrayList<>(); if (segments != null) { this.segments.addAll(segments); diff --git a/src/main/java/com/octopus/openfeature/provider/SegmentDeserializer.java b/src/main/java/com/octopus/openfeature/provider/SegmentDeserializer.java new file mode 100644 index 0000000..3eba90d --- /dev/null +++ b/src/main/java/com/octopus/openfeature/provider/SegmentDeserializer.java @@ -0,0 +1,20 @@ +package com.octopus.openfeature.provider; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.Map; + +class SegmentDeserializer extends JsonDeserializer> { + @Override + public Map.Entry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.getCodec().readTree(p); + String key = node.get("key").asText(); + String value = node.get("value").asText(); + return new AbstractMap.SimpleEntry<>(key, value); + } +} diff --git a/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java index 6039425..52a514f 100644 --- a/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java +++ b/src/test/java/com/octopus/openfeature/provider/FeatureToggleEvaluationDeserializationTests.java @@ -20,8 +20,7 @@ private InputStream resource(String name) { @Test void shouldDeserializeEnabledToggle() throws Exception { - FeatureToggleEvaluation result = objectMapper.readValue( - resource("toggle-enabled-no-segments.json"), FeatureToggleEvaluation.class); + FeatureToggleEvaluation result = objectMapper.readValue(resource("toggle-enabled-no-segments.json"), FeatureToggleEvaluation.class); assertThat(result.getName()).isEqualTo("My Feature"); assertThat(result.getSlug()).isEqualTo("my-feature"); @@ -31,16 +30,14 @@ void shouldDeserializeEnabledToggle() throws Exception { @Test void shouldDeserializeDisabledToggle() throws Exception { - FeatureToggleEvaluation result = objectMapper.readValue( - resource("toggle-disabled.json"), FeatureToggleEvaluation.class); + FeatureToggleEvaluation result = objectMapper.readValue(resource("toggle-disabled.json"), FeatureToggleEvaluation.class); assertThat(result.isEnabled()).isFalse(); } @Test void shouldDeserializeToggleWithMissingSegmentsField() throws Exception { - FeatureToggleEvaluation result = objectMapper.readValue( - resource("toggle-missing-segments.json"), FeatureToggleEvaluation.class); + FeatureToggleEvaluation result = objectMapper.readValue(resource("toggle-missing-segments.json"), FeatureToggleEvaluation.class); assertThat(result.getSegments()).isNotNull().isEmpty(); } @@ -60,7 +57,10 @@ void shouldDeserializeToggleWithSegments() throws Exception { @Test void shouldDeserializeListOfToggles() throws Exception { List result = objectMapper.readValue( - resource("toggle-list.json"), new TypeReference>() {}); + resource("toggle-list.json"), + new TypeReference<>() { + } + ); assertThat(result).hasSize(2); assertThat(result.get(0).getSlug()).isEqualTo("feature-a"); @@ -77,6 +77,6 @@ void shouldIgnoreExtraneousProperties() throws Exception { assertThat(result.getName()).isEqualTo("My Feature"); assertThat(result.getSlug()).isEqualTo("my-feature"); assertThat(result.isEnabled()).isTrue(); - assertThat(result.getSegments()).isEmpty(); + assertThat(result.getSegments()).contains(Map.entry("license-type", "free")); } }