From c08e7bc228f1165bc049e73b6af804e886c3812b Mon Sep 17 00:00:00 2001 From: chrfwow Date: Thu, 2 Jan 2025 09:40:06 +0100 Subject: [PATCH] feat: Update in-process resolver to support flag metadata #1102 Signed-off-by: Todd Baert --- .../resolver/process/InProcessResolver.java | 87 ++- .../resolver/process/model/FeatureFlag.java | 13 +- .../process/InProcessResolverTest.java | 715 ++++++++++-------- .../flagd/resolver/process/MockFlags.java | 38 +- .../flagd/resolver/process/TestUtils.java | 1 + .../process/model/FlagParserTest.java | 37 +- .../flagConfigurations/invalid-metadata.json | 21 + .../flagConfigurations/valid-long.json | 7 +- .../flagConfigurations/valid-simple.json | 7 +- 9 files changed, 560 insertions(+), 366 deletions(-) create mode 100644 providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java index 663d4bb0d..3379bb57f 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java @@ -2,6 +2,10 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag.EMPTY_TARGETING_STRING; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + import dev.openfeature.contrib.providers.flagd.FlagdOptions; import dev.openfeature.contrib.providers.flagd.resolver.Resolver; import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent; @@ -37,8 +41,9 @@ public class InProcessResolver implements Resolver { private final Consumer onConnectionEvent; private final Operator operator; private final long deadline; - private final ImmutableMetadata metadata; + private final ImmutableMetadata fallBackMetadata; private final Supplier connectedSupplier; + private final String scope; /** * Resolves flag values using https://buf.build/open-feature/flagd/docs/main:flagd.sync.v1. Flags @@ -48,20 +53,22 @@ public class InProcessResolver implements Resolver { * @param connectedSupplier lambda providing current connection status from caller * @param onConnectionEvent lambda which handles changes in the connection/stream */ - public InProcessResolver( - FlagdOptions options, - final Supplier connectedSupplier, - Consumer onConnectionEvent) { + public InProcessResolver(FlagdOptions options, final Supplier connectedSupplier, + Consumer onConnectionEvent) { this.flagStore = new FlagStore(getConnector(options)); this.deadline = options.getDeadline(); this.onConnectionEvent = onConnectionEvent; this.operator = new Operator(); this.connectedSupplier = connectedSupplier; - this.metadata = options.getSelector() == null - ? null - : ImmutableMetadata.builder() - .addString("scope", options.getSelector()) - .build(); + if (options.getSelector() == null) { + this.scope = null; + this.fallBackMetadata = null; + } else { + this.scope = options.getSelector(); + this.fallBackMetadata = ImmutableMetadata.builder() + .addString("scope", this.scope) + .build(); + } } /** Initialize in-process resolver. */ @@ -109,8 +116,14 @@ public void shutdown() throws InterruptedException { onConnectionEvent.accept(new ConnectionEvent(false)); } - /** Resolve a boolean flag. */ - public ProviderEvaluation booleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + /** + * Resolve a boolean flag. + */ + public ProviderEvaluation booleanEvaluation( + String key, + Boolean defaultValue, + EvaluationContext ctx + ) { return resolve(Boolean.class, key, ctx); } @@ -161,6 +174,7 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC return ProviderEvaluation.builder() .errorMessage("flag: " + key + " not found") .errorCode(ErrorCode.FLAG_NOT_FOUND) + .flagMetadata(fallBackMetadata) .build(); } @@ -169,6 +183,7 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC return ProviderEvaluation.builder() .errorMessage("flag: " + key + " is disabled") .errorCode(ErrorCode.FLAG_NOT_FOUND) + .flagMetadata(getFlagMetadata(flag)) .build(); } @@ -215,13 +230,51 @@ private ProviderEvaluation resolve(Class type, String key, EvaluationC throw new TypeMismatchError(message); } - final ProviderEvaluation.ProviderEvaluationBuilder evaluationBuilder = ProviderEvaluation.builder() + return ProviderEvaluation.builder() .value((T) value) .variant(resolvedVariant) - .reason(reason); + .reason(reason) + .flagMetadata(getFlagMetadata(flag)) + .build(); + } + + private ImmutableMetadata getFlagMetadata(FeatureFlag flag) { + if (flag == null) { + return fallBackMetadata; + } + + ImmutableMetadata.ImmutableMetadataBuilder metadataBuilder = ImmutableMetadata.builder(); + if (scope != null) { + metadataBuilder.addString("scope", scope); + } + + for (Map.Entry entry : flag.getMetadata().entrySet()) { + Object value = entry.getValue(); + if (value instanceof Number) { + if (value instanceof Long) { + metadataBuilder.addLong(entry.getKey(), (Long) value); + continue; + } else if (value instanceof Double) { + metadataBuilder.addDouble(entry.getKey(), (Double) value); + continue; + } else if (value instanceof Integer) { + metadataBuilder.addInteger(entry.getKey(), (Integer) value); + continue; + } else if (value instanceof Float) { + metadataBuilder.addFloat(entry.getKey(), (Float) value); + continue; + } + } else if (value instanceof Boolean) { + metadataBuilder.addBoolean(entry.getKey(), (Boolean) value); + continue; + } else if (value instanceof String) { + metadataBuilder.addString(entry.getKey(), (String) value); + continue; + } + throw new IllegalArgumentException("The type of the Metadata entry with key " + entry.getKey() + + " and value " + entry.getValue() + " is not supported"); + } - return this.metadata == null - ? evaluationBuilder.build() - : evaluationBuilder.flagMetadata(this.metadata).build(); + return metadataBuilder.build(); } } diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java index 9a3475aa8..0f615e266 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FeatureFlag.java @@ -23,18 +23,21 @@ public class FeatureFlag { private final String defaultVariant; private final Map variants; private final String targeting; + private final Map metadata; /** Construct a flagd feature flag. */ @JsonCreator - public FeatureFlag( - @JsonProperty("state") String state, - @JsonProperty("defaultVariant") String defaultVariant, - @JsonProperty("variants") Map variants, - @JsonProperty("targeting") @JsonDeserialize(using = StringSerializer.class) String targeting) { + public FeatureFlag(@JsonProperty("state") String state, + @JsonProperty("defaultVariant") String defaultVariant, + @JsonProperty("variants") Map variants, + @JsonProperty("targeting") @JsonDeserialize(using = StringSerializer.class) String targeting, + @JsonProperty("metadata") Map metadata + ) { this.state = state; this.defaultVariant = defaultVariant; this.variants = variants; this.targeting = targeting; + this.metadata = metadata; } /** Get targeting rule of the flag. */ diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java index 08c758c0b..362330ae5 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java @@ -10,12 +10,27 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.INT_FLAG; import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.OBJECT_FLAG; import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.VARIANT_MISMATCH_FLAG; +import static dev.openfeature.contrib.providers.flagd.resolver.process.MockFlags.stringVariants; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + import dev.openfeature.contrib.providers.flagd.Config; import dev.openfeature.contrib.providers.flagd.FlagdOptions; import dev.openfeature.contrib.providers.flagd.resolver.common.ConnectionEvent; @@ -35,406 +50,480 @@ import dev.openfeature.sdk.Value; import dev.openfeature.sdk.exceptions.ParseError; import dev.openfeature.sdk.exceptions.TypeMismatchError; -import java.lang.reflect.Field; -import java.time.Duration; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; class InProcessResolverTest { - @Test - public void connectorSetup() { - // given - FlagdOptions forGrpcOptions = FlagdOptions.builder() - .resolverType(Config.Resolver.IN_PROCESS) - .host("localhost") - .port(8080) - .build(); - FlagdOptions forOfflineOptions = FlagdOptions.builder() - .resolverType(Config.Resolver.IN_PROCESS) - .offlineFlagSourcePath("path") - .build(); - FlagdOptions forCustomConnectorOptions = FlagdOptions.builder() - .resolverType(Config.Resolver.IN_PROCESS) - .customConnector(new MockConnector(null)) - .build(); - - // then - assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions)); - assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions)); - assertInstanceOf(MockConnector.class, InProcessResolver.getConnector(forCustomConnectorOptions)); - } + @Test + public void connectorSetup() { + // given + FlagdOptions forGrpcOptions = FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS) + .host("localhost") + .port(8080).build(); + FlagdOptions forOfflineOptions = FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS) + .offlineFlagSourcePath("path").build(); + FlagdOptions forCustomConnectorOptions = FlagdOptions.builder().resolverType(Config.Resolver.IN_PROCESS) + .customConnector(new MockConnector(null)).build(); + + // then + assertInstanceOf(GrpcStreamConnector.class, InProcessResolver.getConnector(forGrpcOptions)); + assertInstanceOf(FileConnector.class, InProcessResolver.getConnector(forOfflineOptions)); + assertInstanceOf(MockConnector.class, InProcessResolver.getConnector(forCustomConnectorOptions)); + } - @Test - public void eventHandling() throws Throwable { - // given - // note - queues with adequate capacity - final BlockingQueue sender = new LinkedBlockingQueue<>(5); - final BlockingQueue receiver = new LinkedBlockingQueue<>(5); - final String key = "key1"; - final String val = "val1"; - final MutableStructure syncMetadata = new MutableStructure(); - syncMetadata.add(key, val); - - InProcessResolver inProcessResolver = getInProcessResolverWth( - new MockStorage(new HashMap<>(), sender), - (connectionEvent) -> receiver.offer(new StorageStateChange( - connectionEvent.isConnected() ? StorageState.OK : StorageState.ERROR, - connectionEvent.getFlagsChanged(), - connectionEvent.getSyncMetadata()))); - - // when - init and emit events - Thread initThread = new Thread(() -> { - try { - inProcessResolver.init(); - } catch (Exception e) { - } - }); - initThread.start(); - if (!sender.offer( - new StorageStateChange(StorageState.OK, Collections.emptyList(), syncMetadata), - 100, - TimeUnit.MILLISECONDS)) { - Assertions.fail("failed to send the event"); + @Test + public void eventHandling() throws Throwable { + // given + // note - queues with adequate capacity + final BlockingQueue sender = new LinkedBlockingQueue<>(5); + final BlockingQueue receiver = new LinkedBlockingQueue<>(5); + final String key = "key1"; + final String val = "val1"; + final MutableStructure syncMetadata = new MutableStructure(); + syncMetadata.add(key, val); + + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(new HashMap<>(), sender), + (connectionEvent) -> receiver.offer(new StorageStateChange( + connectionEvent.isConnected() ? StorageState.OK : StorageState.ERROR, + connectionEvent.getFlagsChanged(), connectionEvent.getSyncMetadata()))); + + // when - init and emit events + Thread initThread = new Thread(() -> { + try { + inProcessResolver.init(); + } catch (Exception e) { + } + }); + initThread.start(); + if (!sender.offer(new StorageStateChange(StorageState.OK, Collections.emptyList(), syncMetadata), 100, + TimeUnit.MILLISECONDS)) { + Assertions.fail("failed to send the event"); + } + if (!sender.offer(new StorageStateChange(StorageState.ERROR), 100, TimeUnit.MILLISECONDS)) { + Assertions.fail("failed to send the event"); + } + + // then - receive events in order + assertTimeoutPreemptively(Duration.ofMillis(200), () -> { + StorageStateChange storageState = receiver.take(); + assertEquals(StorageState.OK, storageState.getStorageState()); + assertEquals(val, storageState.getSyncMetadata().getValue(key).asString()); + }); + + assertTimeoutPreemptively(Duration.ofMillis(200), () -> { + assertEquals(StorageState.ERROR, receiver.take().getStorageState()); + }); } - if (!sender.offer(new StorageStateChange(StorageState.ERROR), 100, TimeUnit.MILLISECONDS)) { - Assertions.fail("failed to send the event"); + + @Test + public void simpleBooleanResolving() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("booleanFlag", BOOLEAN_FLAG); + + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.booleanEvaluation("booleanFlag", + false, + new ImmutableContext()); + + // then + assertEquals(true, providerEvaluation.getValue()); + assertEquals("on", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); } - // then - receive events in order - assertTimeoutPreemptively(Duration.ofMillis(200), () -> { - StorageStateChange storageState = receiver.take(); - assertEquals(StorageState.OK, storageState.getStorageState()); - assertEquals(val, storageState.getSyncMetadata().getValue(key).asString()); - }); + @Test + public void simpleDoubleResolving() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("doubleFlag", DOUBLE_FLAG); - assertTimeoutPreemptively(Duration.ofMillis(200), () -> { - assertEquals(StorageState.ERROR, receiver.take().getStorageState()); - }); - } + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - @Test - public void simpleBooleanResolving() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("booleanFlag", BOOLEAN_FLAG); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.doubleEvaluation("doubleFlag", 0d, + new ImmutableContext()); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + // then + assertEquals(3.141d, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.booleanEvaluation("booleanFlag", false, new ImmutableContext()); + @Test + public void fetchIntegerAsDouble() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("doubleFlag", DOUBLE_FLAG); - // then - assertEquals(true, providerEvaluation.getValue()); - assertEquals("on", providerEvaluation.getVariant()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - } + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - @Test - public void simpleDoubleResolving() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("doubleFlag", DOUBLE_FLAG); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation("doubleFlag", 0, + new ImmutableContext()); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + // then + assertEquals(3, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.doubleEvaluation("doubleFlag", 0d, new ImmutableContext()); + @Test + public void fetchDoubleAsInt() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("integerFlag", INT_FLAG); - // then - assertEquals(3.141d, providerEvaluation.getValue()); - assertEquals("one", providerEvaluation.getVariant()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - } + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - @Test - public void fetchIntegerAsDouble() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("doubleFlag", DOUBLE_FLAG); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.doubleEvaluation("integerFlag", 0d, + new ImmutableContext()); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + // then + assertEquals(1d, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.integerEvaluation("doubleFlag", 0, new ImmutableContext()); + @Test + public void simpleIntResolving() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("integerFlag", INT_FLAG); - // then - assertEquals(3, providerEvaluation.getValue()); - assertEquals("one", providerEvaluation.getVariant()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - } + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - @Test - public void fetchDoubleAsInt() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("integerFlag", INT_FLAG); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation("integerFlag", 0, + new ImmutableContext()); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + // then + assertEquals(1, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.doubleEvaluation("integerFlag", 0d, new ImmutableContext()); + @Test + public void simpleObjectResolving() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("objectFlag", OBJECT_FLAG); - // then - assertEquals(1d, providerEvaluation.getValue()); - assertEquals("one", providerEvaluation.getVariant()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - } + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - @Test - public void simpleIntResolving() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("integerFlag", INT_FLAG); + Map typeDefault = new HashMap<>(); + typeDefault.put("key", "0164"); + typeDefault.put("date", "01.01.1990"); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.objectEvaluation("objectFlag", + Value.objectToValue(typeDefault), new ImmutableContext()); - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.integerEvaluation("integerFlag", 0, new ImmutableContext()); + // then + Value value = providerEvaluation.getValue(); + Map valueMap = value.asStructure().asMap(); - // then - assertEquals(1, providerEvaluation.getValue()); - assertEquals("one", providerEvaluation.getVariant()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - } + assertEquals("0165", valueMap.get("key").asString()); + assertEquals("01.01.2000", valueMap.get("date").asString()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + assertEquals("typeA", providerEvaluation.getVariant()); + } - @Test - public void simpleObjectResolving() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("objectFlag", OBJECT_FLAG); + @Test + public void missingFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - Map typeDefault = new HashMap<>(); - typeDefault.put("key", "0164"); - typeDefault.put("date", "01.01.1990"); + // when/then + ProviderEvaluation missingFlag = inProcessResolver.booleanEvaluation("missingFlag", false, + new ImmutableContext()); + assertEquals(ErrorCode.FLAG_NOT_FOUND, missingFlag.getErrorCode()); + } - // when - ProviderEvaluation providerEvaluation = inProcessResolver.objectEvaluation( - "objectFlag", Value.objectToValue(typeDefault), new ImmutableContext()); + @Test + public void disabledFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("disabledFlag", DISABLED_FLAG); - // then - Value value = providerEvaluation.getValue(); - Map valueMap = value.asStructure().asMap(); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - assertEquals("0165", valueMap.get("key").asString()); - assertEquals("01.01.2000", valueMap.get("date").asString()); - assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); - assertEquals("typeA", providerEvaluation.getVariant()); - } + // when/then + ProviderEvaluation disabledFlag = inProcessResolver.booleanEvaluation("disabledFlag", false, + new ImmutableContext()); + assertEquals(ErrorCode.FLAG_NOT_FOUND, disabledFlag.getErrorCode()); + } - @Test - public void missingFlag() throws Exception { - // given - final Map flagMap = new HashMap<>(); + @Test + public void variantMismatchFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("mismatchFlag", VARIANT_MISMATCH_FLAG); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - // when/then - ProviderEvaluation missingFlag = - inProcessResolver.booleanEvaluation("missingFlag", false, new ImmutableContext()); - assertEquals(ErrorCode.FLAG_NOT_FOUND, missingFlag.getErrorCode()); - } + // when/then + assertThrows(TypeMismatchError.class, () -> { + inProcessResolver.booleanEvaluation("mismatchFlag", false, new ImmutableContext()); + }); + } - @Test - public void disabledFlag() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("disabledFlag", DISABLED_FLAG); + @Test + public void typeMismatchEvaluation() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("stringFlag", BOOLEAN_FLAG); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - // when/then - ProviderEvaluation disabledFlag = - inProcessResolver.booleanEvaluation("disabledFlag", false, new ImmutableContext()); - assertEquals(ErrorCode.FLAG_NOT_FOUND, disabledFlag.getErrorCode()); - } + // when/then + assertThrows(TypeMismatchError.class, () -> { + inProcessResolver.stringEvaluation("stringFlag", "false", new ImmutableContext()); + }); + } - @Test - public void variantMismatchFlag() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("mismatchFlag", VARIANT_MISMATCH_FLAG); + @Test + public void booleanShorthandEvaluation() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("shorthand", FLAG_WIH_SHORTHAND_TARGETING); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - // when/then - assertThrows(TypeMismatchError.class, () -> { - inProcessResolver.booleanEvaluation("mismatchFlag", false, new ImmutableContext()); - }); - } + ProviderEvaluation providerEvaluation = inProcessResolver.booleanEvaluation("shorthand", false, + new ImmutableContext()); - @Test - public void typeMismatchEvaluation() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("stringFlag", BOOLEAN_FLAG); + // then + assertEquals(true, providerEvaluation.getValue()); + assertEquals("true", providerEvaluation.getVariant()); + assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); + } - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + @Test + public void targetingMatchedEvaluationFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); + + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", + "loopAlg", + new MutableContext().add("email", "abc@faas.com")); + + // then + assertEquals("binetAlg", providerEvaluation.getValue()); + assertEquals("binet", providerEvaluation.getVariant()); + assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); + } - // when/then - assertThrows(TypeMismatchError.class, () -> { - inProcessResolver.stringEvaluation("stringFlag", "false", new ImmutableContext()); - }); - } + @Test + public void targetingUnmatchedEvaluationFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); + + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", + "loopAlg", + new MutableContext().add("email", "abc@abc.com")); + + // then + assertEquals("loopAlg", providerEvaluation.getValue()); + assertEquals("loop", providerEvaluation.getVariant()); + assertEquals(Reason.DEFAULT.toString(), providerEvaluation.getReason()); + } - @Test - public void booleanShorthandEvaluation() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("shorthand", FLAG_WIH_SHORTHAND_TARGETING); + @Test + public void explicitTargetingKeyHandling() throws NoSuchFieldException, IllegalAccessException { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("stringFlag", FLAG_WITH_TARGETING_KEY); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - ProviderEvaluation providerEvaluation = - inProcessResolver.booleanEvaluation("shorthand", false, new ImmutableContext()); + // when + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", "loop", + new MutableContext("xyz")); - // then - assertEquals(true, providerEvaluation.getValue()); - assertEquals("true", providerEvaluation.getVariant()); - assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); - } + // then + assertEquals("binetAlg", providerEvaluation.getValue()); + assertEquals("binet", providerEvaluation.getVariant()); + assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); + } - @Test - public void targetingMatchedEvaluationFlag() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); + @Test + public void targetingErrorEvaluationFlag() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("targetingErrorFlag", FLAG_WIH_INVALID_TARGET); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith(new MockStorage(flagMap), + (connectionEvent) -> { + }); - // when - ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation( - "stringFlag", "loopAlg", new MutableContext().add("email", "abc@faas.com")); + // when/then + assertThrows(ParseError.class, () -> { + inProcessResolver.booleanEvaluation("targetingErrorFlag", false, new ImmutableContext()); + }); + } - // then - assertEquals("binetAlg", providerEvaluation.getValue()); - assertEquals("binet", providerEvaluation.getVariant()); - assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); - } + @Test + public void validateMetadataInEvaluationResult() throws Exception { + // given + final String scope = "appName=myApp"; + final Map flagMap = new HashMap<>(); + flagMap.put("booleanFlag", BOOLEAN_FLAG); + + InProcessResolver inProcessResolver = getInProcessResolverWith( + FlagdOptions.builder().selector(scope).build(), + new MockStorage(flagMap)); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.booleanEvaluation("booleanFlag", + false, + new ImmutableContext()); + + // then + final ImmutableMetadata metadata = providerEvaluation.getFlagMetadata(); + assertNotNull(metadata); + assertEquals(scope, metadata.getString("scope")); + } @Test - public void targetingUnmatchedEvaluationFlag() throws Exception { + void selectorIsAddedToFlagMetadata() throws Exception { // given final Map flagMap = new HashMap<>(); - flagMap.put("stringFlag", FLAG_WIH_IF_IN_TARGET); + flagMap.put("flag", INT_FLAG); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + InProcessResolver inProcessResolver = getInProcessResolverWith( + new MockStorage(flagMap), + connectionEvent -> { + }, + "selector"); // when - ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation( - "stringFlag", "loopAlg", new MutableContext().add("email", "abc@abc.com")); + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation( + "flag", + 0, + new ImmutableContext() + ); // then - assertEquals("loopAlg", providerEvaluation.getValue()); - assertEquals("loop", providerEvaluation.getVariant()); - assertEquals(Reason.DEFAULT.toString(), providerEvaluation.getReason()); + assertThat(providerEvaluation.getFlagMetadata()).isNotNull(); + assertThat(providerEvaluation.getFlagMetadata().getString("scope")).isEqualTo("selector"); } @Test - public void explicitTargetingKeyHandling() throws NoSuchFieldException, IllegalAccessException { + void selectorIsOverwrittenByFlagMetadata() throws Exception { // given final Map flagMap = new HashMap<>(); - flagMap.put("stringFlag", FLAG_WITH_TARGETING_KEY); - - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); + final Map flagMetadata = new HashMap<>(); + flagMetadata.put("scope", "new selector"); + flagMap.put( + "flag", + new FeatureFlag( + "stage", + "loop", + stringVariants, + "", + flagMetadata + ) + ); + + InProcessResolver inProcessResolver = getInProcessResolverWith( + new MockStorage(flagMap), + connectionEvent -> { + }, + "selector"); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.stringEvaluation("stringFlag", "loop", new MutableContext("xyz")); + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation( + "flag", + "def", + new ImmutableContext() + ); // then - assertEquals("binetAlg", providerEvaluation.getValue()); - assertEquals("binet", providerEvaluation.getVariant()); - assertEquals(Reason.TARGETING_MATCH.toString(), providerEvaluation.getReason()); - } - - @Test - public void targetingErrorEvaluationFlag() throws Exception { - // given - final Map flagMap = new HashMap<>(); - flagMap.put("targetingErrorFlag", FLAG_WIH_INVALID_TARGET); - - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(flagMap), (connectionEvent) -> {}); - - // when/then - assertThrows(ParseError.class, () -> { - inProcessResolver.booleanEvaluation("targetingErrorFlag", false, new ImmutableContext()); - }); + assertThat(providerEvaluation.getFlagMetadata()).isNotNull(); + assertThat(providerEvaluation.getFlagMetadata().getString("scope")).isEqualTo("new selector"); } - @Test - public void validateMetadataInEvaluationResult() throws Exception { - // given - final String scope = "appName=myApp"; - final Map flagMap = new HashMap<>(); - flagMap.put("booleanFlag", BOOLEAN_FLAG); - - InProcessResolver inProcessResolver = - getInProcessResolverWth(FlagdOptions.builder().selector(scope).build(), new MockStorage(flagMap)); - - // when - ProviderEvaluation providerEvaluation = - inProcessResolver.booleanEvaluation("booleanFlag", false, new ImmutableContext()); + private InProcessResolver getInProcessResolverWith(final FlagdOptions options, final MockStorage storage) + throws NoSuchFieldException, IllegalAccessException { - // then - final ImmutableMetadata metadata = providerEvaluation.getFlagMetadata(); - assertNotNull(metadata); - assertEquals(scope, metadata.getString("scope")); + final InProcessResolver resolver = new InProcessResolver( + options, + () -> true, + connectionEvent -> { + } + ); + return injectFlagStore(resolver, storage); } - private InProcessResolver getInProcessResolverWth(final FlagdOptions options, final MockStorage storage) + private InProcessResolver getInProcessResolverWith(final MockStorage storage, + final Consumer onConnectionEvent) throws NoSuchFieldException, IllegalAccessException { - final InProcessResolver resolver = new InProcessResolver(options, () -> true, (connectionEvent) -> {}); + final InProcessResolver resolver = new InProcessResolver( + FlagdOptions.builder().deadline(1000).build(), () -> true, onConnectionEvent); return injectFlagStore(resolver, storage); } - private InProcessResolver getInProcessResolverWth( - final MockStorage storage, final Consumer onConnectionEvent) + private InProcessResolver getInProcessResolverWith(final MockStorage storage, + final Consumer onConnectionEvent, + String selector) throws NoSuchFieldException, IllegalAccessException { - final InProcessResolver resolver = - new InProcessResolver(FlagdOptions.builder().deadline(1000).build(), () -> true, onConnectionEvent); + final InProcessResolver resolver = new InProcessResolver( + FlagdOptions.builder().selector(selector).deadline(1000).build(), () -> true, onConnectionEvent); return injectFlagStore(resolver, storage); } - // helper to inject flagStore override - private InProcessResolver injectFlagStore(final InProcessResolver resolver, final MockStorage storage) - throws NoSuchFieldException, IllegalAccessException { + // helper to inject flagStore override + private InProcessResolver injectFlagStore(final InProcessResolver resolver, final MockStorage storage) + throws NoSuchFieldException, IllegalAccessException { - final Field flagStore = InProcessResolver.class.getDeclaredField("flagStore"); - flagStore.setAccessible(true); - flagStore.set(resolver, storage); + final Field flagStore = InProcessResolver.class.getDeclaredField("flagStore"); + flagStore.setAccessible(true); + flagStore.set(resolver, storage); + + return resolver; + } - return resolver; - } } diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java index fef135cc9..e81b1fb43 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/MockFlags.java @@ -48,44 +48,38 @@ public class MockFlags { } // correct flag - boolean - static final FeatureFlag BOOLEAN_FLAG = new FeatureFlag("ENABLED", "on", booleanVariant, null); + static final FeatureFlag BOOLEAN_FLAG = new FeatureFlag("ENABLED", "on", booleanVariant, null, new HashMap<>()); // correct flag - boolean - static final FeatureFlag SHORTHAND_FLAG = new FeatureFlag("ENABLED", "false", booleanVariant, null); + static final FeatureFlag SHORTHAND_FLAG = new FeatureFlag("ENABLED", "false", booleanVariant, null, new HashMap<>()); // correct flag - double - static final FeatureFlag DOUBLE_FLAG = new FeatureFlag("ENABLED", "one", doubleVariants, null); + static final FeatureFlag DOUBLE_FLAG = new FeatureFlag("ENABLED", "one", doubleVariants, null, new HashMap<>()); // correct flag - int - static final FeatureFlag INT_FLAG = new FeatureFlag("ENABLED", "one", intVariants, null); + static final FeatureFlag INT_FLAG = new FeatureFlag("ENABLED", "one", intVariants, null, new HashMap<>()); // correct flag - object - static final FeatureFlag OBJECT_FLAG = new FeatureFlag("ENABLED", "typeA", objectVariants, null); + static final FeatureFlag OBJECT_FLAG = new FeatureFlag("ENABLED", "typeA", objectVariants, null, new HashMap<>()); // flag in disabled state - static final FeatureFlag DISABLED_FLAG = new FeatureFlag("DISABLED", "on", booleanVariant, null); + static final FeatureFlag DISABLED_FLAG = new FeatureFlag("DISABLED", "on", booleanVariant, null, new HashMap<>()); // incorrect flag - variant mismatch - static final FeatureFlag VARIANT_MISMATCH_FLAG = new FeatureFlag("ENABLED", "true", stringVariants, null); + static final FeatureFlag VARIANT_MISMATCH_FLAG = new FeatureFlag("ENABLED", "true", stringVariants, null, new HashMap<>()); // flag with targeting rule - string - static final FeatureFlag FLAG_WIH_IF_IN_TARGET = new FeatureFlag( - "ENABLED", - "loop", - stringVariants, - "{\"if\":[{\"in\":[\"@faas.com\",{\"var\":[\"email\"]}]},\"binet\",null]}"); - - static final FeatureFlag FLAG_WITH_TARGETING_KEY = new FeatureFlag( - "ENABLED", - "loop", - stringVariants, - "{\"if\":[{\"==\":[{\"var\":\"targetingKey\"},\"xyz\"]},\"binet\",null]}"); + static final FeatureFlag FLAG_WIH_IF_IN_TARGET = new FeatureFlag("ENABLED", "loop", stringVariants, + "{\"if\":[{\"in\":[\"@faas.com\",{\"var\":[\"email\"]}]},\"binet\",null]}", new HashMap<>()); + + static final FeatureFlag FLAG_WITH_TARGETING_KEY = new FeatureFlag("ENABLED", "loop", stringVariants, + "{\"if\":[{\"==\":[{\"var\":\"targetingKey\"},\"xyz\"]},\"binet\",null]}", new HashMap<>()); // flag with incorrect targeting rule - static final FeatureFlag FLAG_WIH_INVALID_TARGET = - new FeatureFlag("ENABLED", "loop", stringVariants, "{if this, then that}"); + static final FeatureFlag FLAG_WIH_INVALID_TARGET = new FeatureFlag("ENABLED", "loop", stringVariants, + "{if this, then that}", new HashMap<>()); // flag with shorthand rule - static final FeatureFlag FLAG_WIH_SHORTHAND_TARGETING = - new FeatureFlag("ENABLED", "false", shorthandVariant, "{ \"if\": [true, true, false] }"); + static final FeatureFlag FLAG_WIH_SHORTHAND_TARGETING = new FeatureFlag("ENABLED", "false", shorthandVariant, + "{ \"if\": [true, true, false] }", new HashMap<>()); } diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java index c0b6795c8..a603ea266 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/TestUtils.java @@ -14,6 +14,7 @@ public class TestUtils { public static final String VALID_SIMPLE_EXTRA_FIELD = "flagConfigurations/valid-simple-with-extra-fields.json"; public static final String VALID_LONG = "flagConfigurations/valid-long.json"; public static final String INVALID_FLAG = "flagConfigurations/invalid-flag.json"; + public static final String INVALID_FLAG_METADATA = "flagConfigurations/invalid-metadata.json"; public static final String INVALID_CFG = "flagConfigurations/invalid-configuration.json"; public static final String UPDATABLE_FILE = "flagConfigurations/updatableFlags.json"; diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java index 30453a80e..8431e5b6e 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/model/FlagParserTest.java @@ -2,11 +2,13 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_CFG; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_FLAG; +import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.INVALID_FLAG_METADATA; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_LONG; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_SIMPLE; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.VALID_SIMPLE_EXTRA_FIELD; import static dev.openfeature.contrib.providers.flagd.resolver.process.TestUtils.getFlagsFromResource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -16,7 +18,7 @@ class FlagParserTest { @Test - public void validJsonConfigurationParsing() throws IOException { + void validJsonConfigurationParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_SIMPLE), true); FeatureFlag boolFlag = flagMap.get("myBoolFlag"); @@ -28,10 +30,21 @@ public void validJsonConfigurationParsing() throws IOException { assertEquals(true, variants.get("on")); assertEquals(false, variants.get("off")); + + Map metadata = boolFlag.getMetadata(); + + assertInstanceOf(String.class, metadata.get("string")); + assertEquals("string", metadata.get("string")); + + assertInstanceOf(Boolean.class, metadata.get("boolean")); + assertEquals(true, metadata.get("boolean")); + + assertInstanceOf(Double.class, metadata.get("float")); + assertEquals(1.234, metadata.get("float")); } @Test - public void validJsonConfigurationWithExtraFieldsParsing() throws IOException { + void validJsonConfigurationWithExtraFieldsParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_SIMPLE_EXTRA_FIELD), true); FeatureFlag boolFlag = flagMap.get("myBoolFlag"); @@ -46,7 +59,7 @@ public void validJsonConfigurationWithExtraFieldsParsing() throws IOException { } @Test - public void validJsonConfigurationWithTargetingRulesParsing() throws IOException { + void validJsonConfigurationWithTargetingRulesParsing() throws IOException { Map flagMap = FlagParser.parseString(getFlagsFromResource(VALID_LONG), true); FeatureFlag stringFlag = flagMap.get("fibAlgo"); @@ -66,16 +79,26 @@ public void validJsonConfigurationWithTargetingRulesParsing() throws IOException } @Test - public void invalidFlagThrowsError() { + void invalidFlagThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_FLAG); + assertThrows(IllegalArgumentException.class, () -> { + FlagParser.parseString(flagString, true); + }); + } + + @Test + void invalidFlagMetadataThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_FLAG_METADATA); assertThrows(IllegalArgumentException.class, () -> { - FlagParser.parseString(getFlagsFromResource(INVALID_FLAG), true); + FlagParser.parseString(flagString, true); }); } @Test - public void invalidConfigurationsThrowsError() { + void invalidConfigurationsThrowsError() throws IOException { + String flagString = getFlagsFromResource(INVALID_CFG); assertThrows(IllegalArgumentException.class, () -> { - FlagParser.parseString(getFlagsFromResource(INVALID_CFG), true); + FlagParser.parseString(flagString, true); }); } } diff --git a/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json b/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json new file mode 100644 index 000000000..299eac071 --- /dev/null +++ b/providers/flagd/src/test/resources/flagConfigurations/invalid-metadata.json @@ -0,0 +1,21 @@ +{ + "$schema": "../../../main/resources/flagd/schemas/flags.json", + "flags": { + "myBoolFlag": { + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234, + "invalid": { + "a": "a" + } + } + } + } +} diff --git a/providers/flagd/src/test/resources/flagConfigurations/valid-long.json b/providers/flagd/src/test/resources/flagConfigurations/valid-long.json index 142eec2cf..3aecd81c9 100644 --- a/providers/flagd/src/test/resources/flagConfigurations/valid-long.json +++ b/providers/flagd/src/test/resources/flagConfigurations/valid-long.json @@ -7,7 +7,12 @@ "on": true, "off": false }, - "defaultVariant": "on" + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234 + } }, "myStringFlag": { "state": "ENABLED", diff --git a/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json b/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json index 811abbc37..37d997f3b 100644 --- a/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json +++ b/providers/flagd/src/test/resources/flagConfigurations/valid-simple.json @@ -7,7 +7,12 @@ "on": true, "off": false }, - "defaultVariant": "on" + "defaultVariant": "on", + "metadata": { + "string": "string", + "boolean": true, + "float": 1.234 + } } } }