Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ dependencies {
testImplementation(libs.bundles.junit5)
testImplementation(libs.bundles.mockito)
testImplementation(libs.moshi)
testImplementation("org.awaitility:awaitility:4.3.0")

// The main source set gets the bootstrap/config types as compileOnly, so the JMH source set
// needs them on its own compile and runtime classpath to drive the hook end to end.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package datadog.trace.api.openfeature;

import static datadog.trace.api.openfeature.Provider.METADATA;
import static java.time.Duration.ofSeconds;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -39,6 +39,7 @@
import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
Expand All @@ -47,18 +48,13 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class ProviderTest {

@Captor private ArgumentCaptor<EventDetails> eventDetailsCaptor;
private static final long EVENT_TIMEOUT_SECONDS = 10;

private ExecutorService executor;

Expand All @@ -77,15 +73,18 @@ public void tearDown() {
}

@Test
public void testSetProvider() {
public void testSetProvider() throws Exception {
final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
final CompletableFuture<EventDetails> readyEvent = new CompletableFuture<>();
api.onProviderReady(readyEvent::complete);
api.setProvider(new Provider());

final Client client = api.getClient();
assertThat(client.getProviderState(), equalTo(ProviderState.NOT_READY));

FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));
await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY);
readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS);
assertThat(client.getProviderState(), equalTo(ProviderState.READY));
}

@Test
Expand All @@ -97,35 +96,31 @@ public void testSetProviderAndWait() throws Exception {
assertThat(client.getProviderState(), equalTo(ProviderState.NOT_READY));

FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));
await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY);
provider.get(1, SECONDS);
provider.get(EVENT_TIMEOUT_SECONDS, SECONDS);
assertThat(client.getProviderState(), equalTo(ProviderState.READY));
}

@Test
public void testSetProviderAndWaitTimeoutRecoversWhenConfigurationArrives() {
final Consumer<EventDetails> readyEvent = mock(Consumer.class);
public void testSetProviderAndWaitTimeoutRecoversWhenConfigurationArrives() throws Exception {
final CompletableFuture<EventDetails> readyEvent = new CompletableFuture<>();
final Consumer<EventDetails> readyEventHandler = completingHandler(readyEvent);
final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
final Client client = api.getClient();
client.on(ProviderEvent.PROVIDER_READY, readyEvent);
client.on(ProviderEvent.PROVIDER_READY, readyEventHandler);

assertThrows(
ProviderNotReadyError.class,
() -> api.setProviderAndWait(new Provider(new Options().initTimeout(10, MILLISECONDS))));

assertThat(client.getProviderState(), equalTo(ProviderState.ERROR));
verify(readyEvent, times(0)).accept(any());
assertFalse(readyEvent.isDone());

FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));

await()
.atMost(ofSeconds(1))
.untilAsserted(
() -> {
assertThat(client.getProviderState(), equalTo(ProviderState.READY));
verify(readyEvent, times(1)).accept(eventDetailsCaptor.capture());
final EventDetails eventDetails = eventDetailsCaptor.getValue();
assertThat(eventDetails.getProviderName(), equalTo(METADATA));
});
final EventDetails eventDetails = readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS);
assertThat(client.getProviderState(), equalTo(ProviderState.READY));
assertThat(eventDetails.getProviderName(), equalTo(METADATA));
verify(readyEventHandler, times(1)).accept(any());
}

@Test
Expand Down Expand Up @@ -266,49 +261,41 @@ public <T> ProviderEvaluation<T> evaluate(
}

@Test
public void testNullConfigurationAfterReadyTransitionsToErrorAndRecovers() {
public void testNullConfigurationAfterReadyTransitionsToErrorAndRecovers() throws Exception {
final OpenFeatureAPI api = OpenFeatureAPI.getInstance();
api.setProvider(new Provider());
final Client client = api.getClient();

FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));
await().atMost(ofSeconds(1)).until(() -> client.getProviderState() == ProviderState.READY);
api.setProviderAndWait(new Provider());
final Client client = api.getClient();
assertThat(client.getProviderState(), equalTo(ProviderState.READY));

final Consumer<EventDetails> errorEvent = mock(Consumer.class);
final Consumer<EventDetails> readyEvent = mock(Consumer.class);
final Consumer<EventDetails> configChangedEvent = mock(Consumer.class);
client.on(ProviderEvent.PROVIDER_ERROR, errorEvent);
client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, configChangedEvent);
final CompletableFuture<EventDetails> errorEvent = new CompletableFuture<>();
final CompletableFuture<EventDetails> readyEvent = new CompletableFuture<>();
final CompletableFuture<EventDetails> configChangedEvent = new CompletableFuture<>();
final Consumer<EventDetails> errorEventHandler = completingHandler(errorEvent);
final Consumer<EventDetails> readyEventHandler = completingHandler(readyEvent);
final Consumer<EventDetails> configChangedEventHandler = completingHandler(configChangedEvent);
client.on(ProviderEvent.PROVIDER_ERROR, errorEventHandler);
client.on(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, configChangedEventHandler);

FeatureFlaggingGateway.dispatch((ServerConfiguration) null);
await()
.atMost(ofSeconds(1))
.untilAsserted(
() -> {
assertThat(client.getProviderState(), equalTo(ProviderState.ERROR));
verify(errorEvent, times(1)).accept(eventDetailsCaptor.capture());
final EventDetails eventDetails = eventDetailsCaptor.getValue();
assertThat(eventDetails.getProviderName(), equalTo(METADATA));
});
final EventDetails eventDetails = errorEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS);
assertThat(client.getProviderState(), equalTo(ProviderState.ERROR));
assertThat(eventDetails.getProviderName(), equalTo(METADATA));

final FlagEvaluationDetails<String> evalDetails = client.getStringDetails("missing", "default");
assertThat(evalDetails.getValue(), equalTo("default"));
assertThat(evalDetails.getErrorCode(), equalTo(ErrorCode.PROVIDER_NOT_READY));

client.on(ProviderEvent.PROVIDER_READY, readyEvent);
client.on(ProviderEvent.PROVIDER_READY, readyEventHandler);
FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));
await()
.atMost(ofSeconds(1))
.untilAsserted(
() -> {
assertThat(client.getProviderState(), equalTo(ProviderState.READY));
verify(readyEvent, times(1)).accept(any());
});
readyEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS);
assertThat(client.getProviderState(), equalTo(ProviderState.READY));

FeatureFlaggingGateway.dispatch(mock(ServerConfiguration.class));
await()
.atMost(ofSeconds(1))
.untilAsserted(() -> verify(configChangedEvent, times(1)).accept(any()));
configChangedEvent.get(EVENT_TIMEOUT_SECONDS, SECONDS);
verify(errorEventHandler, times(1)).accept(any());
verify(readyEventHandler, times(1)).accept(any());
verify(configChangedEventHandler, times(1)).accept(any());
}

@Test
Expand Down Expand Up @@ -479,6 +466,20 @@ private static String initializationState(final Provider provider) throws Except
return state.get().toString();
}

@SuppressWarnings("unchecked")
private static Consumer<EventDetails> completingHandler(
final CompletableFuture<EventDetails> event) {
final Consumer<EventDetails> handler = mock(Consumer.class);
doAnswer(
invocation -> {
event.complete(invocation.getArgument(0));
return null;
})
.when(handler)
.accept(any());
return handler;
}

private static FlagEvaluationWriter capturingWriter(final AtomicReference<FlagEvalEvent> ref) {
return new FlagEvaluationWriter() {
@Override
Expand Down