diff --git a/src/main/java/dev/openfeature/sdk/EventSupport.java b/src/main/java/dev/openfeature/sdk/EventSupport.java index 1a7c4d870..1458a610f 100644 --- a/src/main/java/dev/openfeature/sdk/EventSupport.java +++ b/src/main/java/dev/openfeature/sdk/EventSupport.java @@ -2,6 +2,7 @@ import dev.openfeature.sdk.internal.ConfigurableThreadFactory; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -117,7 +118,7 @@ public void removeGlobalHandler(ProviderEvent event, Consumer hand * @return set of domain names */ public Set getAllDomainNames() { - return this.handlerStores.keySet(); + return new HashSet<>(this.handlerStores.keySet()); } /** diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java index 2fdb4e3f0..6906ae67f 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java @@ -1,16 +1,22 @@ package dev.openfeature.sdk; +import static dev.openfeature.sdk.ProviderEvent.PROVIDER_CONFIGURATION_CHANGED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import dev.openfeature.sdk.testutils.testProvider.TestProvider; import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class OpenFeatureAPITest { @@ -115,4 +121,42 @@ void featureProviderTrackIsCalled() throws Exception { verify(featureProvider, times(2)).getMetadata(); verify(featureProvider).track(any(), any(), any()); } + + @Test + @DisplayName("Domainless provider initialized after domain provider - OnConfigurationChanged handlers are invoked for both providers after emitting PROVIDER_CONFIGURATION_CHANGED") + void onConfigurationChangedAreCalledForDomainlessAndDomainProviders() throws InterruptedException { + EventProvider domainlessFeatureProvider = spy(EventProvider.class); + EventProvider featureProvider = spy(EventProvider.class); + + Client domainlessClient = api.getClient(); + Client client = api.getClient("domain"); + + CountDownLatch domainlessLatch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); + + domainlessClient.onProviderConfigurationChanged( + eventDetails -> domainlessLatch.countDown() + ); + client.onProviderConfigurationChanged( + eventDetails -> latch.countDown() + ); + + api.setProviderAndWait("domain", featureProvider); + api.setProviderAndWait(domainlessFeatureProvider); + + featureProvider.emit(PROVIDER_CONFIGURATION_CHANGED, mock(ProviderEventDetails.class)); + domainlessFeatureProvider.emit(PROVIDER_CONFIGURATION_CHANGED, mock(ProviderEventDetails.class)); + + boolean domainlessCompleted = domainlessLatch.await(5, TimeUnit.SECONDS); + assertTrue( + domainlessCompleted, + "onProviderConfigurationChanged was not be invoked for a client with default domain" + ); + + boolean completed = latch.await(5, TimeUnit.SECONDS); + assertTrue( + completed, + "onProviderConfigurationChanged was not be invoked for a client with domain 'domain'" + ); + } }