From 0df2cd0a92a710cb535162ef127086262af03798 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 30 Mar 2026 16:08:02 +0200 Subject: [PATCH 1/3] test(fractional): mirror nested fractional scenarios to evaluator gherkin suite Adds evaluator counterparts for the four @fractional-nested scenarios that were added to the SDK-level gherkin in #345 and subsequent commits but never mirrored to the evaluator suite: - Nested if expression as variant name (fractional-nested-if-flag) - Nested var expression as variant name (fractional-nested-var-flag) - Nested if expression as weight (fractional-nested-weight-flag) - Fractional operator used as a boolean condition (fractional-as-condition-flag) All scenarios are tagged @fractional-nested so evaluator implementations can opt out during migration with -t 'not @fractional-nested'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Schrottner --- evaluator/flags/testkit-flags.json | 48 +++++++++++++++++++++ evaluator/gherkin/fractional.feature | 63 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/evaluator/flags/testkit-flags.json b/evaluator/flags/testkit-flags.json index 7a0efc8..c53220d 100644 --- a/evaluator/flags/testkit-flags.json +++ b/evaluator/flags/testkit-flags.json @@ -386,6 +386,54 @@ null ] } + }, + "fractional-nested-if-flag": { + "state": "ENABLED", + "variants": { "premium": "premium", "standard": "standard", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + [{"if": [{"==": [{"var": "tier"}, "premium"]}, "premium", "standard"]}, 50], + ["standard", 50] + ] + } + }, + "fractional-nested-var-flag": { + "state": "ENABLED", + "variants": { "red": "red", "green": "green", "blue": "blue", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + [{"var": "color"}, 50], + ["blue", 50] + ] + } + }, + "fractional-nested-weight-flag": { + "state": "ENABLED", + "variants": { "red": "red", "blue": "blue", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + ["red", {"if": [{"==": [{"var": "tier"}, "premium"]}, 100, 0]}], + ["blue", 10] + ] + } + }, + "fractional-as-condition-flag": { + "state": "ENABLED", + "variants": { "big": "hundreds", "small": "ones", "fallback": "zero" }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + {"fractional": [["false", 0], ["true", 100]]}, + "big", + "small" + ] + } } }, "$evaluators": { diff --git a/evaluator/gherkin/fractional.feature b/evaluator/gherkin/fractional.feature index c71b7fd..8d52a15 100644 --- a/evaluator/gherkin/fractional.feature +++ b/evaluator/gherkin/fractional.feature @@ -110,3 +110,66 @@ Feature: Evaluator fractional operator | SI7p- | lower | | 6LvT0 | upper | | ceQdGm | upper | + + # Nested JSON Logic expressions as bucket variant names / weights. + # Requires evaluator implementations to support the @fractional-nested feature. + # Use -t "not @fractional-nested" to exclude during transition. + + @fractional-nested + Scenario Outline: Fractional operator with nested if expression as variant name + # bucket0=[if(tier=="premium","premium","standard"),50], bucket1=["standard",50] + # jon@company.com bv(100)=36 → bucket0; user1 bv(100)=76 → bucket1 + Given an evaluator + And a String-flag with key "fractional-nested-if-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "tier", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | tier | value | + | jon@company.com | premium | premium | + | jon@company.com | basic | standard | + | user1 | premium | standard | + | user1 | basic | standard | + + @fractional-nested + Scenario Outline: Fractional operator with nested var expression as variant name + # bucket0=[var("color"),50], bucket1=["blue",50] + # jon@company.com bv(100)=36 → bucket0 (resolves var "color"); user1 bv(100)=76 → bucket1 ("blue") + Given an evaluator + And a String-flag with key "fractional-nested-var-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "color", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | color | value | + | jon@company.com | red | red | + | jon@company.com | green | green | + | user1 | red | blue | + | jon@company.com | yellow | fallback | + | jon@company.com | | fallback | + + @fractional-nested + Scenario Outline: Fractional operator with nested if expression as weight + # bucket0=["red",if(tier=="premium",100,0)], bucket1=["blue",10] + Given an evaluator + And a String-flag with key "fractional-nested-weight-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "tier", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | tier | value | + | jon@company.com | premium | red | + | jon@company.com | basic | blue | + | user1 | premium | red | + | user1 | basic | blue | + + @fractional-nested + Scenario: Fractional as condition + Given an evaluator + And a String-flag with key "fractional-as-condition-flag" and a fallback value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "hundreds" From 2cc7278f7edee234b02d2a748c5e64ff401898e7 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 30 Mar 2026 16:18:41 +0200 Subject: [PATCH 2/3] fix(fractional): use boolean literals in condition flag, add false-path scenario Addresses Gemini review feedback: string 'false' is truthy in JSON Logic so the existing test never exercised the else-branch. Switch to actual boolean literals (false/true) and add a fractional-as-condition-false-flag that routes 100% to false, verifying the if-condition takes the else-branch ('small'/'ones'). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Schrottner --- evaluator/flags/testkit-flags.json | 14 +++++++++++++- evaluator/gherkin/fractional.feature | 8 ++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/evaluator/flags/testkit-flags.json b/evaluator/flags/testkit-flags.json index c53220d..ae722fc 100644 --- a/evaluator/flags/testkit-flags.json +++ b/evaluator/flags/testkit-flags.json @@ -429,7 +429,19 @@ "defaultVariant": "fallback", "targeting": { "if": [ - {"fractional": [["false", 0], ["true", 100]]}, + {"fractional": [[false, 0], [true, 100]]}, + "big", + "small" + ] + } + }, + "fractional-as-condition-false-flag": { + "state": "ENABLED", + "variants": { "big": "hundreds", "small": "ones", "fallback": "zero" }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + {"fractional": [[false, 100], [true, 0]]}, "big", "small" ] diff --git a/evaluator/gherkin/fractional.feature b/evaluator/gherkin/fractional.feature index 8d52a15..eb9ca7b 100644 --- a/evaluator/gherkin/fractional.feature +++ b/evaluator/gherkin/fractional.feature @@ -173,3 +173,11 @@ Feature: Evaluator fractional operator And a context containing a targeting key with value "some-targeting-key" When the flag was evaluated with details Then the resolved details value should be "hundreds" + + @fractional-nested + Scenario: Fractional as condition evaluates false path + Given an evaluator + And a String-flag with key "fractional-as-condition-false-flag" and a fallback value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "ones" From 851fa6a94dc589a5733fae2c7bb81fb78e958d67 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 30 Mar 2026 16:21:11 +0200 Subject: [PATCH 3/3] feat(fractional): add false-path condition scenario to SDK test suite Mirrors the fractional-as-condition-false-flag added to the evaluator test suite so both testing paths cover the else-branch of using fractional as a boolean condition. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Simon Schrottner --- flags/custom-ops.json | 20 ++++++++++++++++++++ gherkin/targeting.feature | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/flags/custom-ops.json b/flags/custom-ops.json index 3cc73e7..85f5f9a 100644 --- a/flags/custom-ops.json +++ b/flags/custom-ops.json @@ -186,6 +186,26 @@ ] } }, + "fractional-as-condition-false-flag": { + "state": "ENABLED", + "variants": { + "big": "hundreds", + "small": "ones", + "fallback": "zero" + }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + { + "fractional": [ + [ false, 100 ], + [ true, 0 ] + ] + }, + "big", "small" + ] + } + }, "starts-ends-flag": { "state": "ENABLED", "variants": { diff --git a/gherkin/targeting.feature b/gherkin/targeting.feature index f888904..5f7e2ba 100644 --- a/gherkin/targeting.feature +++ b/gherkin/targeting.feature @@ -193,6 +193,13 @@ Feature: Targeting rules When the flag was evaluated with details Then the resolved details value should be "hundreds" + @fractional @fractional-nested + Scenario: Fractional as condition evaluates false path + Given a String-flag with key "fractional-as-condition-false-flag" and a default value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "ones" + @string Scenario Outline: Substring operators Given a String-flag with key "starts-ends-flag" and a default value "fallback"