Skip to content

Commit

Permalink
feat: Add support for fallback-action (#93)
Browse files Browse the repository at this point in the history
* closes #89: to Added first implementation (draft) of fallbackAction

* Migrated to BiFunction for definition of fallback action, refactored methods according to feedback, added tests

* changed one test case to reflect correct implementation

* Added default implementation for isEnabled(String, BiFunction)

* Incorporated feedback, changed logical method layout in DefaultUnleash implementation, added default implementation in Unleash interface to avoid breaking existing implementation

* fix: fallbackAction and defaultSetting

Should not be possible to define both a fallback action and
a defaultSetting at the same time.

* fix: Improve fallbackAction unit-tests

Use mocked functions and verify that the fallBack action
is actually called with correct arguments.

* fix: remove final arguments in interface

* fix: simplify FakeUnleash for fallbackAction

* fix: formatting

* fix: counting at right place
  • Loading branch information
dennis-menge authored and ivarconr committed Feb 20, 2020
1 parent 560e0fb commit 1f18286
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 16 deletions.
29 changes: 20 additions & 9 deletions src/main/java/no/finn/unleash/DefaultUnleash.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;

import no.finn.unleash.event.EventDispatcher;
import no.finn.unleash.event.ToggleEvaluated;
Expand Down Expand Up @@ -40,7 +41,6 @@ public final class DefaultUnleash implements Unleash {
private final EventDispatcher eventDispatcher;
private final UnleashConfig config;


private static FeatureToggleRepository defaultToggleRepository(UnleashConfig unleashConfig) {
return new FeatureToggleRepository(
unleashConfig,
Expand Down Expand Up @@ -74,24 +74,35 @@ public boolean isEnabled(final String toggleName, final boolean defaultSetting)
}

@Override
public boolean isEnabled(final String toggleName, final UnleashContext context ,final boolean defaultSetting) {
FeatureToggle featureToggle = toggleRepository.getToggle(toggleName);
boolean enabled = isEnabled(featureToggle, context, defaultSetting);
public boolean isEnabled(final String toggleName, final UnleashContext context, final boolean defaultSetting) {
return isEnabled(toggleName, context, (n, c) -> defaultSetting);
}

@Override
public boolean isEnabled(final String toggleName, final BiFunction<String, UnleashContext, Boolean> fallbackAction) {
return isEnabled(toggleName, contextProvider.getContext(), fallbackAction);
}

@Override
public boolean isEnabled(String toggleName, UnleashContext context, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
boolean enabled = checkEnabled(toggleName, context, fallbackAction);
count(toggleName, enabled);
eventDispatcher.dispatch(new ToggleEvaluated(toggleName,enabled));
eventDispatcher.dispatch(new ToggleEvaluated(toggleName, enabled));
return enabled;
}

private boolean isEnabled(FeatureToggle featureToggle, UnleashContext context, boolean defaultSetting) {
private boolean checkEnabled(String toggleName, UnleashContext context, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
FeatureToggle featureToggle = toggleRepository.getToggle(toggleName);
boolean enabled;
UnleashContext enhancedContext = context.applyStaticFields(config);

if (featureToggle == null) {
enabled = defaultSetting;
enabled = fallbackAction.apply(toggleName, enhancedContext);
} else if(!featureToggle.isEnabled()) {
enabled = false;
} else if(featureToggle.getStrategies().size() == 0) {
return true;
} else {
UnleashContext enhancedContext = context.applyStaticFields(config);
enabled = featureToggle.getStrategies().stream()
.anyMatch(as -> getStrategy(as.getName()).isEnabled(as.getParameters(), enhancedContext, as.getConstraints()));
}
Expand All @@ -106,7 +117,7 @@ public Variant getVariant(String toggleName, UnleashContext context) {
@Override
public Variant getVariant(String toggleName, UnleashContext context, Variant defaultValue) {
FeatureToggle featureToggle = toggleRepository.getToggle(toggleName);
boolean enabled = isEnabled(featureToggle, context, false);
boolean enabled = checkEnabled(toggleName, context, (n, c) -> false);
Variant variant = enabled ? selectVariant(featureToggle, context, defaultValue) : defaultValue;
metricService.countVariant(toggleName, variant.getName());
return variant;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/no/finn/unleash/FakeUnleash.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

public final class FakeUnleash implements Unleash {
private boolean enableAll = false;
Expand All @@ -27,6 +28,19 @@ public boolean isEnabled(String toggleName, boolean defaultSetting) {
}
}

@Override
public boolean isEnabled(String toggleName, UnleashContext context, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
return isEnabled(toggleName, fallbackAction);
}

@Override
public boolean isEnabled(String toggleName, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
if(!features.containsKey(toggleName)) {
return fallbackAction.apply(toggleName, UnleashContext.builder().build());
}
return isEnabled(toggleName);
}

@Override
public Variant getVariant(String toggleName, UnleashContext context) {
return getVariant(toggleName, Variant.DISABLED_VARIANT);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/no/finn/unleash/Unleash.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package no.finn.unleash;

import java.util.List;
import java.util.function.BiFunction;

public interface Unleash {
boolean isEnabled(String toggleName);
Expand All @@ -15,6 +16,14 @@ default boolean isEnabled(String toggleName, UnleashContext context, boolean def
return isEnabled(toggleName, defaultSetting);
}

default boolean isEnabled(String toggleName, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
return isEnabled(toggleName, false);
}

default boolean isEnabled(String toggleName, UnleashContext context, BiFunction<String, UnleashContext, Boolean> fallbackAction) {
return isEnabled(toggleName, context, false);
}

Variant getVariant(final String toggleName, final UnleashContext context);

Variant getVariant(final String toggleName, final UnleashContext context, final Variant defaultValue);
Expand Down
56 changes: 49 additions & 7 deletions src/test/java/no/finn/unleash/UnleashTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import no.finn.unleash.repository.ToggleRepository;
import no.finn.unleash.strategy.Strategy;
Expand All @@ -15,21 +17,18 @@

import no.finn.unleash.variant.Payload;
import no.finn.unleash.variant.VariantDefinition;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

public class UnleashTest {

Expand Down Expand Up @@ -81,6 +80,49 @@ public void unknown_feature_should_use_default_setting() {
assertThat(unleash.isEnabled("test", true), is(true));
}

@Test
public void fallback_function_should_be_invoked_and_return_true() {
when(toggleRepository.getToggle("test")).thenReturn(null);
BiFunction<String, UnleashContext, Boolean> fallbackAction = mock(BiFunction.class);
when(fallbackAction.apply(eq("test"), any(UnleashContext.class))).thenReturn(true);

assertThat(unleash.isEnabled("test", fallbackAction), is(true));
verify(fallbackAction, times(1)).apply(anyString(), any(UnleashContext.class));
}

@Test
public void fallback_function_should_be_invoked_also_with_context() {
when(toggleRepository.getToggle("test")).thenReturn(null);
BiFunction<String, UnleashContext, Boolean> fallbackAction = mock(BiFunction.class);
when(fallbackAction.apply(eq("test"), any(UnleashContext.class))).thenReturn(true);

UnleashContext context = UnleashContext.builder().userId("123").build();

assertThat(unleash.isEnabled("test", context, fallbackAction), is(true));
verify(fallbackAction, times(1)).apply(anyString(), any(UnleashContext.class));
}

@Test
void fallback_function_should_be_invoked_and_return_false() {
when(toggleRepository.getToggle("test")).thenReturn(null);
BiFunction<String, UnleashContext, Boolean> fallbackAction = mock(BiFunction.class);
when(fallbackAction.apply(eq("test"), any(UnleashContext.class))).thenReturn(false);

assertThat(unleash.isEnabled("test", fallbackAction), is(false));
verify(fallbackAction, times(1)).apply(anyString(), any(UnleashContext.class));
}

@Test
void fallback_function_should_not_be_called_when_toggle_is_defined() {
when(toggleRepository.getToggle("test")).thenReturn(new FeatureToggle("test", true, asList(new ActivationStrategy("default", null))));

BiFunction<String, UnleashContext, Boolean> fallbackAction = mock(BiFunction.class);
when(fallbackAction.apply(eq("test"), any(UnleashContext.class))).thenReturn(false);

assertThat(unleash.isEnabled("test", fallbackAction), is(true));
verify(fallbackAction, never()).apply(anyString(), any(UnleashContext.class));
}

@Test
public void should_register_custom_strategies() {
//custom strategy
Expand Down

0 comments on commit 1f18286

Please sign in to comment.