Skip to content

Commit f970220

Browse files
committed
feat(flagd): ssl e2e tests
Signed-off-by: Simon Schrottner <[email protected]>
1 parent d5110e3 commit f970220

File tree

9 files changed

+234
-29
lines changed

9 files changed

+234
-29
lines changed

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[submodule "providers/flagd/test-harness"]
55
path = providers/flagd/test-harness
66
url = https://github.com/open-feature/test-harness.git
7+
branch = v0.5.19
78
[submodule "providers/flagd/spec"]
89
path = providers/flagd/spec
910
url = https://github.com/open-feature/spec.git
Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,46 @@
11
package dev.openfeature.contrib.providers.flagd.e2e;
22

3+
import org.apache.logging.log4j.util.Strings;
34
import org.jetbrains.annotations.NotNull;
45
import org.testcontainers.containers.GenericContainer;
56
import org.testcontainers.containers.Network;
67
import org.testcontainers.utility.DockerImageName;
78
import org.testcontainers.utility.MountableFile;
89

9-
import java.io.IOException;
10-
import java.util.Properties;
10+
import java.io.File;
11+
import java.nio.file.Files;
12+
import java.util.List;
1113

1214
public class ContainerConfig {
1315
private static final String version;
1416
private static final Network network = Network.newNetwork();
1517

1618
static {
17-
Properties properties = new Properties();
19+
String path = "test-harness/version.txt";
20+
File file = new File(path);
1821
try {
19-
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("flagdTestbed.properties"));
20-
version = properties.getProperty("version");
21-
} catch (IOException e) {
22+
List<String> lines = Files.readAllLines(file.toPath());
23+
version = lines.get(0);
24+
} catch (Exception e) {
2225
throw new RuntimeException(e);
2326
}
2427
}
2528

