Skip to content

Commit d5110e3

Browse files
aepflitoddbaert
andauthored
feat(flagd): Improve e2e coverage (#1092)
Signed-off-by: Simon Schrottner <[email protected]> Co-authored-by: Todd Baert <[email protected]>
1 parent c172337 commit d5110e3

File tree

13 files changed

+308
-27
lines changed

13 files changed

+308
-27
lines changed

providers/flagd/pom.xml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
<dependencies>
3535
<!-- we inherent dev.openfeature.javasdk and the test dependencies from the parent pom -->
36-
36+
3737
<!-- pin protobuf-java to version used by io.grpc deps; update to 4.0 when io.grpc requires it -->
3838
<dependency>
3939
<groupId>com.google.protobuf</groupId>
@@ -335,6 +335,36 @@
335335
</arguments>
336336
</configuration>
337337
</execution>
338+
<execution>
339+
<id>copy-gherkin-flagd-rpc-caching.feature</id>
340+
<phase>validate</phase>
341+
<goals>
342+
<goal>exec</goal>
343+
</goals>
344+
<configuration>
345+
<executable>cp</executable>
346+
<arguments>
347+
<argument>test-harness/gherkin/flagd-rpc-caching.feature</argument>
348+
<argument>src/test/resources/features/</argument>
349+
</arguments>
350+
</configuration>
351+
</execution>
352+
<execution>
353+
<id>copy-gherkin-config.feature</id>
354+
<phase>validate</phase>
355+
<goals>
356+
<goal>exec</goal>
357+
</goals>
358+
<configuration>
359+
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
360+
<!-- run: cp test-harness/features/flagd.feature src/test/resources/features/ -->
361+
<executable>cp</executable>
362+
<arguments>
363+
<argument>test-harness/gherkin/config.feature</argument>
364+
<argument>src/test/resources/features/</argument>
365+
</arguments>
366+
</configuration>
367+
</execution>
338368
<execution>
339369
<id>copy-gherkin-flagd-reconnect.feature</id>
340370
<phase>validate</phase>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e;
2+
3+
import org.junit.jupiter.api.Order;
4+
import org.junit.platform.suite.api.ConfigurationParameter;
5+
import org.junit.platform.suite.api.IncludeEngines;
6+
import org.junit.platform.suite.api.SelectClasspathResource;
7+
import org.junit.platform.suite.api.Suite;
8+
9+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
10+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
11+
12+
/**
13+
* Class for running the tests associated with "stable" e2e tests (no fake disconnection) for the in-process provider
14+
*/
15+
@Order(value = Integer.MAX_VALUE)
16+
@Suite
17+
@IncludeEngines("cucumber")
18+
@SelectClasspathResource("features/config.feature")
19+
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
20+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.steps.config")
21+
public class RunConfigCucumberTest {
22+
23+
24+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
@SelectClasspathResource("features/evaluation.feature")
2020
@SelectClasspathResource("features/flagd-json-evaluator.feature")
2121
@SelectClasspathResource("features/flagd.feature")
22+
@SelectClasspathResource("features/flagd-rpc-caching.feature")
2223
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
2324
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.rpc,dev.openfeature.contrib.providers.flagd.e2e.steps")
2425
@Testcontainers

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
44
import io.cucumber.java.AfterAll;
5+
import io.cucumber.java.Before;
56
import org.junit.jupiter.api.Order;
67
import org.junit.jupiter.api.parallel.Isolated;
78

@@ -24,6 +25,10 @@ public class FlagdInProcessSetup {
2425
@BeforeAll()
2526
public static void setup() throws InterruptedException {
2627
flagdContainer.start();
28+
}
29+
30+
@Before()
31+
public static void setupTest() throws InterruptedException {
2732
FlagdInProcessSetup.provider = new FlagdProvider(FlagdOptions.builder()
2833
.resolverType(Config.Resolver.IN_PROCESS)
2934
.deadline(1000)
@@ -37,4 +42,4 @@ public static void setup() throws InterruptedException {
3742
public static void tearDown() {
3843
flagdContainer.stop();
3944
}
40-
}
45+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
44
import io.cucumber.java.AfterAll;
5+
import io.cucumber.java.Before;
56
import org.junit.jupiter.api.Order;
67
import org.junit.jupiter.api.parallel.Isolated;
78

@@ -21,6 +22,9 @@ public class FlagdInProcessSetup {
2122
@BeforeAll()
2223
public static void setup() throws InterruptedException {
2324
flagdContainer.start();
25+
}
26+
@Before()
27+
public static void setupTest() throws InterruptedException {
2428
FeatureProvider workingProvider = new FlagdProvider(FlagdOptions.builder()
2529
.resolverType(Config.Resolver.IN_PROCESS)
2630
.port(flagdContainer.getFirstMappedPort())

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
package dev.openfeature.contrib.providers.flagd.e2e.reconnect.rpc;
22

3-
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
4-
import io.cucumber.java.AfterAll;
5-
import org.junit.jupiter.api.Order;
6-
import org.junit.jupiter.api.parallel.Isolated;
7-
83
import dev.openfeature.contrib.providers.flagd.Config;
94
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
105
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6+
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
117
import dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps.StepDefinitions;
128
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
139
import dev.openfeature.sdk.FeatureProvider;
10+
import io.cucumber.java.AfterAll;
11+
import io.cucumber.java.Before;
1412
import io.cucumber.java.BeforeAll;
13+
import org.junit.jupiter.api.Order;
14+
import org.junit.jupiter.api.parallel.Isolated;
1515
import org.testcontainers.containers.GenericContainer;
16-
import org.testcontainers.junit.jupiter.Container;
17-
import org.testcontainers.utility.DockerImageName;
1816

1917
@Isolated()
2018
@Order(value = Integer.MAX_VALUE)
2119
public class FlagdRpcSetup {
2220
private static final GenericContainer flagdContainer = ContainerConfig.flagd(true);
2321

2422
@BeforeAll()
25-
public static void setup() throws InterruptedException {
23+
public static void setups() throws InterruptedException {
2624
flagdContainer.start();
25+
}
26+
27+
@Before()
28+
public static void setupTest() throws InterruptedException {
2729

2830
FeatureProvider workingProvider = new FlagdProvider(FlagdOptions.builder()
2931
.resolverType(Config.Resolver.RPC)

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/reconnect/steps/StepDefinitions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,4 @@ public void an_error_should_be_indicated_within_the_configured_deadline() {
125125
OpenFeatureAPI.getInstance().setProviderAndWait("unavailable", StepDefinitions.unavailableProvider);
126126
});
127127
}
128-
}
128+
}

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
package dev.openfeature.contrib.providers.flagd.e2e.rpc;
22

3-
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
4-
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
5-
import io.cucumber.java.AfterAll;
6-
import org.junit.jupiter.api.Order;
7-
import org.junit.jupiter.api.parallel.Isolated;
8-
93
import dev.openfeature.contrib.providers.flagd.Config;
104
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
115
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6+
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
127
import dev.openfeature.contrib.providers.flagd.e2e.steps.StepDefinitions;
138
import dev.openfeature.sdk.FeatureProvider;
9+
import io.cucumber.java.AfterAll;
10+
import io.cucumber.java.Before;
1411
import io.cucumber.java.BeforeAll;
12+
import org.junit.jupiter.api.Order;
13+
import org.junit.jupiter.api.parallel.Isolated;
1514
import org.testcontainers.containers.GenericContainer;
16-
import org.testcontainers.junit.jupiter.Container;
17-
import org.testcontainers.junit.jupiter.Testcontainers;
18-
import org.testcontainers.utility.DockerImageName;
1915

2016
@Isolated()
2117
@Order(value = Integer.MAX_VALUE)
@@ -27,11 +23,14 @@ public class FlagdRpcSetup {
2723
@BeforeAll()
2824
public static void setup() {
2925
flagdContainer.start();
26+
}
27+
28+
@Before()
29+
public static void test_setup() {
3030
FlagdRpcSetup.provider = new FlagdProvider(FlagdOptions.builder()
3131
.resolverType(Config.Resolver.RPC)
3232
.port(flagdContainer.getFirstMappedPort())
33-
.deadline(1000)
34-
.cacheType(CacheType.DISABLED.getValue())
33+
.deadline(500)
3534
.build());
3635
StepDefinitions.setProvider(provider);
3736
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/StepDefinitions.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public class StepDefinitions {
7575
* Injects the client to use for this test.
7676
* Tests run one at a time, but just in case, a lock is used to make sure the
7777
* client is not updated mid-test.
78-
*
78+
*
7979
* @param client client to inject into test.
8080
*/
8181
public static void setProvider(FeatureProvider provider) {
@@ -208,6 +208,12 @@ public void a_string_flag_with_key_is_evaluated_with_details_and_default_value(S
208208
this.stringFlagDefaultValue = defaultValue;
209209
}
210210

211+
@When("a string flag with key {string} is evaluated with details")
212+
public void a_string_flag_with_key_is_evaluated_with_details(String flagKey) {
213+
this.stringFlagKey = flagKey;
214+
this.stringFlagDefaultValue = "";
215+
}
216+
211217
@Then("the resolved string details value should be {string}, the variant should be {string}, and the reason should be {string}")
212218
public void the_resolved_string_value_should_be_the_variant_should_be_and_the_reason_should_be(String expectedValue,
213219
String expectedVariant, String expectedReason) {
@@ -347,6 +353,7 @@ public void a_string_flag_with_key_is_evaluated_as_an_integer_with_details_and_a
347353
typeErrorDetails = client.getIntegerDetails(typeErrorFlagKey, typeErrorDefaultValue);
348354
}
349355

356+
350357
@Then("the default integer value should be returned")
351358
public void then_the_default_integer_value_should_be_returned() {
352359
assertEquals(typeErrorDefaultValue, typeErrorDetails.getValue());
@@ -525,7 +532,7 @@ public void the_resolved_string_zero_value_should_be(String expected) {
525532
String value = client.getStringValue(this.stringFlagKey, this.stringFlagDefaultValue);
526533
assertEquals(expected, value);
527534
}
528-
535+
529536
@When("a context containing a targeting key with value {string}")
530537
public void a_context_containing_a_targeting_key_with_value(String targetingKey) {
531538
this.context = new ImmutableContext(targetingKey);
@@ -537,4 +544,4 @@ public void the_returned_reason_should_be(String reason) {
537544
this.context);
538545
assertEquals(reason, details.getReason());
539546
}
540-
}
547+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e.steps.config;
2+
3+
import dev.openfeature.contrib.providers.flagd.Config;
4+
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
5+
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
6+
import io.cucumber.java.en.Then;
7+
import io.cucumber.java.en.When;
8+
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
import java.util.Arrays;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.Objects;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
18+
public class ConfigSteps {
19+
20+
FlagdOptions.FlagdOptionsBuilder builder = FlagdOptions.builder();
21+
FlagdOptions options;
22+
23+
@When("we initialize a config")
24+
public void we_initialize_a_config() {
25+
options = builder.build();
26+
}
27+
28+
@When("we initialize a config for {string}")
29+
public void we_initialize_a_config_for(String string) {
30+
switch (string.toLowerCase()) {
31+
case "in-process":
32+
options = builder.resolverType(Config.Resolver.IN_PROCESS).build();
33+
break;
34+
case "rpc":
35+
options = builder.resolverType(Config.Resolver.RPC).build();
36+
break;
37+
default:
38+
throw new RuntimeException("Unknown resolver type: " + string);
39+
}
40+
}
41+
42+
@When("we have an option {string} of type {string} with value {string}")
43+
public void we_have_an_option_of_type_with_value(String option, String type, String value) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
44+
Object converted = convert(value, type);
45+
Method method = Arrays.stream(builder.getClass().getMethods())
46+
.filter(method1 -> method1.getName().equals(option))
47+
.findFirst()
48+
.orElseThrow(RuntimeException::new);
49+
method.invoke(builder, converted);
50+
}
51+
52+
53+
Map<String, String> envVarsSet = new HashMap<>();
54+
55+
@When("we have an environment variable {string} with value {string}")
56+
public void we_have_an_environment_variable_with_value(String varName, String value) throws IllegalAccessException, NoSuchFieldException {
57+
String getenv = System.getenv(varName);
58+
envVarsSet.put(varName, getenv);
59+
EnvironmentVariableUtils.set(varName, value);
60+
}
61+
62+
private Object convert(String value, String type) throws ClassNotFoundException {
63+
if (Objects.equals(value, "null")) return null;
64+
switch (type) {
65+
case "Boolean":
66+
return Boolean.parseBoolean(value);
67+
case "String":
68+
return value;
69+
case "Integer":
70+
return Integer.parseInt(value);
71+
case "Long":
72+
return Long.parseLong(value);
73+
case "ResolverType":
74+
switch (value.toLowerCase()) {
75+
case "in-process":
76+
return Config.Resolver.IN_PROCESS;
77+
case "rpc":
78+
return Config.Resolver.RPC;
79+
default:
80+
throw new RuntimeException("Unknown resolver type: " + value);
81+
}
82+
case "CacheType":
83+
return CacheType.valueOf(value.toUpperCase()).getValue();
84+
}
85+
throw new RuntimeException("Unknown config type: " + type);
86+
}
87+
88+
@Then("the option {string} of type {string} should have the value {string}")
89+
public void the_option_of_type_should_have_the_value(String option, String type, String value) throws Throwable {
90+
Object convert = convert(value, type);
91+
92+
assertThat(options).hasFieldOrPropertyWithValue(option, convert);
93+
94+
// Resetting env vars
95+
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
96+
if (envVar.getValue() == null) {
97+
EnvironmentVariableUtils.clear(envVar.getKey());
98+
} else {
99+
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
100+
}
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)