diff --git a/providers/flagd/schemas b/providers/flagd/schemas index 9f823b5b3..1daf5ff56 160000 --- a/providers/flagd/schemas +++ b/providers/flagd/schemas @@ -1 +1 @@ -Subproject commit 9f823b5b36bf219f8ea342de006fdf5013c1bc79 +Subproject commit 1daf5ff56b48d582187d59e35d48c6e191c23839 diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFileTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFileTest.java index edea71850..e5661e38c 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFileTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFileTest.java @@ -28,7 +28,17 @@ @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") @IncludeTags("file") -@ExcludeTags({"unixsocket", "targetURI", "reconnect", "customCert", "events", "contextEnrichment", "deprecated"}) +@ExcludeTags({ + "unixsocket", + "targetURI", + "reconnect", + "customCert", + "events", + "contextEnrichment", + "fractional-v1", + "deprecated", + "operator-errors" +}) @Testcontainers public class RunFileTest { diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java index 385d4e83c..475d377f4 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunInProcessTest.java @@ -28,7 +28,7 @@ @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") @IncludeTags("in-process") -@ExcludeTags({"unixsocket", "deprecated"}) +@ExcludeTags({"unixsocket", "fractional-v1", "deprecated", "operator-errors"}) @Testcontainers public class RunInProcessTest { diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java index d98fb5986..491e8dd7e 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunRpcTest.java @@ -28,7 +28,7 @@ @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps") @ConfigurationParameter(key = OBJECT_FACTORY_PROPERTY_NAME, value = "io.cucumber.picocontainer.PicoFactory") @IncludeTags({"rpc"}) -@ExcludeTags({"unixsocket", "deprecated"}) +@ExcludeTags({"unixsocket", "fractional-v1", "deprecated", "operator-errors"}) @Testcontainers public class RunRpcTest { diff --git a/providers/flagd/test-harness b/providers/flagd/test-harness index 3bff4b7ea..ff2fbe6c6 160000 --- a/providers/flagd/test-harness +++ b/providers/flagd/test-harness @@ -1 +1 @@ -Subproject commit 3bff4b7eaee0efc8cfe60e0ef6fbd77441b370e6 +Subproject commit ff2fbe6c6584953cb2753ae9188d1cee14f7f57f diff --git a/tools/flagd-api-testkit/test-harness b/tools/flagd-api-testkit/test-harness index ad336ebc7..854e80122 160000 --- a/tools/flagd-api-testkit/test-harness +++ b/tools/flagd-api-testkit/test-harness @@ -1 +1 @@ -Subproject commit ad336ebc7cad91ee028a72dec5f60a17a17316b1 +Subproject commit 854e801228317a6a2eb61aca045e1e77325302b1 diff --git a/tools/flagd-core/schemas b/tools/flagd-core/schemas index 9f823b5b3..1daf5ff56 160000 --- a/tools/flagd-core/schemas +++ b/tools/flagd-core/schemas @@ -1 +1 @@ -Subproject commit 9f823b5b36bf219f8ea342de006fdf5013c1bc79 +Subproject commit 1daf5ff56b48d582187d59e35d48c6e191c23839 diff --git a/tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/targeting/Fractional.java b/tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/targeting/Fractional.java index f36ebfffe..156d00e8a 100644 --- a/tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/targeting/Fractional.java +++ b/tools/flagd-core/src/main/java/dev/openfeature/contrib/tools/flagd/core/targeting/Fractional.java @@ -17,6 +17,8 @@ @Slf4j class Fractional implements PreEvaluatedArgumentsExpression { + static final int MAX_WEIGHT = Integer.MAX_VALUE; + @Override public String key() { return "fractional"; @@ -24,7 +26,7 @@ public String key() { @Override public Object evaluate(List arguments, Object data, String jsonPath) throws JsonLogicEvaluationException { - if (arguments.size() < 2) { + if (arguments.size() < 1) { return null; } @@ -39,7 +41,6 @@ public Object evaluate(List arguments, Object data, String jsonPath) throws Json if (arg1 instanceof String) { // first arg is a String, use for bucketing bucketBy = (String) arg1; - Object[] source = arguments.toArray(); distributions = Arrays.copyOfRange(source, 1, source.length); } else { @@ -54,7 +55,7 @@ public Object evaluate(List arguments, Object data, String jsonPath) throws Json } final List propertyList = new ArrayList<>(); - int totalWeight = 0; + long totalWeight = 0; try { for (Object dist : distributions) { @@ -67,20 +68,40 @@ public Object evaluate(List arguments, Object data, String jsonPath) throws Json return null; } + if (totalWeight > MAX_WEIGHT) { + log.debug("Total weight {} exceeds maximum allowed value {}", totalWeight, MAX_WEIGHT); + return null; + } + + if (totalWeight == 0) { + log.debug("Total weight is 0, no valid distribution possible"); + return null; + } + // find distribution - return distributeValue(bucketBy, propertyList, totalWeight, jsonPath); + return distributeValue(bucketBy, propertyList, (int) totalWeight, jsonPath); } - private static String distributeValue( - final String hashKey, final List propertyList, int totalWeight, String jsonPath) + private static Object distributeValue( + final String hashKey, + final List propertyList, + final int totalWeight, + final String jsonPath) throws JsonLogicEvaluationException { byte[] bytes = hashKey.getBytes(StandardCharsets.UTF_8); int mmrHash = MurmurHash3.hash32x86(bytes, 0, bytes.length, 0); - float bucket = Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE * 100; + return distributeValueFromHash(mmrHash, propertyList, totalWeight, jsonPath); + } - float bucketSum = 0; + static Object distributeValueFromHash( + final int hash, final List propertyList, final int totalWeight, final String jsonPath) + throws JsonLogicEvaluationException { + long longHash = Integer.toUnsignedLong(hash); + int bucket = (int) ((longHash * totalWeight) >>> 32); + + int bucketSum = 0; for (FractionProperty p : propertyList) { - bucketSum += p.getPercentage(totalWeight); + bucketSum += p.weight; if (bucket < bucketSum) { return p.getVariant(); @@ -88,13 +109,13 @@ private static String distributeValue( } // this shall not be reached - throw new JsonLogicEvaluationException("Unable to find a correct bucket", jsonPath); + throw new JsonLogicEvaluationException("Unable to find a correct bucket for hash " + hash, jsonPath); } @Getter @SuppressWarnings({"checkstyle:NoFinalizer"}) - private static class FractionProperty { - private final String variant; + static class FractionProperty { + private final Object variant; private final int weight; protected final void finalize() { @@ -112,29 +133,38 @@ protected final void finalize() { throw new JsonLogicException("Fraction property needs at least one element", jsonPath); } - // first must be a string - if (!(array.get(0) instanceof String)) { + // variant must be a primitive (string, number, boolean) or null; + // nested JSONLogic expressions are pre-evaluated to these types + Object first = array.get(0); + if (first instanceof String || first instanceof Number || first instanceof Boolean || first == null) { + variant = first; + } else { throw new JsonLogicException( - "First element of the fraction property is not a string variant", jsonPath); + "First element of the fraction property must resolve to a string, number, boolean, or null", + jsonPath); } - variant = (String) array.get(0); if (array.size() >= 2) { - // second element must be a number + // weight must be a number if (!(array.get(1) instanceof Number)) { throw new JsonLogicException("Second element of the fraction property is not a number", jsonPath); } - weight = ((Number) array.get(1)).intValue(); + Number rawWeight = (Number) array.get(1); + + // weights must be integers + double weightDouble = rawWeight.doubleValue(); + if (Double.isInfinite(weightDouble) + || Double.isNaN(weightDouble) + || weightDouble != Math.floor(weightDouble)) { + throw new JsonLogicException("Weights must be integers", jsonPath); + } + + // negative weights can be the result of rollout calculations, + // so we clamp to 0 rather than throwing an error + weight = Math.max(0, (int) weightDouble); } else { weight = 1; } } - - float getPercentage(int totalWeight) { - if (weight == 0) { - return 0; - } - return (float) (weight * 100) / totalWeight; - } } } diff --git a/tools/flagd-core/src/main/resources/flagd/schemas/targeting.json b/tools/flagd-core/src/main/resources/flagd/schemas/targeting.json index 4409c3e37..f8dd7544f 100644 --- a/tools/flagd-core/src/main/resources/flagd/schemas/targeting.json +++ b/tools/flagd-core/src/main/resources/flagd/schemas/targeting.json @@ -35,7 +35,7 @@ "type": "string" }, { - "description": "When returned from rules, strings are used to as keys to retrieve the associated value from the \"variants\" object. Be sure that the returned string is present as a key in the variants!.", + "description": "When returned from rules, the behavior of arrays is not defined.", "type": "array" } ] @@ -461,18 +461,26 @@ "maxItems": 2, "items": [ { - "description": "If this bucket is randomly selected, this string is used to as a key to retrieve the associated value from the \"variants\" object.", - "type": "string" + "description": "If this bucket is randomly selected, this JSONLogic will be evaluated, and the result will be used as the variant key to return from the variants map.", + "$ref": "#/definitions/args" }, { - "description": "Weighted distribution for this variant key.", - "type": "number" + "description": "Weighted distribution for this variant key. Must be a non-negative integer. Can be a JSONLogic expression that evaluates to a number (e.g. for time-based progressive rollouts); computed negative weights are clamped to 0 at evaluation time. The total weight sum across all variants must not exceed 2,147,483,647.", + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/anyRule" + } + ] } ] }, "fractionalOp": { "type": "array", - "minItems": 3, + "minItems": 1, "$comment": "there seems to be a bug here, where ajv gives a warning (not an error) because maxItems doesn't equal the number of entries in items, though this is valid in this case", "items": [ { @@ -492,7 +500,7 @@ }, "fractionalShorthandOp": { "type": "array", - "minItems": 2, + "minItems": 1, "items": { "$ref": "#/definitions/fractionalWeightArg" } diff --git a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/e2e/FlagdCoreEvaluatorTest.java b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/e2e/FlagdCoreEvaluatorTest.java index b341ad962..a7d2ecc12 100644 --- a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/e2e/FlagdCoreEvaluatorTest.java +++ b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/e2e/FlagdCoreEvaluatorTest.java @@ -3,6 +3,7 @@ import dev.openfeature.contrib.tools.flagd.api.Evaluator; import dev.openfeature.contrib.tools.flagd.api.testkit.AbstractEvaluatorTest; import dev.openfeature.contrib.tools.flagd.core.FlagdCore; +import org.junit.platform.suite.api.ExcludeTags; /** * Compliance test suite for {@link FlagdCore} — the reference implementation of the @@ -10,6 +11,7 @@ * configuration. Registered as an {@link dev.openfeature.contrib.tools.flagd.api.testkit.EvaluatorFactory} * via {@code META-INF/services}. */ +@ExcludeTags({"fractional-v1"}) public class FlagdCoreEvaluatorTest extends AbstractEvaluatorTest { @Override diff --git a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/FractionalTest.java b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/FractionalTest.java index 897be5219..6bdd45b29 100644 --- a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/FractionalTest.java +++ b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/FractionalTest.java @@ -1,6 +1,8 @@ package dev.openfeature.contrib.tools.flagd.core.targeting; -import static dev.openfeature.contrib.tools.flagd.core.targeting.Operator.*; +import static dev.openfeature.contrib.tools.flagd.core.targeting.Operator.FLAGD_PROPS_KEY; +import static dev.openfeature.contrib.tools.flagd.core.targeting.Operator.FLAG_KEY; +import static dev.openfeature.contrib.tools.flagd.core.targeting.Operator.TARGET_KEY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.provider.Arguments.arguments; diff --git a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/OperatorTest.java b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/OperatorTest.java index cbd28a9e9..888b8ef03 100644 --- a/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/OperatorTest.java +++ b/tools/flagd-core/src/test/java/dev/openfeature/contrib/tools/flagd/core/targeting/OperatorTest.java @@ -75,7 +75,7 @@ void testFlagPropertiesConstructor() { } @Test - void fractionalTestA() throws TargetingRuleException { + void fractionalTestB() throws TargetingRuleException { // given // fractional rule with email as expression key @@ -112,11 +112,11 @@ void fractionalTestA() throws TargetingRuleException { Object evalVariant = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); // then - assertEquals("yellow", evalVariant); + assertEquals("blue", evalVariant); } @Test - void fractionalTestB() throws TargetingRuleException { + void fractionalTestA() throws TargetingRuleException { // given // fractional rule with email as expression key @@ -153,7 +153,7 @@ void fractionalTestB() throws TargetingRuleException { Object evalVariant = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); // then - assertEquals("blue", evalVariant); + assertEquals("yellow", evalVariant); } @Test @@ -243,6 +243,177 @@ void stringCompEndsWith() throws TargetingRuleException { assertEquals(true, evalVariant); } + @Test + void nestedIfAsVariant() throws TargetingRuleException { + // fractional with a nested "if" expression producing the variant string + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " {\"if\": [{\"==\": [{\"var\": \"tier\"}, \"premium\"]}, \"gold\", \"silver\"]},\n" + + " 50\n" + + " ],\n" + + " [\n" + + " \"bronze\",\n" + + " 50\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("tier", new Value("premium")); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // the "if" resolves to "gold" because tier == premium; + // bucket key = "headerColorrachel@faas.com", same as fractionalTestB + // with 50/50 split between "gold" and "bronze", bucket determines the result + assertTrue(result.equals("gold") || result.equals("bronze"), "Expected 'gold' or 'bronze', got: " + result); + } + + @Test + void nestedIfAsVariantNonPremium() throws TargetingRuleException { + // same as above but tier != premium, so "if" resolves to "silver" + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " {\"if\": [{\"==\": [{\"var\": \"tier\"}, \"premium\"]}, \"gold\", \"silver\"]},\n" + + " 50\n" + + " ],\n" + + " [\n" + + " \"bronze\",\n" + + " 50\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("tier", new Value("basic")); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // the "if" resolves to "silver" because tier != premium + assertTrue(result.equals("silver") || result.equals("bronze"), "Expected 'silver' or 'bronze', got: " + result); + } + + @Test + void nestedVarAsVariant() throws TargetingRuleException { + // variant name pulled from context via {"var": "color"} + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " {\"var\": \"color\"},\n" + + " 100\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("color", new Value("magenta")); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // single bucket with weight 100, variant resolved from var to "magenta" + assertEquals("magenta", result); + } + + @Test + void nestedVarAsWeight() throws TargetingRuleException { + // weight pulled from context via {"var": "rollout"} + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " \"red\",\n" + + " {\"var\": \"rollout\"}\n" + + " ],\n" + + " [\n" + + " \"blue\",\n" + + " {\"var\": \"remaining\"}\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("rollout", new Value(75)); + ctxData.put("remaining", new Value(25)); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // weights resolved from context: 75/25 split + assertTrue(result.equals("red") || result.equals("blue"), "Expected 'red' or 'blue', got: " + result); + } + + @Test + void nestedBooleanVariant() throws TargetingRuleException { + // boolean variants resolved via nested "if" + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " {\"if\": [{\"==\": [{\"var\": \"tier\"}, \"premium\"]}, true, false]},\n" + + " 100\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("tier", new Value("premium")); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // single bucket, variant resolves to true + assertEquals(true, result); + } + + @Test + void nestedNumericVariant() throws TargetingRuleException { + // numeric variant resolved via arithmetic expression + final String targetingRule = "{\n" + + " \"fractional\": [\n" + + " {\"cat\":[\n" + + " {\"var\":\"$flagd.flagKey\"},\n" + + " {\"var\": \"email\"}\n" + + " ]},\n" + + " [\n" + + " {\"+\": [{\"var\": \"base\"}, 1]},\n" + + " 100\n" + + " ]\n" + + " ]\n" + + "}"; + + Map ctxData = new HashMap<>(); + ctxData.put("email", new Value("rachel@faas.com")); + ctxData.put("base", new Value(41)); + + Object result = OPERATOR.apply("headerColor", targetingRule, new ImmutableContext(ctxData)); + + // single bucket, variant resolves to 42.0 (jsonlogic arithmetic returns double) + assertEquals(42.0, result); + } + @Test void semVerA() throws TargetingRuleException { // given diff --git a/tools/flagd-core/src/test/resources/fractional/allZeroWeights.json b/tools/flagd-core/src/test/resources/fractional/allZeroWeights.json new file mode 100644 index 000000000..5149d1fa4 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/allZeroWeights.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + "on", + -10 + ], + [ + "off", + -20 + ] + ], + "result": null +} diff --git a/tools/flagd-core/src/test/resources/fractional/booleanVariants.json b/tools/flagd-core/src/test/resources/fractional/booleanVariants.json new file mode 100644 index 000000000..a742f8961 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/booleanVariants.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + true, + 50 + ], + [ + false, + 50 + ] + ], + "result": true +} diff --git a/tools/flagd-core/src/test/resources/fractional/floatWeightRejected.json b/tools/flagd-core/src/test/resources/fractional/floatWeightRejected.json new file mode 100644 index 000000000..44c28a0f5 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/floatWeightRejected.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + "on", + 0.5 + ], + [ + "off", + 0.5 + ] + ], + "result": null +} diff --git a/tools/flagd-core/src/test/resources/fractional/negativeWeightClamped.json b/tools/flagd-core/src/test/resources/fractional/negativeWeightClamped.json new file mode 100644 index 000000000..c24edfb2f --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/negativeWeightClamped.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + "on", + -1000 + ], + [ + "off", + 1 + ] + ], + "result": "off" +} diff --git a/tools/flagd-core/src/test/resources/fractional/notEnoughBuckets.json b/tools/flagd-core/src/test/resources/fractional/notEnoughBuckets.json index b29de7a83..56c453201 100644 --- a/tools/flagd-core/src/test/resources/fractional/notEnoughBuckets.json +++ b/tools/flagd-core/src/test/resources/fractional/notEnoughBuckets.json @@ -5,5 +5,5 @@ 50 ] ], - "result": null + "result": "blue" } diff --git a/tools/flagd-core/src/test/resources/fractional/nullVariant.json b/tools/flagd-core/src/test/resources/fractional/nullVariant.json new file mode 100644 index 000000000..8b9af9a23 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/nullVariant.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + null, + 50 + ], + [ + "blue", + 50 + ] + ], + "result": null +} diff --git a/tools/flagd-core/src/test/resources/fractional/selfContainedFractionalB.json b/tools/flagd-core/src/test/resources/fractional/selfContainedFractionalB.json index 2beb7e5be..e632f17d9 100644 --- a/tools/flagd-core/src/test/resources/fractional/selfContainedFractionalB.json +++ b/tools/flagd-core/src/test/resources/fractional/selfContainedFractionalB.json @@ -10,5 +10,5 @@ 50 ] ], - "result": "blue" + "result": "red" } diff --git a/tools/flagd-core/src/test/resources/fractional/string.json b/tools/flagd-core/src/test/resources/fractional/string.json new file mode 100644 index 000000000..8c55b68c1 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/string.json @@ -0,0 +1,14 @@ +{ + "rule": [ + "some string", + [ + "blue", + 50 + ], + [ + "green", + 70 + ] + ], + "result": "blue" +} diff --git a/tools/flagd-core/src/test/resources/fractional/weightOverflow.json b/tools/flagd-core/src/test/resources/fractional/weightOverflow.json new file mode 100644 index 000000000..fca19a3a9 --- /dev/null +++ b/tools/flagd-core/src/test/resources/fractional/weightOverflow.json @@ -0,0 +1,13 @@ +{ + "rule": [ + [ + "on", + 2000000000 + ], + [ + "off", + 2000000000 + ] + ], + "result": null +}