29+
2630
/**
27-
*
2831
* @return a {@link org.testcontainers.containers.GenericContainer} instance of a stable sync flagd server with the port 9090 exposed
2932
*/
30-
public static GenericContainer sync() {
33+
public static GenericContainer sync() {
3134
return sync(false, false);
3235
}
3336

3437
/**
35-
*
36-
* @param unstable if an unstable version of the container, which terminates the connection regularly should be used.
38+
* @param unstable if an unstable version of the container, which terminates the connection regularly should be used.
3739
* @param addNetwork if set to true a custom network is attached for cross container access e.g. envoy --> sync:8015
3840
* @return a {@link org.testcontainers.containers.GenericContainer} instance of a sync flagd server with the port 8015 exposed
3941
*/
4042
public static GenericContainer sync(boolean unstable, boolean addNetwork) {
41-
String container = generateContainerName("flagd", unstable);
43+
String container = generateContainerName("flagd", unstable ? "unstable" : "");
4244
GenericContainer genericContainer = new GenericContainer(DockerImageName.parse(container))
4345
.withExposedPorts(8015);
4446

@@ -51,20 +53,18 @@ public static GenericContainer sync(boolean unstable, boolean addNetwork) {
5153
}
5254

5355
/**
54-
*
5556
* @return a {@link org.testcontainers.containers.GenericContainer} instance of a stable flagd server with the port 8013 exposed
5657
*/
5758
public static GenericContainer flagd() {
5859
return flagd(false);
5960
}
6061

6162
/**
62-
*
6363
* @param unstable if an unstable version of the container, which terminates the connection regularly should be used.
6464
* @return a {@link org.testcontainers.containers.GenericContainer} instance of a flagd server with the port 8013 exposed
6565
*/
6666
public static GenericContainer flagd(boolean unstable) {
67-
String container = generateContainerName("flagd", unstable);
67+
String container = generateContainerName("flagd", unstable ? "unstable" : "");
6868
return new GenericContainer(DockerImageName.parse(container))
6969
.withExposedPorts(8013);
7070
}
@@ -73,7 +73,6 @@ public static GenericContainer flagd(boolean unstable) {
7373
/**
7474
* @return a {@link org.testcontainers.containers.GenericContainer} instance of envoy container using
7575
* flagd sync service as backend expose on port 9211
76-
*
7776
*/
7877
public static GenericContainer envoy() {
7978
final String container = "envoyproxy/envoy:v1.31.0";
@@ -85,14 +84,14 @@ public static GenericContainer envoy() {
8584
.withNetworkAliases("envoy");
8685
}
8786

88-
private static @NotNull String generateContainerName(String type, boolean unstable) {
87+
public static @NotNull String generateContainerName(String type, String addition) {
8988
String container = "ghcr.io/open-feature/";
9089
container += type;
9190
container += "-testbed";
92-
if (unstable) {
93-
container += "-unstable";
91+
if (!Strings.isBlank(addition)) {
92+
container += "-" + addition;
9493
}
95-
container += ":" + version;
94+
container += ":v" + version;
9695
return container;
9796
}
9897
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e;
2+
3+
import org.apache.logging.log4j.core.config.Order;
4+
import org.junit.jupiter.api.Disabled;
5+
import org.junit.platform.suite.api.ConfigurationParameter;
6+
import org.junit.platform.suite.api.IncludeEngines;
7+
import org.junit.platform.suite.api.SelectClasspathResource;
8+
import org.junit.platform.suite.api.Suite;
9+
import org.testcontainers.junit.jupiter.Testcontainers;
10+
11+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
12+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
13+
14+
/**
15+
* Class for running the reconnection tests for the RPC provider
16+
*/
17+
@Order(value = Integer.MAX_VALUE)
18+
@Suite(failIfNoTests = false)
19+
@IncludeEngines("cucumber")
20+
//@SelectClasspathResource("features/evaluation.feature")
21+
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
22+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.ssl.process,dev.openfeature.contrib.providers.flagd.e2e.steps")
23+
@Testcontainers
24+
public class RunFlagdInProcessSSLCucumberTest {
25+
26+
}
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e;
2+
3+
import org.apache.logging.log4j.core.config.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+
import org.testcontainers.junit.jupiter.Testcontainers;
9+
10+
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
11+
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
12+
13+
/**
14+
* Class for running the reconnection tests for the RPC provider
15+
*/
16+
@Order(value = Integer.MAX_VALUE)
17+
@Suite
18+
@IncludeEngines("cucumber")
19+
@SelectClasspathResource("features/evaluation.feature")
20+
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
21+
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.ssl.rpc,dev.openfeature.contrib.providers.flagd.e2e.steps")
22+
@Testcontainers
23+
public class RunFlagdRpcSSLCucumberTest {
24+
25+
}
26+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e.ssl.process;
2+
3+
import dev.openfeature.contrib.providers.flagd.Config;
4+
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
5+
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6+
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
7+
import dev.openfeature.contrib.providers.flagd.e2e.steps.StepDefinitions;
8+
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
9+
import dev.openfeature.sdk.FeatureProvider;
10+
import io.cucumber.java.AfterAll;
11+
import io.cucumber.java.Before;
12+
import io.cucumber.java.BeforeAll;
13+
import org.junit.jupiter.api.Order;
14+
import org.junit.jupiter.api.parallel.Isolated;
15+
import org.testcontainers.containers.GenericContainer;
16+
import org.testcontainers.utility.DockerImageName;
17+
18+
import java.io.File;
19+
20+
@Isolated()
21+
@Order(value = Integer.MAX_VALUE)
22+
public class FlagdInProcessSetup {
23+
private static final GenericContainer flagdContainer =
24+
new GenericContainer(
25+
DockerImageName.parse(
26+
ContainerConfig.generateContainerName("flagd", "ssl")
27+
)
28+
).withExposedPorts(8015);
29+
30+
@BeforeAll()
31+
public static void setups() throws InterruptedException {
32+
flagdContainer.start();
33+
}
34+
35+
@Before()
36+
public static void setupTest() throws InterruptedException {
37+
String path = "test-harness/ssl/custom-root-cert.crt";
38+
39+
File file = new File(path);
40+
String absolutePath = file.getAbsolutePath();
41+
FeatureProvider workingProvider = new FlagdProvider(FlagdOptions.builder()
42+
.resolverType(Config.Resolver.IN_PROCESS)
43+
.port(flagdContainer.getFirstMappedPort())
44+
.deadline(10000)
45+
.streamDeadlineMs(0) // this makes reconnect tests more predictable
46+
.tls(true)
47+
.certPath(absolutePath)
48+
.build());
49+
StepDefinitions.setProvider(workingProvider);
50+
51+
}
52+
53+
@AfterAll
54+
public static void tearDown() {
55+
flagdContainer.stop();
56+
}
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.openfeature.contrib.providers.flagd.e2e.ssl.rpc;
2+
3+
import dev.openfeature.contrib.providers.flagd.Config;
4+
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
5+
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
6+
import dev.openfeature.contrib.providers.flagd.e2e.ContainerConfig;
7+
import dev.openfeature.contrib.providers.flagd.e2e.steps.StepDefinitions;
8+
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
9+
import dev.openfeature.sdk.FeatureProvider;
10+
import io.cucumber.java.AfterAll;
11+
import io.cucumber.java.Before;
12+
import io.cucumber.java.BeforeAll;
13+
import org.junit.jupiter.api.Order;
14+
import org.junit.jupiter.api.parallel.Isolated;
15+
import org.testcontainers.containers.GenericContainer;
16+
import org.testcontainers.utility.DockerImageName;
17+
18+
import java.io.File;
19+
20+
@Isolated()
21+
@Order(value = Integer.MAX_VALUE)
22+
public class FlagdRpcSetup {
23+
private static final GenericContainer flagdContainer =
24+
new GenericContainer(
25+
DockerImageName.parse(
26+
ContainerConfig.generateContainerName("flagd", "ssl")
27+
)
28+
).withExposedPorts(8013);
29+
30+
@BeforeAll()
31+
public static void setups() throws InterruptedException {
32+
flagdContainer.start();
33+
}
34+
35+
@Before()
36+
public static void setupTest() throws InterruptedException {
37+
String path = "test-harness/ssl/custom-root-cert.crt";
38+
39+
File file = new File(path);
40+
String absolutePath = file.getAbsolutePath();
41+
FeatureProvider workingProvider = new FlagdProvider(FlagdOptions.builder()
42+
.resolverType(Config.Resolver.RPC)
43+
.port(flagdContainer.getFirstMappedPort())
44+
.deadline(10000)
45+
.streamDeadlineMs(0) // this makes reconnect tests more predictable
46+
.tls(true)
47+
.certPath(absolutePath)
48+
.build());
49+
StepDefinitions.setProvider(workingProvider);
50+
51+
}
52+
53+
@AfterAll
54+
public static void tearDown() {
55+
flagdContainer.stop();
56+
}
57+
}

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

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,42 @@
33
import dev.openfeature.contrib.providers.flagd.Config;
44
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
55
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
6+
import io.cucumber.java.en.Given;
67
import io.cucumber.java.en.Then;
78
import io.cucumber.java.en.When;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
811

912
import java.lang.reflect.InvocationTargetException;
1013
import java.lang.reflect.Method;
14+
import java.util.ArrayList;
1115
import java.util.Arrays;
1216
import java.util.HashMap;
17+
import java.util.List;
1318
import java.util.Map;
1419
import java.util.Objects;
1520

1621
import static org.assertj.core.api.Assertions.assertThat;
1722

1823
public class ConfigSteps {
24+
public static final List<String> IGNORED_FOR_NOW = new ArrayList<String>() {
25+
{
26+
add("offlinePollIntervalMs");
27+
add("retryGraceAttempts");
28+
add("retryBackoffMaxMs");
29+
}
30+
};
31+
private static final Logger LOG = LoggerFactory.getLogger(ConfigSteps.class);
1932

2033
FlagdOptions.FlagdOptionsBuilder builder = FlagdOptions.builder();
2134
FlagdOptions options;
2235

23-
@When("we initialize a config")
36+
@When("a config was initialized")
2437
public void we_initialize_a_config() {
2538
options = builder.build();
2639
}
2740

28-
@When("we initialize a config for {string}")
41+
@When("a config was initialized for {string}")
2942
public void we_initialize_a_config_for(String string) {
3043
switch (string.toLowerCase()) {
3144
case "in-process":
@@ -39,11 +52,16 @@ public void we_initialize_a_config_for(String string) {
3952
}
4053
}
4154

42-
@When("we have an option {string} of type {string} with value {string}")
55+
@Given("an option {string} of type {string} with value {string}")
4356
public void we_have_an_option_of_type_with_value(String option, String type, String value) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
57+
if(IGNORED_FOR_NOW.contains(option)) {
58+
LOG.error("option '{}' is not supported", option);
59+
return;
60+
}
61+
4462
Object converted = convert(value, type);
4563
Method method = Arrays.stream(builder.getClass().getMethods())
46-
.filter(method1 -> method1.getName().equals(option))
64+
.filter(method1 -> method1.getName().equals(mapOptionNames(option)))
4765
.findFirst()
4866
.orElseThrow(RuntimeException::new);
4967
method.invoke(builder, converted);
@@ -52,7 +70,7 @@ public void we_have_an_option_of_type_with_value(String option, String type, Str
5270

5371
Map<String, String> envVarsSet = new HashMap<>();
5472

55-
@When("we have an environment variable {string} with value {string}")
73+
@Given("an environment variable {string} with value {string}")
5674
public void we_have_an_environment_variable_with_value(String varName, String value) throws IllegalAccessException, NoSuchFieldException {
5775
String getenv = System.getenv(varName);
5876
envVarsSet.put(varName, getenv);
@@ -89,15 +107,38 @@ private Object convert(String value, String type) throws ClassNotFoundException
89107
public void the_option_of_type_should_have_the_value(String option, String type, String value) throws Throwable {
90108
Object convert = convert(value, type);
91109

110+
if(IGNORED_FOR_NOW.contains(option)) {
111+
LOG.error("option '{}' is not supported", option);
112+
return;
113+
}
114+
115+
116+
option = mapOptionNames(option);
117+
92118
assertThat(options).hasFieldOrPropertyWithValue(option, convert);
93119

94120
// Resetting env vars
95-
for (Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
121+
for (
122+
Map.Entry<String, String> envVar : envVarsSet.entrySet()) {
96123
if (envVar.getValue() == null) {
97124
EnvironmentVariableUtils.clear(envVar.getKey());
98125
} else {
99126
EnvironmentVariableUtils.set(envVar.getKey(), envVar.getValue());
100127
}
101128
}
102129
}
130+
131+
private static String mapOptionNames(String option) {
132+
Map<String, String> propertyMapper = new HashMap<>();
133+
propertyMapper.put("resolver", "resolverType");
134+
propertyMapper.put("deadlineMs", "deadline");
135+
propertyMapper.put("keepAliveTime", "keepAlive");
136+
propertyMapper.put("retryBackoffMaxMs", "keepAlive");
137+
propertyMapper.put("cache", "cacheType");
138+
139+
if (propertyMapper.get(option) != null) {
140+
option = propertyMapper.get(option);
141+
}
142+
return option;
143+
}
103144
}

0 commit comments

Comments
 (0)