diff --git a/src/main/java/com/launchdarkly/client/TestFeatureStore.java b/src/main/java/com/launchdarkly/client/TestFeatureStore.java new file mode 100644 index 000000000..b8107ce21 --- /dev/null +++ b/src/main/java/com/launchdarkly/client/TestFeatureStore.java @@ -0,0 +1,40 @@ +package com.launchdarkly.client; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A decorated {@link InMemoryFeatureStore} which provides functionality to create (or override) "on" or "off" feature flags for all users. + * + * Using this store is useful for testing purposes when you want to have runtime support for turning specific features "on" or "off". + * + */ +public class TestFeatureStore extends InMemoryFeatureStore { + + private AtomicInteger version = new AtomicInteger(0); + + /** + * Turns a feature, identified by key, "on" for every user. If the feature rules already exist in the store then it will override it to be "on" for every {@link LDUser}. + * If the feature rule is not currently in the store, it will create one that is "on" for every {@link LDUser}. + * + * @param key the key of the feature flag to be "on". + */ + public void turnFeatureOn(String key) { + writeFeatureRep(key, new Variation.Builder<>(true, 100).build()); + } + + /** + * Turns a feature, identified by key, "off" for every user. If the feature rules already exists in the store then it will override it to be "off" for every {@link LDUser}. + * If the feature rule is not currently in the store, it will create one that is "off" for every {@link LDUser}. + * + * @param key the key of the feature flag to be "off". + */ + public void turnFeatureOff(String key) { + writeFeatureRep(key, new Variation.Builder<>(false, 100).build()); + } + + private void writeFeatureRep(final String key, final Variation variation) { + FeatureRep newFeature = new FeatureRep.Builder(String.format("test-%s", key), key) + .variation(variation).version(version.incrementAndGet()).build(); + upsert(key, newFeature); + } +} diff --git a/src/test/java/com/launchdarkly/client/LDClientTest.java b/src/test/java/com/launchdarkly/client/LDClientTest.java index 78e020d4a..d0543c9ac 100644 --- a/src/test/java/com/launchdarkly/client/LDClientTest.java +++ b/src/test/java/com/launchdarkly/client/LDClientTest.java @@ -5,6 +5,8 @@ import org.junit.Test; import java.io.IOException; +import java.io.ObjectInput; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -12,6 +14,7 @@ import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class LDClientTest extends EasyMockSupport { @@ -45,6 +48,76 @@ public void testOffline() throws IOException { verifyAll(); } + @Test + public void testTestFeatureStoreFlagOn() throws IOException, InterruptedException, ExecutionException, TimeoutException { + TestFeatureStore testFeatureStore = new TestFeatureStore(); + LDConfig config = new LDConfig.Builder() + .startWaitMillis(10L) + .stream(false) + .featureStore(testFeatureStore) + .build(); + + expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(new Object()); + expect(pollingProcessor.start()).andReturn(initFuture); + expect(pollingProcessor.initialized()).andReturn(true).times(1); + expect(eventProcessor.sendEvent(anyObject(Event.class))).andReturn(true); + replayAll(); + + client = createMockClient(config); + testFeatureStore.turnFeatureOn("key"); + assertTrue("Test flag should be on, but was not.", client.toggle("key", new LDUser("user"), false)); + + verifyAll(); + } + + @Test + public void testTestFeatureStoreFlagOff() throws IOException, InterruptedException, ExecutionException, TimeoutException { + TestFeatureStore testFeatureStore = new TestFeatureStore(); + LDConfig config = new LDConfig.Builder() + .startWaitMillis(10L) + .stream(false) + .featureStore(testFeatureStore) + .build(); + + expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(new Object()); + expect(pollingProcessor.start()).andReturn(initFuture); + expect(pollingProcessor.initialized()).andReturn(true).times(1); + expect(eventProcessor.sendEvent(anyObject(Event.class))).andReturn(true); + replayAll(); + + client = createMockClient(config); + testFeatureStore.turnFeatureOff("key"); + assertFalse("Test flag should be off, but was on (the default).", client.toggle("key", new LDUser("user"), true)); + + verifyAll(); + } + + @Test + public void testTestFeatureStoreFlagOnThenOff() throws IOException, InterruptedException, ExecutionException, TimeoutException { + TestFeatureStore testFeatureStore = new TestFeatureStore(); + LDConfig config = new LDConfig.Builder() + .startWaitMillis(10L) + .stream(false) + .featureStore(testFeatureStore) + .build(); + + expect(initFuture.get(10L, TimeUnit.MILLISECONDS)).andReturn(new Object()); + expect(pollingProcessor.start()).andReturn(initFuture); + expect(pollingProcessor.initialized()).andReturn(true).times(2); + expect(eventProcessor.sendEvent(anyObject(Event.class))).andReturn(true).times(2); + replayAll(); + + client = createMockClient(config); + + testFeatureStore.turnFeatureOn("key"); + assertTrue("Test flag should be on, but was not.", client.toggle("key", new LDUser("user"), false)); + + testFeatureStore.turnFeatureOff("key"); + assertFalse("Test flag should be off, but was on (the default).", client.toggle("key", new LDUser("user"), true)); + + verifyAll(); + } + @Test public void testUseLdd() throws IOException { LDConfig config = new LDConfig.Builder()