Skip to content

Commit

Permalink
chore: add e2e test for reconnect (#596)
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert authored Dec 22, 2023
1 parent b657df1 commit c22b90e
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 27 deletions.
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,26 @@ jobs:
main:
runs-on: ubuntu-latest
services:
# flagd-testbed for flagd-provider e2e tests
# flagd-testbed for flagd RPC provider e2e tests
flagd:
image: ghcr.io/open-feature/flagd-testbed:v0.4.10
ports:
- 8013:8013
# sync-testbed for flagd-provider e2e tests
# flagd-testbed for flagd RPC provider reconnect e2e tests
flagd-unstable:
image: ghcr.io/open-feature/flagd-testbed-unstable:v0.4.9
ports:
- 8014:8013
# sync-testbed for flagd in-process provider e2e tests
sync:
image: ghcr.io/open-feature/sync-testbed:v0.4.10
ports:
- 9090:9090
# sync-testbed for flagd in-process provider reconnect e2e tests
sync-unstable:
image: ghcr.io/open-feature/sync-testbed-unstable:v0.4.9
ports:
- 9091:9090

steps:
- name: Checkout Repository
Expand Down
16 changes: 16 additions & 0 deletions providers/flagd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,22 @@
</arguments>
</configuration>
</execution>
<execution>
<id>copy-gherkin-flagd-reconnect.feature</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<!-- copy the feature spec we want to test into resources so them can be easily loaded -->
<!-- run: cp test-harness/features/flagd-reconnect.feature src/test/resources/features/ -->
<executable>cp</executable>
<arguments>
<argument>test-harness/gherkin/flagd-reconnect.feature</argument>
<argument>src/test/resources/features/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcConnector;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.GrpcResolver;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.Cache;
import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
import dev.openfeature.flagd.grpc.Schema.EventStreamResponse;
import dev.openfeature.flagd.grpc.Schema.ResolveBooleanRequest;
import dev.openfeature.flagd.grpc.Schema.ResolveBooleanResponse;
Expand All @@ -26,6 +25,7 @@
import dev.openfeature.sdk.Reason;
import dev.openfeature.sdk.Structure;
import dev.openfeature.sdk.Value;
import io.cucumber.java.AfterAll;
import io.grpc.Channel;
import io.grpc.Deadline;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -83,6 +83,10 @@ public static void init() {
api = OpenFeatureAPI.getInstance();
}

@AfterAll
public static void cleanUp() {
api.shutdown();
}

@Test
void resolvers_call_grpc_service_and_return_details() {
Expand Down Expand Up @@ -191,7 +195,6 @@ void zero_value() {
.build();

ServiceBlockingStub serviceBlockingStubMock = mock(ServiceBlockingStub.class);
ServiceStub serviceStubMock = mock(ServiceStub.class);
when(serviceBlockingStubMock.withDeadlineAfter(anyLong(), any(TimeUnit.class)))
.thenReturn(serviceBlockingStubMock);
when(serviceBlockingStubMock
Expand Down Expand Up @@ -261,7 +264,6 @@ void test_metadata_from_grpc_response() {


ServiceBlockingStub serviceBlockingStubMock = mock(ServiceBlockingStub.class);
ServiceStub serviceStubMock = mock(ServiceStub.class);

when(serviceBlockingStubMock.withDeadlineAfter(anyLong(), any(TimeUnit.class))).thenReturn(
serviceBlockingStubMock);
Expand Down Expand Up @@ -329,7 +331,6 @@ void context_is_parsed_and_passed_to_grpc_service() {
.build();

ServiceBlockingStub serviceBlockingStubMock = mock(ServiceBlockingStub.class);
ServiceStub serviceStubMock = mock(ServiceStub.class);
when(serviceBlockingStubMock.withDeadlineAfter(anyLong(), any(TimeUnit.class)))
.thenReturn(serviceBlockingStubMock);
when(serviceBlockingStubMock.resolveBoolean(argThat(
Expand Down Expand Up @@ -375,7 +376,6 @@ void null_context_handling() {
context.add("key", (String) null);

final ServiceBlockingStub serviceBlockingStubMock = mock(ServiceBlockingStub.class);
final ServiceStub serviceStubMock = mock(ServiceStub.class);

// when
when(serviceBlockingStubMock.withDeadlineAfter(anyLong(), any(TimeUnit.class)))
Expand Down Expand Up @@ -405,7 +405,6 @@ void reason_mapped_correctly_if_unknown() {
.build();

ServiceBlockingStub serviceBlockingStubMock = mock(ServiceBlockingStub.class);
ServiceStub serviceStubMock = mock(ServiceStub.class);
when(serviceBlockingStubMock.withDeadlineAfter(anyLong(), any(TimeUnit.class)))
.thenReturn(serviceBlockingStubMock);
when(serviceBlockingStubMock.resolveBoolean(any(ResolveBooleanRequest.class))).thenReturn(badReasonResponse);
Expand Down Expand Up @@ -510,11 +509,6 @@ void invalidate_cache() {
structMap.put("flags", com.google.protobuf.Value.newBuilder().
setStructValue(Struct.newBuilder().putAllFields(flagsMap)).build());

EventStreamResponse eResponse = EventStreamResponse.newBuilder()
.setType("configuration_change")
.setData(Struct.newBuilder().putAllFields(structMap).build())
.build();

// should cache results
FlagEvaluationDetails<Boolean> booleanDetails;
FlagEvaluationDetails<String> stringDetails;
Expand Down Expand Up @@ -726,11 +720,6 @@ void disabled_cache() {
structMap.put("flags", com.google.protobuf.Value.newBuilder().
setStructValue(Struct.newBuilder().putAllFields(flagsMap)).build());

EventStreamResponse eResponse = EventStreamResponse.newBuilder()
.setType("configuration_change")
.setData(Struct.newBuilder().putAllFields(structMap).build())
.build();

// should not cache results
FlagEvaluationDetails<Boolean> booleanDetails = api.getClient().getBooleanDetails(FLAG_KEY_BOOLEAN, false);
FlagEvaluationDetails<String> stringDetails = api.getClient().getStringDetails(FLAG_KEY_STRING, "wrong");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

/**
* Class for running the tests associated with "stable" e2e tests (no fake disconnection) for the in-process provider
*/
@Order(value = Integer.MAX_VALUE)
@Suite
@IncludeEngines("cucumber")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.openfeature.contrib.providers.flagd.e2e;

import org.junit.jupiter.api.Order;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

/**
* Class for running the reconnection tests for the in-process provider
*/
@Order(value = Integer.MAX_VALUE)
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features/flagd-reconnect.feature")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.reconnect.process,dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps")
public class RunFlagdInProcessReconnectCucumberTest {

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

/**
* Class for running the tests associated with "stable" e2e tests (no fake disconnection) for the RPC provider
*/
@Order(value = Integer.MAX_VALUE)
@Suite
@IncludeEngines("cucumber")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.openfeature.contrib.providers.flagd.e2e;

import org.apache.logging.log4j.core.config.Order;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

/**
* Class for running the reconnection tests for the RPC provider
*/
@Order(value = Integer.MAX_VALUE)
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features/flagd-reconnect.feature")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "dev.openfeature.contrib.providers.flagd.e2e.reconnect.rpc,dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps")
public class RunFlagdRpcReconnectCucumberTest {

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.openfeature.contrib.providers.flagd.e2e.reconnect.process;

import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.parallel.Isolated;

import dev.openfeature.contrib.providers.flagd.Config;
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
import dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps.StepDefinitions;
import dev.openfeature.sdk.FeatureProvider;
import io.cucumber.java.BeforeAll;

@Isolated()
@Order(value = Integer.MAX_VALUE)
public class FlagdInProcessSetup {

private static FeatureProvider provider;

@BeforeAll()
public static void setup() throws InterruptedException {
FlagdInProcessSetup.provider = new FlagdProvider(FlagdOptions.builder()
.resolverType(Config.Evaluator.IN_PROCESS)
.deadline(3000)
.host("localhost")
.port(9091)
.build());
StepDefinitions.setProvider(provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dev.openfeature.contrib.providers.flagd.e2e.reconnect.rpc;

import dev.openfeature.contrib.providers.flagd.resolver.grpc.cache.CacheType;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.parallel.Isolated;

import dev.openfeature.contrib.providers.flagd.Config;
import dev.openfeature.contrib.providers.flagd.FlagdOptions;
import dev.openfeature.contrib.providers.flagd.FlagdProvider;
import dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps.StepDefinitions;
import dev.openfeature.sdk.FeatureProvider;
import io.cucumber.java.BeforeAll;

@Isolated()
@Order(value = Integer.MAX_VALUE)
public class FlagdRpcSetup {

private static FeatureProvider provider;

@BeforeAll()
public static void setup() {
FlagdRpcSetup.provider = new FlagdProvider(FlagdOptions.builder()
.resolverType(Config.Evaluator.RPC)
.port(8014)
// set a generous deadline, to prevent timeouts in actions
.deadline(3000)
.cacheType(CacheType.DISABLED.getValue())
.build());
StepDefinitions.setProvider(provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package dev.openfeature.contrib.providers.flagd.e2e.reconnect.steps;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Duration;
import java.util.function.Consumer;

import org.awaitility.Awaitility;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.parallel.Isolated;

import dev.openfeature.sdk.Client;
import dev.openfeature.sdk.EventDetails;
import dev.openfeature.sdk.FeatureProvider;
import dev.openfeature.sdk.OpenFeatureAPI;
import io.cucumber.java.AfterAll;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

/**
* Test suite for testing flagd provider reconnect functionality.
* The associated container run a flagd instance which restarts every 5s.
*/
@Isolated()
@Order(value = Integer.MAX_VALUE)
public class StepDefinitions {

private static Client client;
private static FeatureProvider provider;

private int readyHandlerRunCount = 0;
private int errorHandlerRunCount = 0;

private Consumer<EventDetails> readyHandler = (EventDetails) -> {
readyHandlerRunCount++;
};
private Consumer<EventDetails> errorHandler = (EventDetails) -> {
errorHandlerRunCount++;
};

/**
* Injects the client to use for this test.
* Tests run one at a time, but just in case, a lock is used to make sure the
* client is not updated mid-test.
*
* @param client client to inject into test.
*/
public static void setProvider(FeatureProvider provider) {
StepDefinitions.provider = provider;
}

public StepDefinitions() {
StepDefinitions.client = OpenFeatureAPI.getInstance().getClient("unstable");
OpenFeatureAPI.getInstance().setProviderAndWait("unstable", provider);
}

@Given("a flagd provider is set")
public static void setup() {
// done in constructor
}

@AfterAll()
public static void cleanUp() throws InterruptedException {
StepDefinitions.provider.shutdown();
StepDefinitions.provider = null;
StepDefinitions.client = null;
}

@When("a PROVIDER_READY handler and a PROVIDER_ERROR handler are added")
public void a_provider_ready_handler_and_a_provider_error_handler_are_added() {
client.onProviderReady(this.readyHandler);
client.onProviderError(this.errorHandler);
}

@Then("the PROVIDER_READY handler must run when the provider connects")
public void the_provider_ready_handler_must_run_when_the_provider_connects() {
// no errors expected yet
assertEquals(0, errorHandlerRunCount);
// wait up to 15 seconds for a connect (PROVIDER_READY event)
Awaitility.await().atMost(Duration.ofSeconds(15))
.until(() -> {
return this.readyHandlerRunCount == 1;
});

}

@Then("the PROVIDER_ERROR handler must run when the provider's connection is lost")
public void the_provider_error_handler_must_run_when_the_provider_s_connection_is_lost() {
// wait up to 15 seconds for a disconnect (PROVIDER_ERROR event)
Awaitility.await().atMost(Duration.ofSeconds(15))
.until(() -> {
return this.errorHandlerRunCount > 0;
});
}

@Then("when the connection is reestablished the PROVIDER_READY handler must run again")
public void when_the_connection_is_reestablished_the_provider_ready_handler_must_run_again() {
// wait up to 15 seconds for a reconnect (PROVIDER_READY event)
Awaitility.await().atMost(Duration.ofSeconds(15))
.until(() -> {
return this.readyHandlerRunCount > 1;
});
}
}
Loading

0 comments on commit c22b90e

Please sign in to comment.