diff --git a/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java b/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java index c8e2d5f16..d9972a087 100644 --- a/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java +++ b/src/main/java/io/getunleash/metric/UnleashMetricServiceImpl.java @@ -2,14 +2,13 @@ import io.getunleash.util.UnleashConfig; import io.getunleash.util.UnleashScheduledExecutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.HttpURLConnection; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class UnleashMetricServiceImpl implements UnleashMetricService { private static final Logger LOGGER = LoggerFactory.getLogger(UnleashMetricServiceImpl.class); @@ -38,7 +37,14 @@ public UnleashMetricServiceImpl( this.started = LocalDateTime.now(ZoneId.of("UTC")); this.unleashConfig = unleashConfig; this.metricSender = metricSender; - this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getSendMetricsInterval()).intValue(), 1)); + this.maxInterval = + Integer.max( + 20, + 300 + / Integer.max( + Long.valueOf(unleashConfig.getSendMetricsInterval()) + .intValue(), + 1)); long metricsInterval = unleashConfig.getSendMetricsInterval(); executor.setInterval(sendMetrics(), metricsInterval, metricsInterval); } @@ -86,17 +92,30 @@ private void handleHttpErrorCodes(int responseCode) { if (responseCode == 404) { interval.set(maxInterval); failures.incrementAndGet(); - LOGGER.error("Server said that the Metrics receiving endpoint at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashURLs().getClientMetricsURL(), maxInterval); + LOGGER.error( + "Server said that the Metrics receiving endpoint at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", + unleashConfig.getUnleashURLs().getClientMetricsURL(), + maxInterval); } else if (responseCode == 429) { interval.set(Math.min(failures.incrementAndGet(), maxInterval)); - LOGGER.info("Client Metrics was RATE LIMITED for the {}. time. Further backing off. Current backoff at {} times our metrics post interval", failures.get(), interval.get()); - } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED || responseCode == HttpURLConnection.HTTP_FORBIDDEN) { + LOGGER.info( + "Client Metrics was RATE LIMITED for the {}. time. Further backing off. Current backoff at {} times our metrics post interval", + failures.get(), + interval.get()); + } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED + || responseCode == HttpURLConnection.HTTP_FORBIDDEN) { failures.incrementAndGet(); interval.set(maxInterval); - LOGGER.error("Client was not authorized to post metrics to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashURLs().getClientMetricsURL(), maxInterval); + LOGGER.error( + "Client was not authorized to post metrics to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", + unleashConfig.getUnleashURLs().getClientMetricsURL(), + maxInterval); } else if (responseCode >= 500) { interval.set(Math.min(failures.incrementAndGet(), maxInterval)); - LOGGER.info("Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", responseCode, interval.get()); + LOGGER.info( + "Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", + responseCode, + interval.get()); } } diff --git a/src/main/java/io/getunleash/repository/FeatureRepository.java b/src/main/java/io/getunleash/repository/FeatureRepository.java index e1a85fdcb..b81338349 100644 --- a/src/main/java/io/getunleash/repository/FeatureRepository.java +++ b/src/main/java/io/getunleash/repository/FeatureRepository.java @@ -8,18 +8,16 @@ import io.getunleash.lang.Nullable; import io.getunleash.util.UnleashConfig; import io.getunleash.util.UnleashScheduledExecutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.HttpURLConnection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FeatureRepository implements IFeatureRepository { - private final Integer maxInterval; // Set to 20 times our polling interval, so with our default of 15 seconds, the longest interval will be 5 minutes. private static final Logger LOGGER = LoggerFactory.getLogger(FeatureRepository.class); private final UnleashConfig unleashConfig; private final BackupHandler featureBackupHandler; @@ -32,52 +30,74 @@ public class FeatureRepository implements IFeatureRepository { private AtomicInteger failures = new AtomicInteger(0); private AtomicInteger interval = new AtomicInteger(0); + private final Integer maxInterval; public FeatureRepository(UnleashConfig unleashConfig) { this(unleashConfig, new FeatureBackupHandlerFile(unleashConfig)); } public FeatureRepository( - UnleashConfig unleashConfig, - final BackupHandler featureBackupHandler) { + UnleashConfig unleashConfig, + final BackupHandler featureBackupHandler) { this.unleashConfig = unleashConfig; this.featureBackupHandler = featureBackupHandler; this.featureFetcher = unleashConfig.getUnleashFeatureFetcherFactory().apply(unleashConfig); this.featureBootstrapHandler = new FeatureBootstrapHandler(unleashConfig); this.eventDispatcher = new EventDispatcher(unleashConfig); - this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1)); + this.maxInterval = + Integer.max( + 20, + 300 + / Integer.max( + Long.valueOf(unleashConfig.getFetchTogglesInterval()) + .intValue(), + 1)); this.initCollections(unleashConfig.getScheduledExecutor()); } protected FeatureRepository( - UnleashConfig unleashConfig, - BackupHandler featureBackupHandler, - EventDispatcher eventDispatcher, - FeatureFetcher featureFetcher, - FeatureBootstrapHandler featureBootstrapHandler) { + UnleashConfig unleashConfig, + BackupHandler featureBackupHandler, + EventDispatcher eventDispatcher, + FeatureFetcher featureFetcher, + FeatureBootstrapHandler featureBootstrapHandler) { this.unleashConfig = unleashConfig; this.featureBackupHandler = featureBackupHandler; this.featureFetcher = featureFetcher; this.featureBootstrapHandler = featureBootstrapHandler; this.eventDispatcher = eventDispatcher; - this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1)); + this.maxInterval = + Integer.max( + 20, + 300 + / Integer.max( + Long.valueOf(unleashConfig.getFetchTogglesInterval()) + .intValue(), + 1)); this.initCollections(unleashConfig.getScheduledExecutor()); } protected FeatureRepository( - UnleashConfig unleashConfig, - FeatureBackupHandlerFile featureBackupHandler, - UnleashScheduledExecutor executor, - FeatureFetcher featureFetcher, - FeatureBootstrapHandler featureBootstrapHandler) { + UnleashConfig unleashConfig, + FeatureBackupHandlerFile featureBackupHandler, + UnleashScheduledExecutor executor, + FeatureFetcher featureFetcher, + FeatureBootstrapHandler featureBootstrapHandler) { this.unleashConfig = unleashConfig; this.featureBackupHandler = featureBackupHandler; this.featureFetcher = featureFetcher; this.featureBootstrapHandler = featureBootstrapHandler; this.eventDispatcher = new EventDispatcher(unleashConfig); - this.maxInterval = Integer.max(20, 300 / Integer.max(Long.valueOf(unleashConfig.getFetchTogglesInterval()).intValue(), 1)); + this.maxInterval = + Integer.max( + 20, + 300 + / Integer.max( + Long.valueOf(unleashConfig.getFetchTogglesInterval()) + .intValue(), + 1)); this.initCollections(executor); } @@ -115,25 +135,23 @@ private void updateFeaturesInternal(@Nullable final Consumer h if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) { SegmentCollection segmentCollection = response.getSegmentCollection(); featureCollection = - new FeatureCollection( - response.getToggleCollection(), - segmentCollection != null - ? segmentCollection - : new SegmentCollection(Collections.emptyList())); + new FeatureCollection( + response.getToggleCollection(), + segmentCollection != null + ? segmentCollection + : new SegmentCollection(Collections.emptyList())); featureBackupHandler.write(featureCollection); } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { handleHttpErrorCodes(response.getHttpStatusCode()); return; } - - interval.set(Math.max(failures.decrementAndGet(),0)); + interval.set(Math.max(failures.decrementAndGet(), 0)); if (!ready) { eventDispatcher.dispatch(new UnleashReady()); ready = true; } } catch (UnleashException e) { - interval.set(Math.min(failures.incrementAndGet(), maxInterval)); if (handler != null) { handler.accept(e); } else { @@ -149,17 +167,30 @@ private void handleHttpErrorCodes(int responseCode) { if (responseCode == 404) { interval.set(maxInterval); failures.incrementAndGet(); - LOGGER.error("Server said that the API at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashAPI(), maxInterval); + LOGGER.error( + "Server said that the API at {} does not exist. Backing off to {} times our poll interval to avoid overloading server", + unleashConfig.getUnleashAPI(), + maxInterval); } else if (responseCode == 429) { interval.set(Math.min(failures.incrementAndGet(), maxInterval)); - LOGGER.info("Client was RATE LIMITED for the {} time. Further backing off. Current backoff at {} times our poll interval", failures.get(), interval.get()); - } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED || responseCode == HttpURLConnection.HTTP_FORBIDDEN) { + LOGGER.info( + "Client was RATE LIMITED for the {} time. Further backing off. Current backoff at {} times our poll interval", + failures.get(), + interval.get()); + } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED + || responseCode == HttpURLConnection.HTTP_FORBIDDEN) { failures.incrementAndGet(); interval.set(maxInterval); - LOGGER.error("Client failed to authenticate to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", unleashConfig.getUnleashAPI(), maxInterval); + LOGGER.error( + "Client failed to authenticate to the Unleash API at {}. Backing off to {} times our poll interval to avoid overloading server", + unleashConfig.getUnleashAPI(), + maxInterval); } else if (responseCode >= 500) { interval.set(Math.min(failures.incrementAndGet(), maxInterval)); - LOGGER.info("Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", responseCode, interval.get()); + LOGGER.info( + "Server failed with a {} status code. Backing off. Current backoff at {} times our poll interval", + responseCode, + interval.get()); } } @@ -171,8 +202,8 @@ private void handleHttpErrorCodes(int responseCode) { @Override public List getFeatureNames() { return featureCollection.getToggleCollection().getFeatures().stream() - .map(FeatureToggle::getName) - .collect(Collectors.toList()); + .map(FeatureToggle::getName) + .collect(Collectors.toList()); } @Override diff --git a/src/main/java/io/getunleash/variant/VariantUtil.java b/src/main/java/io/getunleash/variant/VariantUtil.java index f2c71493b..0a0fd5655 100644 --- a/src/main/java/io/getunleash/variant/VariantUtil.java +++ b/src/main/java/io/getunleash/variant/VariantUtil.java @@ -133,5 +133,4 @@ public static Variant selectVariant( } return null; } - } diff --git a/src/test/java/io/getunleash/DefaultUnleashTest.java b/src/test/java/io/getunleash/DefaultUnleashTest.java index 2f620735e..44f329272 100644 --- a/src/test/java/io/getunleash/DefaultUnleashTest.java +++ b/src/test/java/io/getunleash/DefaultUnleashTest.java @@ -243,7 +243,6 @@ public void asynchronous_fetch_on_initialisation_fails_silently_and_retries() when(fetcher.fetchFeatures()) .thenThrow(UnleashException.class) .thenReturn(new ClientFeaturesResponse(expectedStatus, expectedResponse)); - UnleashConfig config = UnleashConfig.builder() .unleashAPI("http://wrong:4242") diff --git a/src/test/java/io/getunleash/event/SubscriberTest.java b/src/test/java/io/getunleash/event/SubscriberTest.java index de76ead65..b190ac364 100644 --- a/src/test/java/io/getunleash/event/SubscriberTest.java +++ b/src/test/java/io/getunleash/event/SubscriberTest.java @@ -1,7 +1,8 @@ package io.getunleash.event; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static io.getunleash.repository.FeatureToggleResponse.Status.UNAVAILABLE; +import static io.getunleash.repository.FeatureToggleResponse.Status.CHANGED; import static org.assertj.core.api.Assertions.assertThat; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; @@ -44,19 +45,29 @@ void setup() { } @Test - void subscriberAreNotified() { + void subscribersAreNotified() { + serverMock.stubFor(post("/client/register").willReturn(ok())); + serverMock.stubFor(post("/client/metrics").willReturn(ok())); + serverMock.stubFor( + get("/client/features") + .willReturn( + ok().withHeader("Content-Type", "application/json") + .withBody("{\"features\": [], \"version\": 2 }"))); Unleash unleash = new DefaultUnleash(unleashConfig); unleash.isEnabled("myFeature"); unleash.isEnabled("myFeature"); unleash.isEnabled("myFeature"); - assertThat(testSubscriber.togglesFetchedCounter).isEqualTo(2); // one forced, one scheduled - assertThat(testSubscriber.status).isEqualTo(UNAVAILABLE); + assertThat(testSubscriber.togglesFetchedCounter) + .isEqualTo( + 2); // Server successfully returns, we call synchronous fetch and schedule + // once, so 2 calls. + assertThat(testSubscriber.status).isEqualTo(CHANGED); assertThat(testSubscriber.toggleEvaluatedCounter).isEqualTo(3); assertThat(testSubscriber.toggleName).isEqualTo("myFeature"); assertThat(testSubscriber.toggleEnabled).isFalse(); - assertThat(testSubscriber.errors).hasSize(2); + assertThat(testSubscriber.errors).isEmpty(); // assertThat(testSubscriber.events).filteredOn(e -> e instanceof // ToggleBootstrapHandler.ToggleBootstrapRead).hasSize(1); diff --git a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java index a82940321..975a015a4 100644 --- a/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java +++ b/src/test/java/io/getunleash/metric/UnleashMetricServiceImplTest.java @@ -3,14 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; -import io.getunleash.repository.ClientFeaturesResponse; -import io.getunleash.repository.FeatureToggleResponse; import io.getunleash.util.UnleashConfig; import io.getunleash.util.UnleashScheduledExecutor; - import java.util.HashSet; import java.util.Set; - import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -20,11 +16,11 @@ public class UnleashMetricServiceImplTest { public void should_register_future_for_sending_interval_regualry() { long interval = 10; UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(interval) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(interval) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashMetricService unleashMetricService = new UnleashMetricServiceImpl(config, executor); @@ -35,24 +31,24 @@ public void should_register_future_for_sending_interval_regualry() { public void should_register_client() { long interval = 10; UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(interval) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(interval) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricService unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); Set strategies = new HashSet<>(); strategies.add("default"); strategies.add("custom"); unleashMetricService.register(strategies); ArgumentCaptor argument = - ArgumentCaptor.forClass(ClientRegistration.class); + ArgumentCaptor.forClass(ClientRegistration.class); verify(sender).registerClient(argument.capture()); assertThat(argument.getValue().getAppName()).isEqualTo(config.getAppName()); @@ -66,25 +62,25 @@ public void should_register_client() { public void should_register_client_with_env() { long interval = 10; UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .environment("dev") - .sendMetricsInterval(interval) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .environment("dev") + .sendMetricsInterval(interval) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricService unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); Set strategies = new HashSet<>(); strategies.add("default"); strategies.add("custom"); unleashMetricService.register(strategies); ArgumentCaptor argument = - ArgumentCaptor.forClass(ClientRegistration.class); + ArgumentCaptor.forClass(ClientRegistration.class); verify(sender).registerClient(argument.capture()); assertThat(argument.getValue().getEnvironment()).isEqualTo(config.getEnvironment()); @@ -93,17 +89,17 @@ public void should_register_client_with_env() { @Test public void should_send_metrics() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricService unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class); verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong()); @@ -115,18 +111,18 @@ public void should_send_metrics() { @Test public void should_record_and_send_metrics() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .environment("prod") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .environment("prod") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricService unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.count("someToggle", true); unleashMetricService.count("someToggle", false); unleashMetricService.count("someToggle", true); @@ -138,7 +134,7 @@ public void should_record_and_send_metrics() { sendMetricsCallback.getValue().run(); ArgumentCaptor clientMetricsArgumentCaptor = - ArgumentCaptor.forClass(ClientMetrics.class); + ArgumentCaptor.forClass(ClientMetrics.class); verify(sender).sendMetrics(clientMetricsArgumentCaptor.capture()); ClientMetrics clientMetrics = clientMetricsArgumentCaptor.getValue(); @@ -157,17 +153,17 @@ public void should_record_and_send_metrics() { @Test public void should_record_and_send_variant_metrics() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricService unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); @@ -180,7 +176,7 @@ public void should_record_and_send_variant_metrics() { sendMetricsCallback.getValue().run(); ArgumentCaptor clientMetricsArgumentCaptor = - ArgumentCaptor.forClass(ClientMetrics.class); + ArgumentCaptor.forClass(ClientMetrics.class); verify(sender).sendMetrics(clientMetricsArgumentCaptor.capture()); ClientMetrics clientMetrics = clientMetricsArgumentCaptor.getValue(); @@ -192,11 +188,11 @@ public void should_record_and_send_variant_metrics() { assertThat(bucket.getStop()).isNotNull(); assertThat(bucket.getToggles()).hasSize(1); assertThat(bucket.getToggles().get("someToggle").getVariants().get("v1").longValue()) - .isEqualTo(3l); + .isEqualTo(3l); assertThat(bucket.getToggles().get("someToggle").getVariants().get("v2").longValue()) - .isEqualTo(1l); + .isEqualTo(1l); assertThat(bucket.getToggles().get("someToggle").getVariants().get("disabled").longValue()) - .isEqualTo(1l); + .isEqualTo(1l); assertThat(bucket.getToggles().get("someToggle").getYes()).isEqualTo(0l); assertThat(bucket.getToggles().get("someToggle").getNo()).isEqualTo(0l); } @@ -204,17 +200,17 @@ public void should_record_and_send_variant_metrics() { @Test public void should_backoff_when_told_to_by_429_code() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricServiceImpl unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); @@ -225,7 +221,14 @@ public void should_backoff_when_told_to_by_429_code() { ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class); verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong()); - when(sender.sendMetrics(any(ClientMetrics.class))).thenReturn(429).thenReturn(429).thenReturn(429).thenReturn(200).thenReturn(200).thenReturn(200).thenReturn(200); + when(sender.sendMetrics(any(ClientMetrics.class))) + .thenReturn(429) + .thenReturn(429) + .thenReturn(429) + .thenReturn(200) + .thenReturn(200) + .thenReturn(200) + .thenReturn(200); sendMetricsCallback.getValue().run(); assertThat(unleashMetricService.getInterval()).isEqualTo(1); @@ -269,17 +272,17 @@ public void should_backoff_when_told_to_by_429_code() { @Test public void server_errors_should_also_incrementally_backoff() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricServiceImpl unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); @@ -290,13 +293,13 @@ public void server_errors_should_also_incrementally_backoff() { ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class); verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong()); when(sender.sendMetrics(any(ClientMetrics.class))) - .thenReturn(500) - .thenReturn(502) - .thenReturn(503) - .thenReturn(304) - .thenReturn(304) - .thenReturn(304) - .thenReturn(304); + .thenReturn(500) + .thenReturn(502) + .thenReturn(503) + .thenReturn(304) + .thenReturn(304) + .thenReturn(304) + .thenReturn(304); sendMetricsCallback.getValue().run(); assertThat(unleashMetricService.getInterval()).isEqualTo(1); assertThat(unleashMetricService.getFailures()).isEqualTo(1); @@ -339,17 +342,17 @@ public void server_errors_should_also_incrementally_backoff() { @Test public void failure_to_authenticate_immediately_increases_interval_to_max() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricServiceImpl unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); @@ -359,9 +362,7 @@ public void failure_to_authenticate_immediately_increases_interval_to_max() { // Call the sendMetricsCallback ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class); verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong()); - when(sender.sendMetrics(any(ClientMetrics.class))) - .thenReturn(403) - .thenReturn(200); + when(sender.sendMetrics(any(ClientMetrics.class))).thenReturn(403).thenReturn(200); sendMetricsCallback.getValue().run(); assertThat(unleashMetricService.getInterval()).isEqualTo(30); assertThat(unleashMetricService.getFailures()).isEqualTo(1); @@ -378,17 +379,17 @@ public void failure_to_authenticate_immediately_increases_interval_to_max() { @Test public void url_not_found_immediately_increases_interval_to_max() { UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .sendMetricsInterval(10) - .unleashAPI("http://unleash.com") - .build(); + UnleashConfig.builder() + .appName("test") + .sendMetricsInterval(10) + .unleashAPI("http://unleash.com") + .build(); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); DefaultHttpMetricsSender sender = mock(DefaultHttpMetricsSender.class); UnleashMetricServiceImpl unleashMetricService = - new UnleashMetricServiceImpl(config, sender, executor); + new UnleashMetricServiceImpl(config, sender, executor); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); unleashMetricService.countVariant("someToggle", "v1"); @@ -398,9 +399,7 @@ public void url_not_found_immediately_increases_interval_to_max() { // Call the sendMetricsCallback ArgumentCaptor sendMetricsCallback = ArgumentCaptor.forClass(Runnable.class); verify(executor).setInterval(sendMetricsCallback.capture(), anyLong(), anyLong()); - when(sender.sendMetrics(any(ClientMetrics.class))) - .thenReturn(404) - .thenReturn(200); + when(sender.sendMetrics(any(ClientMetrics.class))).thenReturn(404).thenReturn(200); sendMetricsCallback.getValue().run(); assertThat(unleashMetricService.getInterval()).isEqualTo(30); assertThat(unleashMetricService.getFailures()).isEqualTo(1); diff --git a/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java b/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java index 0eb5a4cc9..3a0afd6ff 100644 --- a/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java +++ b/src/test/java/io/getunleash/repository/FeatureRepositoryTest.java @@ -1,7 +1,5 @@ package io.getunleash.repository; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -11,24 +9,16 @@ import io.getunleash.lang.Nullable; import io.getunleash.util.UnleashConfig; import io.getunleash.util.UnleashScheduledExecutor; - import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.*; - -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -48,24 +38,24 @@ public void setUp() { fetcher = mock(HttpFeatureFetcher.class); defaultConfig = - new UnleashConfig.Builder() - .appName("test") - .unleashAPI("http://localhost:4242/api/") - .scheduledExecutor(mock(UnleashScheduledExecutor.class)) - .fetchTogglesInterval(200L) - .synchronousFetchOnInitialisation(false) - .build(); + new UnleashConfig.Builder() + .appName("test") + .unleashAPI("http://localhost:4242/api/") + .scheduledExecutor(mock(UnleashScheduledExecutor.class)) + .fetchTogglesInterval(200L) + .synchronousFetchOnInitialisation(false) + .build(); } @Test public void no_backup_file_and_no_repository_available_should_give_empty_repo() { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .unleashAPI("http://localhost:4242/api/") - .scheduledExecutor(executor) - .build(); + UnleashConfig.builder() + .appName("test") + .unleashAPI("http://localhost:4242/api/") + .scheduledExecutor(executor) + .build(); when(backupHandler.read()).thenReturn(new FeatureCollection()); when(bootstrapHandler.read()).thenReturn(new FeatureCollection()); @@ -77,21 +67,21 @@ public void no_backup_file_and_no_repository_available_should_give_empty_repo() public void backup_toggles_should_be_loaded_at_startup() { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .appName("test") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:4242/api/") - .fetchTogglesInterval(Long.MAX_VALUE) - .build(); + UnleashConfig.builder() + .appName("test") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:4242/api/") + .fetchTogglesInterval(Long.MAX_VALUE) + .build(); when(backupHandler.read()) - .thenReturn( - new FeatureCollection( - new ToggleCollection(Collections.emptyList()), - new SegmentCollection(Collections.emptyList()))); + .thenReturn( + new FeatureCollection( + new ToggleCollection(Collections.emptyList()), + new SegmentCollection(Collections.emptyList()))); new FeatureRepository( - config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); verify(backupHandler, times(1)).read(); } @@ -102,37 +92,37 @@ public void feature_toggles_should_be_updated() { ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); UnleashConfig config = - new UnleashConfig.Builder() - .appName("test") - .unleashAPI("http://localhost:4242/api/") - .scheduledExecutor(executor) - .fetchTogglesInterval(200L) - .synchronousFetchOnInitialisation(false) - .build(); + new UnleashConfig.Builder() + .appName("test") + .unleashAPI("http://localhost:4242/api/") + .scheduledExecutor(executor) + .fetchTogglesInterval(200L) + .synchronousFetchOnInitialisation(false) + .build(); FeatureCollection featureCollection = - populatedFeatureCollection( - null, - new FeatureToggle( - "toggleFetcherCalled", - false, - Arrays.asList(new ActivationStrategy("custom", null)))); + populatedFeatureCollection( + null, + new FeatureToggle( + "toggleFetcherCalled", + false, + Arrays.asList(new ActivationStrategy("custom", null)))); when(backupHandler.read()).thenReturn(featureCollection); featureCollection = - populatedFeatureCollection( - null, - new FeatureToggle( - "toggleFetcherCalled", - true, - Arrays.asList(new ActivationStrategy("custom", null)))); + populatedFeatureCollection( + null, + new FeatureToggle( + "toggleFetcherCalled", + true, + Arrays.asList(new ActivationStrategy("custom", null)))); ClientFeaturesResponse response = - new ClientFeaturesResponse( - ClientFeaturesResponse.Status.CHANGED, featureCollection); + new ClientFeaturesResponse( + ClientFeaturesResponse.Status.CHANGED, featureCollection); FeatureRepository featureRepository = - new FeatureRepository(config, backupHandler, executor, fetcher, bootstrapHandler); + new FeatureRepository(config, backupHandler, executor, fetcher, bootstrapHandler); // run the toggleName fetcher callback verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong()); verify(fetcher, times(0)).fetchFeatures(); @@ -148,25 +138,25 @@ public void feature_toggles_should_be_updated() { @Test public void get_feature_names_should_return_list_of_names() { FeatureCollection featureCollection = - populatedFeatureCollection( - null, - new FeatureToggle( - "toggleFeatureName1", - true, - Arrays.asList(new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Arrays.asList(new ActivationStrategy("custom", null)))); + populatedFeatureCollection( + null, + new FeatureToggle( + "toggleFeatureName1", + true, + Arrays.asList(new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Arrays.asList(new ActivationStrategy("custom", null)))); when(backupHandler.read()).thenReturn(featureCollection); FeatureRepository featureRepository = - new FeatureRepository( - defaultConfig, - backupHandler, - new EventDispatcher(defaultConfig), - fetcher, - bootstrapHandler); + new FeatureRepository( + defaultConfig, + backupHandler, + new EventDispatcher(defaultConfig), + fetcher, + bootstrapHandler); assertEquals(2, featureRepository.getFeatureNames().size()); assertEquals("toggleFeatureName2", featureRepository.getFeatureNames().get(1)); } @@ -175,23 +165,23 @@ public void get_feature_names_should_return_list_of_names() { public void should_perform_synchronous_fetch_on_initialisation() { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(true) - .scheduledExecutor(executor) - .appName("test-sync-update") - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(true) + .scheduledExecutor(executor) + .appName("test-sync-update") + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()).thenReturn(new FeatureCollection()); FeatureCollection featureCollection = populatedFeatureCollection(null); ClientFeaturesResponse response = - new ClientFeaturesResponse( - ClientFeaturesResponse.Status.CHANGED, featureCollection); + new ClientFeaturesResponse( + ClientFeaturesResponse.Status.CHANGED, featureCollection); when(fetcher.fetchFeatures()).thenReturn(response); new FeatureRepository( - config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); verify(fetcher, times(1)).fetchFeatures(); } @@ -199,19 +189,19 @@ public void should_perform_synchronous_fetch_on_initialisation() { public void should_not_perform_synchronous_fetch_on_initialisation() { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .scheduledExecutor(executor) - .appName("test-sync-update") - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .scheduledExecutor(executor) + .appName("test-sync-update") + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()).thenReturn(new FeatureCollection()); FeatureCollection featureCollection = populatedFeatureCollection(null); ClientFeaturesResponse response = - new ClientFeaturesResponse( - ClientFeaturesResponse.Status.CHANGED, featureCollection); + new ClientFeaturesResponse( + ClientFeaturesResponse.Status.CHANGED, featureCollection); when(fetcher.fetchFeatures()).thenReturn(response); @@ -221,7 +211,7 @@ public void should_not_perform_synchronous_fetch_on_initialisation() { } private FeatureCollection populatedFeatureCollection( - @Nullable List segments, FeatureToggle... featureToggles) { + @Nullable List segments, FeatureToggle... featureToggles) { List toggleList = new ArrayList<>(); toggleList.addAll(Arrays.asList(featureToggles)); @@ -229,27 +219,27 @@ private FeatureCollection populatedFeatureCollection( if (segments != null) segmentList.addAll(segments); return new FeatureCollection( - new ToggleCollection(toggleList), new SegmentCollection(segmentList)); + new ToggleCollection(toggleList), new SegmentCollection(segmentList)); } @Test public void should_read_from_bootstrap_location_if_backup_was_empty() - throws URISyntaxException, IOException { + throws URISyntaxException, IOException { File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .toggleBootstrapProvider(toggleBootstrapProvider) - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .toggleBootstrapProvider(toggleBootstrapProvider) + .build(); when(backupHandler.read()).thenReturn(new FeatureCollection()); @@ -259,91 +249,101 @@ public void should_read_from_bootstrap_location_if_backup_was_empty() @Test public void should_not_read_bootstrap_if_backup_was_found() - throws IOException, URISyntaxException { + throws IOException, URISyntaxException { File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .toggleBootstrapProvider(toggleBootstrapProvider) - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .toggleBootstrapProvider(toggleBootstrapProvider) + .build(); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); when(backupHandler.read()) - .thenReturn( - populatedFeatureCollection( - Arrays.asList( - new Segment( - 1, - "some-name", - Arrays.asList( - new Constraint( - "some-context", - Operator.IN, - "some-value")))), - new FeatureToggle( - "toggleFeatureName1", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))))); + .thenReturn( + populatedFeatureCollection( + Arrays.asList( + new Segment( + 1, + "some-name", + Arrays.asList( + new Constraint( + "some-context", + Operator.IN, + "some-value")))), + new FeatureToggle( + "toggleFeatureName1", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))))); new FeatureRepository( - config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); verify(toggleBootstrapProvider, times(0)).read(); } @Test - public void should_increase_to_max_interval_when_denied() throws URISyntaxException, IOException { + public void should_increase_to_max_interval_when_denied() + throws URISyntaxException, IOException { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()) - .thenReturn( - populatedFeatureCollection( - Arrays.asList( - new Segment( - 1, - "some-name", - Arrays.asList( - new Constraint( - "some-context", - Operator.IN, - "some-value")))), - new FeatureToggle( - "toggleFeatureName1", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))))); - when(fetcher.fetchFeatures()).thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 403)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); - - FeatureRepository featureRepository = new FeatureRepository(config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + .thenReturn( + populatedFeatureCollection( + Arrays.asList( + new Segment( + 1, + "some-name", + Arrays.asList( + new Constraint( + "some-context", + Operator.IN, + "some-value")))), + new FeatureToggle( + "toggleFeatureName1", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))))); + when(fetcher.fetchFeatures()) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 403)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); + + FeatureRepository featureRepository = + new FeatureRepository( + config, + backupHandler, + new EventDispatcher(config), + fetcher, + bootstrapHandler); verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong()); runnableArgumentCaptor.getValue().run(); assertThat(featureRepository.getFailures()).isEqualTo(1); @@ -359,46 +359,56 @@ public void should_increase_to_max_interval_when_denied() throws URISyntaxExcept } @Test - public void should_increase_to_max_interval_when_not_found() throws URISyntaxException, IOException { + public void should_increase_to_max_interval_when_not_found() + throws URISyntaxException, IOException { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()) - .thenReturn( - populatedFeatureCollection( - Arrays.asList( - new Segment( - 1, - "some-name", - Arrays.asList( - new Constraint( - "some-context", - Operator.IN, - "some-value")))), - new FeatureToggle( - "toggleFeatureName1", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))))); - when(fetcher.fetchFeatures()).thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 404)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); - - FeatureRepository featureRepository = new FeatureRepository(config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + .thenReturn( + populatedFeatureCollection( + Arrays.asList( + new Segment( + 1, + "some-name", + Arrays.asList( + new Constraint( + "some-context", + Operator.IN, + "some-value")))), + new FeatureToggle( + "toggleFeatureName1", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))))); + when(fetcher.fetchFeatures()) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 404)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); + + FeatureRepository featureRepository = + new FeatureRepository( + config, + backupHandler, + new EventDispatcher(config), + fetcher, + bootstrapHandler); verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong()); runnableArgumentCaptor.getValue().run(); assertThat(featureRepository.getFailures()).isEqualTo(1); @@ -412,53 +422,68 @@ public void should_increase_to_max_interval_when_not_found() throws URISyntaxExc assertThat(featureRepository.getFailures()).isEqualTo(0); assertThat(featureRepository.getInterval()).isEqualTo(0); } + @Test - public void should_incrementally_increase_interval_as_we_receive_too_many_requests() throws URISyntaxException, IOException { + public void should_incrementally_increase_interval_as_we_receive_too_many_requests() + throws URISyntaxException, IOException { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()) - .thenReturn( - populatedFeatureCollection( - Arrays.asList( - new Segment( - 1, - "some-name", - Arrays.asList( - new Constraint( - "some-context", - Operator.IN, - "some-value")))), - new FeatureToggle( - "toggleFeatureName1", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))))); - FeatureRepository featureRepository = new FeatureRepository(config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + .thenReturn( + populatedFeatureCollection( + Arrays.asList( + new Segment( + 1, + "some-name", + Arrays.asList( + new Constraint( + "some-context", + Operator.IN, + "some-value")))), + new FeatureToggle( + "toggleFeatureName1", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))))); + FeatureRepository featureRepository = + new FeatureRepository( + config, + backupHandler, + new EventDispatcher(config), + fetcher, + bootstrapHandler); verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong()); when(fetcher.fetchFeatures()) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 429)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); runnableArgumentCaptor.getValue().run(); assertThat(featureRepository.getInterval()).isEqualTo(1); assertThat(featureRepository.getFailures()).isEqualTo(1); @@ -499,52 +524,66 @@ public void should_incrementally_increase_interval_as_we_receive_too_many_reques } @Test - public void server_errors_should_incrementally_increase_interval() throws URISyntaxException, IOException { + public void server_errors_should_incrementally_increase_interval() + throws URISyntaxException, IOException { UnleashScheduledExecutor executor = mock(UnleashScheduledExecutor.class); ArgumentCaptor runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); File file = - new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); + new File(getClass().getClassLoader().getResource("unleash-repo-v2.json").toURI()); ToggleBootstrapProvider toggleBootstrapProvider = mock(ToggleBootstrapProvider.class); when(toggleBootstrapProvider.read()).thenReturn(fileToString(file)); UnleashConfig config = - UnleashConfig.builder() - .synchronousFetchOnInitialisation(false) - .appName("test-sync-update") - .scheduledExecutor(executor) - .unleashAPI("http://localhost:8080") - .build(); + UnleashConfig.builder() + .synchronousFetchOnInitialisation(false) + .appName("test-sync-update") + .scheduledExecutor(executor) + .unleashAPI("http://localhost:8080") + .build(); when(backupHandler.read()) - .thenReturn( - populatedFeatureCollection( - Arrays.asList( - new Segment( - 1, - "some-name", - Arrays.asList( - new Constraint( - "some-context", - Operator.IN, - "some-value")))), - new FeatureToggle( - "toggleFeatureName1", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))), - new FeatureToggle( - "toggleFeatureName2", - true, - Collections.singletonList( - new ActivationStrategy("custom", null))))); - FeatureRepository featureRepository = new FeatureRepository(config, backupHandler, new EventDispatcher(config), fetcher, bootstrapHandler); + .thenReturn( + populatedFeatureCollection( + Arrays.asList( + new Segment( + 1, + "some-name", + Arrays.asList( + new Constraint( + "some-context", + Operator.IN, + "some-value")))), + new FeatureToggle( + "toggleFeatureName1", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))), + new FeatureToggle( + "toggleFeatureName2", + true, + Collections.singletonList( + new ActivationStrategy("custom", null))))); + FeatureRepository featureRepository = + new FeatureRepository( + config, + backupHandler, + new EventDispatcher(config), + fetcher, + bootstrapHandler); verify(executor).setInterval(runnableArgumentCaptor.capture(), anyLong(), anyLong()); when(fetcher.fetchFeatures()) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 500)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 502)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 503)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) - .thenReturn(new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 500)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 502)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.UNAVAILABLE, 503)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)) + .thenReturn( + new ClientFeaturesResponse(FeatureToggleResponse.Status.NOT_CHANGED, 304)); runnableArgumentCaptor.getValue().run(); assertThat(featureRepository.getInterval()).isEqualTo(1); assertThat(featureRepository.getFailures()).isEqualTo(1); @@ -587,5 +626,4 @@ public void server_errors_should_incrementally_increase_interval() throws URISyn private String fileToString(File f) throws IOException { return new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8); } - } diff --git a/src/test/java/io/getunleash/variant/VariantUtilTest.java b/src/test/java/io/getunleash/variant/VariantUtilTest.java index ea4e5a4ab..ee431edb6 100644 --- a/src/test/java/io/getunleash/variant/VariantUtilTest.java +++ b/src/test/java/io/getunleash/variant/VariantUtilTest.java @@ -5,7 +5,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import com.sangupta.murmur.Murmur3; import io.getunleash.ActivationStrategy; import io.getunleash.FeatureToggle; import io.getunleash.UnleashContext; @@ -411,6 +410,4 @@ public void feature_variants_variant_d_with_override_client_spec_tests() { toggle, UnleashContext.builder().userId("10").build(), DISABLED_VARIANT); assertThat(variantUser10.getName()).isEqualTo("variant2"); } - - }