Skip to content

Commit 0815d19

Browse files
aepfliCopilottoddbaert
authored
feat(operators): add follow-up error scenarios for operator null-return behaviour (#356)
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com> Signed-off-by: Todd Baert <todd.baert@dynatrace.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
1 parent 0707f4b commit 0815d19

6 files changed

Lines changed: 340 additions & 20 deletions

File tree

evaluator/flags/testkit-flags.json

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -462,29 +462,38 @@
462462
},
463463
"semver-invalid-version-flag": {
464464
"state": "ENABLED",
465-
"variants": { "match": "match", "no-match": "no-match", "fallback": "fallback" },
465+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
466466
"defaultVariant": "fallback",
467467
"targeting": {
468-
"sem_ver": [{"var": "version"}, "=", "1.0.0"]
468+
"if": [
469+
{"sem_ver": [{"var": "version"}, "=", "1.0.0"]},
470+
"true", "false"
471+
]
469472
}
470473
},
471474
"semver-invalid-operator-flag": {
472475
"state": "ENABLED",
473-
"variants": { "match": "match", "no-match": "no-match", "fallback": "fallback" },
476+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
474477
"defaultVariant": "fallback",
475478
"targeting": {
476-
"sem_ver": [{"var": "version"}, "===", "1.0.0"]
479+
"if": [
480+
{"sem_ver": [{"var": "version"}, "===", "1.0.0"]},
481+
"true", "false"
482+
]
477483
}
478484
},
479485
"fractional-null-bucket-key-flag": {
480486
"state": "ENABLED",
481-
"variants": { "one": "one", "two": "two", "fallback": "fallback" },
487+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
482488
"defaultVariant": "fallback",
483489
"targeting": {
484-
"fractional": [
485-
{"var": "missing_key"},
486-
["one", 50],
487-
["two", 50]
490+
"if": [
491+
{"fractional": [
492+
{"var": "missing_key"},
493+
["one", 50],
494+
["two", 50]
495+
]},
496+
"true", "false"
488497
]
489498
}
490499
},
@@ -583,6 +592,88 @@
583592
"miss"
584593
]
585594
}
595+
},
596+
"starts-with-non-string-flag": {
597+
"state": "ENABLED",
598+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
599+
"defaultVariant": "fallback",
600+
"targeting": {
601+
"if": [
602+
{"starts_with": [{"var": "num"}, "abc"]},
603+
"true", "false"
604+
]
605+
}
606+
},
607+
"ends-with-non-string-flag": {
608+
"state": "ENABLED",
609+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
610+
"defaultVariant": "fallback",
611+
"targeting": {
612+
"if": [
613+
{"ends_with": [{"var": "num"}, "xyz"]},
614+
"true", "false"
615+
]
616+
}
617+
},
618+
"starts-with-wrong-args-flag": {
619+
"state": "ENABLED",
620+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
621+
"defaultVariant": "fallback",
622+
"targeting": {
623+
"if": [
624+
{"starts_with": ["abc"]},
625+
"true", "false"
626+
]
627+
}
628+
},
629+
"ends-with-wrong-args-flag": {
630+
"state": "ENABLED",
631+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
632+
"defaultVariant": "fallback",
633+
"targeting": {
634+
"if": [
635+
{"ends_with": ["xyz"]},
636+
"true", "false"
637+
]
638+
}
639+
},
640+
"fractional-zero-weights-flag": {
641+
"state": "ENABLED",
642+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
643+
"defaultVariant": "fallback",
644+
"targeting": {
645+
"if": [
646+
{"fractional": [
647+
{"var": "targetingKey"},
648+
["one", 0],
649+
["two", 0]
650+
]},
651+
"true", "false"
652+
]
653+
}
654+
},
655+
"fractional-negative-weight-flag": {
656+
"state": "ENABLED",
657+
"variants": { "one": "one", "two": "two", "fallback": "fallback" },
658+
"defaultVariant": "fallback",
659+
"targeting": {
660+
"fractional": [
661+
{"var": "targetingKey"},
662+
["one", -50],
663+
["two", 100]
664+
]
665+
}
666+
},
667+
"semver-wrong-args-flag": {
668+
"state": "ENABLED",
669+
"variants": { "true": "true", "false": "false", "fallback": "fallback" },
670+
"defaultVariant": "fallback",
671+
"targeting": {
672+
"if": [
673+
{"sem_ver": [{"var": "version"}, "="]},
674+
"true", "false"
675+
]
676+
}
586677
}
587678
},
588679
"$evaluators": {

evaluator/gherkin/fractional.feature

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,24 @@ Feature: Evaluator fractional operator
195195
And a String-flag with key "fractional-null-bucket-key-flag" and a fallback value "wrong"
196196
When the flag was evaluated with details
197197
Then the resolved details value should be "fallback"
198+
And the reason should be "DEFAULT"
199+
200+
# Follow-up error scenarios from https://github.com/open-feature/flagd/issues/1874
201+
202+
@operator-errors
203+
Scenario: fractional with all-zero bucket weights falls back to default variant
204+
Given an evaluator
205+
And a String-flag with key "fractional-zero-weights-flag" and a fallback value "wrong"
206+
And a context containing a targeting key with value "any-user"
207+
When the flag was evaluated with details
208+
Then the resolved details value should be "fallback"
209+
And the reason should be "DEFAULT"
210+
211+
@operator-errors
212+
Scenario: fractional negative bucket weight is clamped to zero
213+
# ["one", -50] is treated as ["one", 0]; "two" gets 100% of the weight
214+
Given an evaluator
215+
And a String-flag with key "fractional-negative-weight-flag" and a fallback value "wrong"
216+
And a context containing a targeting key with value "any-user"
217+
When the flag was evaluated with details
218+
Then the resolved details value should be "two"

evaluator/gherkin/semver.feature

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Feature: Evaluator semantic version operator
3939
And a context containing a key "version", with type "String" and with value "<context_value>"
4040
When the flag was evaluated with details
4141
Then the resolved details value should be "fallback"
42+
And the reason should be "DEFAULT"
4243
Examples:
4344
| key | context_value |
4445
| semver-invalid-version-flag | not-a-version |
@@ -86,3 +87,14 @@ Feature: Evaluator semantic version operator
8687
| 1.0.0 | match |
8788
| 1.0.0+other | match |
8889
| 2.0.0 | no-match |
90+
91+
# Follow-up error scenarios from https://github.com/open-feature/flagd/issues/1874
92+
93+
@operator-errors
94+
Scenario: sem_ver returns null for wrong argument count
95+
Given an evaluator
96+
And a String-flag with key "semver-wrong-args-flag" and a fallback value "wrong"
97+
And a context containing a key "version", with type "String" and with value "1.0.0"
98+
When the flag was evaluated with details
99+
Then the resolved details value should be "fallback"
100+
And the reason should be "DEFAULT"

evaluator/gherkin/string.feature

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,32 @@ Feature: Evaluator string comparison operator
1616
| uvwxyz | postfix |
1717
| abcxyz | prefix |
1818
| lmnopq | none |
19+
20+
# Follow-up error scenarios from https://github.com/open-feature/flagd/issues/1874
21+
# starts_with and ends_with must return null (not false) on error so that the
22+
# default variant is selected, rather than looking up a non-existent "false" variant.
23+
24+
@operator-errors
25+
Scenario Outline: starts_with and ends_with return null for non-string input
26+
Given an evaluator
27+
And a String-flag with key "<key>" and a fallback value "wrong"
28+
And a context containing a key "num", with type "Integer" and with value "123"
29+
When the flag was evaluated with details
30+
Then the resolved details value should be "fallback"
31+
And the reason should be "DEFAULT"
32+
Examples:
33+
| key |
34+
| starts-with-non-string-flag |
35+
| ends-with-non-string-flag |
36+
37+
@operator-errors
38+
Scenario Outline: starts_with and ends_with return null for wrong argument count
39+
Given an evaluator
40+
And a String-flag with key "<key>" and a fallback value "wrong"
41+
When the flag was evaluated with details
42+
Then the resolved details value should be "fallback"
43+
And the reason should be "DEFAULT"
44+
Examples:
45+
| key |
46+
| starts-with-wrong-args-flag |
47+
| ends-with-wrong-args-flag |

flags/edge-case-flags.json

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,40 +56,155 @@
5656
"semver-invalid-version-flag": {
5757
"state": "ENABLED",
5858
"variants": {
59-
"match": "match",
60-
"no-match": "no-match",
59+
"true": "true",
60+
"false": "false",
6161
"fallback": "fallback"
6262
},
6363
"defaultVariant": "fallback",
6464
"targeting": {
65-
"sem_ver": [{"var": "version"}, "=", "1.0.0"]
65+
"if": [
66+
{"sem_ver": [{"var": "version"}, "=", "1.0.0"]},
67+
"true", "false"
68+
]
6669
}
6770
},
6871
"semver-invalid-operator-flag": {
6972
"state": "ENABLED",
7073
"variants": {
71-
"match": "match",
72-
"no-match": "no-match",
74+
"true": "true",
75+
"false": "false",
7376
"fallback": "fallback"
7477
},
7578
"defaultVariant": "fallback",
7679
"targeting": {
77-
"sem_ver": [{"var": "version"}, "===", "1.0.0"]
80+
"if": [
81+
{"sem_ver": [{"var": "version"}, "===", "1.0.0"]},
82+
"true", "false"
83+
]
7884
}
7985
},
8086
"fractional-null-bucket-key-flag": {
8187
"state": "ENABLED",
8288
"variants": {
83-
"one": "one",
84-
"two": "two",
89+
"true": "true",
90+
"false": "false",
91+
"fallback": "fallback"
92+
},
93+
"defaultVariant": "fallback",
94+
"targeting": {
95+
"if": [
96+
{"fractional": [
97+
{"var": "missing_key"},
98+
["one", 50],
99+
["two", 50]
100+
]},
101+
"true", "false"
102+
]
103+
}
104+
},
105+
"starts-with-non-string-flag": {
106+
"state": "ENABLED",
107+
"variants": {
108+
"true": "true",
109+
"false": "false",
110+
"fallback": "fallback"
111+
},
112+
"defaultVariant": "fallback",
113+
"targeting": {
114+
"if": [
115+
{"starts_with": [{"var": "num"}, "abc"]},
116+
"true", "false"
117+
]
118+
}
119+
},
120+
"ends-with-non-string-flag": {
121+
"state": "ENABLED",
122+
"variants": {
123+
"true": "true",
124+
"false": "false",
125+
"fallback": "fallback"
126+
},
127+
"defaultVariant": "fallback",
128+
"targeting": {
129+
"if": [
130+
{"ends_with": [{"var": "num"}, "xyz"]},
131+
"true", "false"
132+
]
133+
}
134+
},
135+
"starts-with-wrong-args-flag": {
136+
"state": "ENABLED",
137+
"variants": {
138+
"true": "true",
139+
"false": "false",
140+
"fallback": "fallback"
141+
},
142+
"defaultVariant": "fallback",
143+
"targeting": {
144+
"if": [
145+
{"starts_with": ["abc"]},
146+
"true", "false"
147+
]
148+
}
149+
},
150+
"ends-with-wrong-args-flag": {
151+
"state": "ENABLED",
152+
"variants": {
153+
"true": "true",
154+
"false": "false",
155+
"fallback": "fallback"
156+
},
157+
"defaultVariant": "fallback",
158+
"targeting": {
159+
"if": [
160+
{"ends_with": ["xyz"]},
161+
"true", "false"
162+
]
163+
}
164+
},
165+
"fractional-zero-weights-flag": {
166+
"state": "ENABLED",
167+
"variants": {
168+
"true": "true",
169+
"false": "false",
85170
"fallback": "fallback"
86171
},
87172
"defaultVariant": "fallback",
173+
"targeting": {
174+
"if": [
175+
{"fractional": [
176+
{"var": "targetingKey"},
177+
["one", 0],
178+
["two", 0]
179+
]},
180+
"true", "false"
181+
]
182+
}
183+
},
184+
"fractional-negative-weight-flag": {
185+
"state": "ENABLED",
186+
"variants": { "one": "one", "two": "two", "fallback": "fallback" },
187+
"defaultVariant": "fallback",
88188
"targeting": {
89189
"fractional": [
90-
{"var": "missing_key"},
91-
["one", 50],
92-
["two", 50]
190+
{"var": "targetingKey"},
191+
["one", -50],
192+
["two", 100]
193+
]
194+
}
195+
},
196+
"semver-wrong-args-flag": {
197+
"state": "ENABLED",
198+
"variants": {
199+
"true": "true",
200+
"false": "false",
201+
"fallback": "fallback"
202+
},
203+
"defaultVariant": "fallback",
204+
"targeting": {
205+
"if": [
206+
{"sem_ver": [{"var": "version"}, "="]},
207+
"true", "false"
93208
]
94209
}
95210
}

0 commit comments

Comments
 (0)