Skip to content

Commit 4a633c8

Browse files
authored
chore: implement json-evaluator e2e (#438)
Signed-off-by: Todd Baert <[email protected]>
1 parent e8837e2 commit 4a633c8

File tree

6 files changed

+125
-53
lines changed

6 files changed

+125
-53
lines changed

providers/flagd/pom.xml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,23 @@
290290
<argument>src/test/resources/features/</argument>
291291
</arguments>
292292
</configuration>
293-
</execution>
293+
</execution>
294+
<execution>
295+
<id>copy-gherkin-flagd-json-evaluator.feature</id>
296+
<phase>validate</phase>
297+
<goals>
298+
<goal>exec</goal>
299+
</goals>
300+
<configuration>
301+
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
302+
<!-- run: cp test-harness/features/flagd-json-evaluator.feature src/test/resources/features/ -->
303+
<executable>cp</executable>
304+
<arguments>
305+
<argument>test-harness/gherkin/flagd-json-evaluator.feature</argument>
306+
<argument>src/test/resources/features/</argument>
307+
</arguments>
308+
</configuration>
309+
</execution>
294310
</executions>
295311
</plugin>
296312
</plugins>

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFlagdInProcessCucumberTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
@Suite
1212
@IncludeEngines("cucumber")
13-
@SelectClasspathResource("features")
13+
@SelectClasspathResource("features/evaluation.feature")
14+
@SelectClasspathResource("features/flagd-json-evaluator.feature")
1415
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
15-
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.process,dev.openfeature.contrib.providers.flagd.e2e")
16+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.process,dev.openfeature.contrib.providers.flagd.e2e.steps")
1617
public class RunFlagdInProcessCucumberTest {
1718

1819
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/RunFlagdRpcCucumberTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
@Suite
1212
@IncludeEngines("cucumber")
13-
@SelectClasspathResource("features")
13+
@SelectClasspathResource("features/evaluation.feature")
14+
@SelectClasspathResource("features/flagd-json-evaluator.feature")
1415
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
15-
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.rpc,dev.openfeature.contrib.providers.flagd.e2e")
16+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.rpc,dev.openfeature.contrib.providers.flagd.e2e.steps")
1617
public class RunFlagdRpcCucumberTest {
1718

1819
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/process/FlagdInProcessSetup.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dev.openfeature.contrib.providers.flagd.Config;
44
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
55
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6-
import dev.openfeature.contrib.providers.flagd.e2e.StepDefinitions;
6+
import dev.openfeature.contrib.providers.flagd.e2e.steps.StepDefinitions;
77
import dev.openfeature.sdk.Client;
88
import dev.openfeature.sdk.OpenFeatureAPI;
99
import io.cucumber.java.BeforeAll;

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/rpc/FlagdRpcSetup.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dev.openfeature.contrib.providers.flagd.Config;
44
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
55
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6-
import dev.openfeature.contrib.providers.flagd.e2e.StepDefinitions;
6+
import dev.openfeature.contrib.providers.flagd.e2e.steps.StepDefinitions;
77
import dev.openfeature.sdk.Client;
88
import dev.openfeature.sdk.OpenFeatureAPI;
99
import io.cucumber.java.BeforeAll;
Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.openfeature.contrib.providers.flagd.e2e;
1+
package dev.openfeature.contrib.providers.flagd.e2e.steps;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44

@@ -10,33 +10,36 @@
1010
import dev.openfeature.sdk.EvaluationContext;
1111
import dev.openfeature.sdk.FlagEvaluationDetails;
1212
import dev.openfeature.sdk.ImmutableContext;
13+
import dev.openfeature.sdk.ImmutableStructure;
1314
import dev.openfeature.sdk.Reason;
1415
import dev.openfeature.sdk.Structure;
1516
import dev.openfeature.sdk.Value;
16-
import io.cucumber.java.AfterAll;
1717
import io.cucumber.java.BeforeAll;
18+
import io.cucumber.java.en.And;
1819
import io.cucumber.java.en.Given;
1920
import io.cucumber.java.en.Then;
2021
import io.cucumber.java.en.When;
2122

2223
/**
23-
* Common test suite used by both RPC and in-process flagd.
24+
* Common test suite used by both RPC and in-process flagd providers.
2425
*/
2526
public class StepDefinitions {
2627

2728
private static final ReentrantReadWriteLock sync = new ReentrantReadWriteLock();
2829
private static Client client;
2930

30-
private boolean booleanFlagValue;
31-
private String stringFlagValue;
32-
private int intFlagValue;
33-
private double doubleFlagValue;
34-
private Value objectFlagValue;
31+
private String booleanFlagKey;
32+
private String stringFlagKey;
33+
private String intFlagKey;
34+
private String doubleFlagKey;
35+
private String objectFlagKey;
36+
37+
private boolean booleanFlagDefaultValue;
38+
private String stringFlagDefaultValue;
39+
private int intFlagDefaultValue;
40+
private double doubleFlagDefaultValue;
41+
private Value objectFlagDefaultValue;
3542

36-
private FlagEvaluationDetails<Boolean> booleanFlagDetails;
37-
private FlagEvaluationDetails<String> stringFlagDetails;
38-
private FlagEvaluationDetails<Integer> intFlagDetails;
39-
private FlagEvaluationDetails<Double> doubleFlagDetails;
4043
private FlagEvaluationDetails<Value> objectFlagDetails;
4144

4245
private String contextAwareFlagKey;
@@ -51,24 +54,22 @@ public class StepDefinitions {
5154
private int typeErrorDefaultValue;
5255
private FlagEvaluationDetails<Integer> typeErrorDetails;
5356

57+
private EvaluationContext customEvaluatorContext;
58+
5459
/**
5560
* Injects the client to use for this test.
56-
* Tests run one at a time, but just in case, a lock is used to make sure the client is not updated mid-test.
61+
* Tests run one at a time, but just in case, a lock is used to make sure the
62+
* client is not updated mid-test.
5763
*
5864
* @param client client to inject into test.
5965
*/
6066
public static void setClient(Client client) {
6167
StepDefinitions.client = client;
62-
sync.writeLock().lock();
63-
}
64-
65-
@AfterAll()
66-
public static void cleanUp() {
67-
sync.writeLock().unlock();
6868
}
6969

7070
@BeforeAll()
7171
@Given("a provider is registered")
72+
@Given("a flagd provider is set")
7273
public static void setup() {
7374
// this is handled by the "Setup" files
7475
}
@@ -81,57 +82,67 @@ public static void setup() {
8182
@When("a boolean flag with key {string} is evaluated with default value {string}")
8283
public void a_boolean_flag_with_key_boolean_flag_is_evaluated_with_default_value_false(String flagKey,
8384
String defaultValue) {
84-
this.booleanFlagValue = client.getBooleanValue(flagKey, Boolean.valueOf(defaultValue));
85+
this.booleanFlagKey = flagKey;
86+
this.booleanFlagDefaultValue = Boolean.valueOf(defaultValue);
8587
}
8688

8789
@Then("the resolved boolean value should be {string}")
8890
public void the_resolved_boolean_value_should_be_true(String expected) {
89-
assertEquals(Boolean.valueOf(expected), this.booleanFlagValue);
91+
boolean value = client.getBooleanValue(this.booleanFlagKey, Boolean.valueOf(this.booleanFlagDefaultValue));
92+
assertEquals(Boolean.valueOf(expected), value);
9093
}
9194

9295
// string value
9396
@When("a string flag with key {string} is evaluated with default value {string}")
9497
public void a_string_flag_with_key_is_evaluated_with_default_value(String flagKey, String defaultValue) {
95-
this.stringFlagValue = client.getStringValue(flagKey, defaultValue);
98+
this.stringFlagKey = flagKey;
99+
this.stringFlagDefaultValue = defaultValue;
96100
}
97101

98102
@Then("the resolved string value should be {string}")
99103
public void the_resolved_string_value_should_be(String expected) {
100-
assertEquals(expected, this.stringFlagValue);
104+
String value = client.getStringValue(this.stringFlagKey, this.stringFlagDefaultValue);
105+
assertEquals(expected, value);
101106
}
102107

103108
// integer value
104109
@When("an integer flag with key {string} is evaluated with default value {int}")
105110
public void an_integer_flag_with_key_is_evaluated_with_default_value(String flagKey, Integer defaultValue) {
106-
this.intFlagValue = client.getIntegerValue(flagKey, defaultValue);
111+
this.intFlagKey = flagKey;
112+
this.intFlagDefaultValue = defaultValue;
107113
}
108114

109115
@Then("the resolved integer value should be {int}")
110116
public void the_resolved_integer_value_should_be(int expected) {
111-
assertEquals(expected, this.intFlagValue);
117+
int value = client.getIntegerValue(this.intFlagKey, this.intFlagDefaultValue);
118+
assertEquals(expected, value);
112119
}
113120

114121
// float/double value
115122
@When("a float flag with key {string} is evaluated with default value {double}")
116123
public void a_float_flag_with_key_is_evaluated_with_default_value(String flagKey, double defaultValue) {
117-
this.doubleFlagValue = client.getDoubleValue(flagKey, defaultValue);
124+
this.doubleFlagKey = flagKey;
125+
this.doubleFlagDefaultValue = defaultValue;
118126
}
119127

120128
@Then("the resolved float value should be {double}")
121129
public void the_resolved_float_value_should_be(double expected) {
122-
assertEquals(expected, this.doubleFlagValue);
130+
double value = client.getDoubleValue(this.doubleFlagKey, this.doubleFlagDefaultValue);
131+
assertEquals(expected, value);
123132
}
124133

125134
// object value
126135
@When("an object flag with key {string} is evaluated with a null default value")
127136
public void an_object_flag_with_key_is_evaluated_with_a_null_default_value(String flagKey) {
128-
this.objectFlagValue = client.getObjectValue(flagKey, new Value());
137+
this.objectFlagKey = flagKey;
138+
this.objectFlagDefaultValue = new Value(); // empty value is equivalent to null
129139
}
130140

131141
@Then("the resolved object value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively")
132142
public void the_resolved_object_value_should_be_contain_fields_and_with_values_and_respectively(String boolField,
133143
String stringField, String numberField, String boolValue, String stringValue, int numberValue) {
134-
Structure structure = this.objectFlagValue.asStructure();
144+
Value value = client.getObjectValue(this.objectFlagKey, this.objectFlagDefaultValue);
145+
Structure structure = value.asStructure();
135146

136147
assertEquals(Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean());
137148
assertEquals(stringValue, structure.asMap().get(stringField).asString());
@@ -146,71 +157,88 @@ public void the_resolved_object_value_should_be_contain_fields_and_with_values_a
146157
@When("a boolean flag with key {string} is evaluated with details and default value {string}")
147158
public void a_boolean_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey,
148159
String defaultValue) {
149-
this.booleanFlagDetails = client.getBooleanDetails(flagKey, Boolean.valueOf(defaultValue));
160+
this.booleanFlagKey = flagKey;
161+
this.booleanFlagDefaultValue = Boolean.valueOf(defaultValue);
150162
}
151163

152164
@Then("the resolved boolean details value should be {string}, the variant should be {string}, and the reason should be {string}")
153165
public void the_resolved_boolean_value_should_be_the_variant_should_be_and_the_reason_should_be(
154166
String expectedValue,
155167
String expectedVariant, String expectedReason) {
156-
assertEquals(Boolean.valueOf(expectedValue), booleanFlagDetails.getValue());
157-
assertEquals(expectedVariant, booleanFlagDetails.getVariant());
158-
assertEquals(expectedReason, booleanFlagDetails.getReason());
168+
FlagEvaluationDetails<Boolean> details = client.getBooleanDetails(this.booleanFlagKey,
169+
Boolean.valueOf(this.booleanFlagDefaultValue));
170+
171+
assertEquals(Boolean.valueOf(expectedValue), details.getValue());
172+
assertEquals(expectedVariant, details.getVariant());
173+
assertEquals(expectedReason, details.getReason());
159174
}
160175

161176
// string details
162177
@When("a string flag with key {string} is evaluated with details and default value {string}")
163178
public void a_string_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey,
164179
String defaultValue) {
165-
this.stringFlagDetails = client.getStringDetails(flagKey, defaultValue);
180+
this.stringFlagKey = flagKey;
181+
this.stringFlagDefaultValue = defaultValue;
166182
}
167183

168184
@Then("the resolved string details value should be {string}, the variant should be {string}, and the reason should be {string}")
169185
public void the_resolved_string_value_should_be_the_variant_should_be_and_the_reason_should_be(String expectedValue,
170186
String expectedVariant, String expectedReason) {
171-
assertEquals(expectedValue, this.stringFlagDetails.getValue());
172-
assertEquals(expectedVariant, this.stringFlagDetails.getVariant());
173-
assertEquals(expectedReason, this.stringFlagDetails.getReason());
187+
FlagEvaluationDetails<String> details = client.getStringDetails(this.stringFlagKey,
188+
this.stringFlagDefaultValue);
189+
190+
assertEquals(expectedValue, details.getValue());
191+
assertEquals(expectedVariant, details.getVariant());
192+
assertEquals(expectedReason, details.getReason());
174193
}
175194

176195
// integer details
177196
@When("an integer flag with key {string} is evaluated with details and default value {int}")
178197
public void an_integer_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey, int defaultValue) {
179-
this.intFlagDetails = client.getIntegerDetails(flagKey, defaultValue);
198+
this.intFlagKey = flagKey;
199+
this.intFlagDefaultValue = defaultValue;
180200
}
181201

182202
@Then("the resolved integer details value should be {int}, the variant should be {string}, and the reason should be {string}")
183203
public void the_resolved_integer_value_should_be_the_variant_should_be_and_the_reason_should_be(int expectedValue,
184204
String expectedVariant, String expectedReason) {
185-
assertEquals(expectedValue, this.intFlagDetails.getValue());
186-
assertEquals(expectedVariant, this.intFlagDetails.getVariant());
187-
assertEquals(expectedReason, this.intFlagDetails.getReason());
205+
FlagEvaluationDetails<Integer> details = client.getIntegerDetails(this.intFlagKey, this.intFlagDefaultValue);
206+
207+
assertEquals(expectedValue, details.getValue());
208+
assertEquals(expectedVariant, details.getVariant());
209+
assertEquals(expectedReason, details.getReason());
188210
}
189211

190212
// float/double details
191213
@When("a float flag with key {string} is evaluated with details and default value {double}")
192214
public void a_float_flag_with_key_is_evaluated_with_details_and_default_value(String flagKey, double defaultValue) {
193-
this.doubleFlagDetails = client.getDoubleDetails(flagKey, defaultValue);
215+
this.doubleFlagKey = flagKey;
216+
this.doubleFlagDefaultValue = defaultValue;
194217
}
195218

196219
@Then("the resolved float details value should be {double}, the variant should be {string}, and the reason should be {string}")
197220
public void the_resolved_float_value_should_be_the_variant_should_be_and_the_reason_should_be(double expectedValue,
198221
String expectedVariant, String expectedReason) {
199-
assertEquals(expectedValue, this.doubleFlagDetails.getValue());
200-
assertEquals(expectedVariant, this.doubleFlagDetails.getVariant());
201-
assertEquals(expectedReason, this.doubleFlagDetails.getReason());
222+
FlagEvaluationDetails<Double> details = client.getDoubleDetails(this.doubleFlagKey,
223+
this.doubleFlagDefaultValue);
224+
225+
assertEquals(expectedValue, details.getValue());
226+
assertEquals(expectedVariant, details.getVariant());
227+
assertEquals(expectedReason, details.getReason());
202228
}
203229

204230
// object details
205231
@When("an object flag with key {string} is evaluated with details and a null default value")
206232
public void an_object_flag_with_key_is_evaluated_with_details_and_a_null_default_value(String flagKey) {
207-
this.objectFlagDetails = client.getObjectDetails(flagKey, new Value());
233+
this.objectFlagKey = flagKey;
234+
this.objectFlagDefaultValue = new Value();
208235
}
209236

210237
@Then("the resolved object details value should be contain fields {string}, {string}, and {string}, with values {string}, {string} and {int}, respectively")
211238
public void the_resolved_object_value_should_be_contain_fields_and_with_values_and_respectively_again(
212239
String boolField,
213240
String stringField, String numberField, String boolValue, String stringValue, int numberValue) {
241+
this.objectFlagDetails = client.getObjectDetails(this.objectFlagKey, this.objectFlagDefaultValue);
214242
Structure structure = this.objectFlagDetails.getValue().asStructure();
215243

216244
assertEquals(Boolean.valueOf(boolValue), structure.asMap().get(boolField).asBoolean());
@@ -303,4 +331,30 @@ public void the_reason_should_indicate_an_error_and_the_error_code_should_be_typ
303331
assertEquals(errorCode, typeErrorDetails.getErrorCode().toString());
304332
}
305333

334+
/*
335+
* Custom JSON evaluators (only run for flagd-in-process)
336+
*/
337+
338+
@And("a context containing a nested property with outer key {string} and inner key {string}, with value {string}")
339+
public void a_context_containing_a_nested_property_with_outer_key_and_inner_key_with_value(String outerKey,
340+
String innerKey, String value) throws InstantiationException {
341+
Map<String, Value> innerMap = new HashMap<String, Value>();
342+
innerMap.put(innerKey, new Value(value));
343+
Map<String, Value> outerMap = new HashMap<String, Value>();
344+
outerMap.put(outerKey, new Value(new ImmutableStructure(innerMap)));
345+
this.customEvaluatorContext = new ImmutableContext(outerMap);
346+
}
347+
348+
@And("a context containing a key {string}, with value {string}")
349+
public void a_context_containing_a_key_with_value(String key, String value) {
350+
Map<String, Value> attrs = new HashMap<String, Value>();
351+
attrs.put(key, new Value(value));
352+
this.customEvaluatorContext = new ImmutableContext(attrs);
353+
}
354+
355+
@Then("the returned value should be {string}")
356+
public void the_returned_value_should_be(String expected) {
357+
String value = client.getStringValue(this.stringFlagKey, this.stringFlagDefaultValue, this.customEvaluatorContext);
358+
assertEquals(expected, value);
359+
}
306360
}

0 commit comments

Comments
 (0)