diff --git a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java index 76e4bd2..e1adafd 100644 --- a/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java +++ b/lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/integrations/TestData.java @@ -110,7 +110,36 @@ public FlagBuilder flag(String key) { } return new FlagBuilder(key).booleanFlag(); } - + + /** + * Deletes a specific flag from the test data by create a versioned tombstone. + *

+ * This has the same effect as if a flag were removed on the LaunchDarkly dashboard. + * It immediately propagates the flag change to any {@code LDClient} instance(s) that you have + * already configured to use this {@code TestData}. If no {@code LDClient} has been started yet, + * it simply adds tombstone to the test data which will be provided to any {@code LDClient} that + * you subsequently configure. + * + * @param key the flag key + * @return a flag configuration builder + */ + public TestData delete(String key) { + final ItemDescriptor tombstoneItem; + synchronized (lock) { + final ItemDescriptor oldItem = currentFlags.get(key); + final int oldVersion = oldItem == null ? 0 : oldItem.getVersion(); + tombstoneItem = ItemDescriptor.deletedItem(oldVersion + 1); + currentFlags.put(key, tombstoneItem); + currentBuilders.remove(key); + } + + for (DataSourceImpl instance: instances) { + instance.updates.upsert(DataModel.FEATURES, key, tombstoneItem); + } + + return this; + } + /** * Updates the test data with the specified flag configuration. *

@@ -146,7 +175,7 @@ public TestData update(FlagBuilder flagBuilder) { return this; } - + /** * Simulates a change in the data source status. *

diff --git a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java index 77e194d..fe71166 100644 --- a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java +++ b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataTest.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.iterableWithSize; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @SuppressWarnings("javadoc") @@ -151,7 +152,34 @@ public void updatesFlag() throws Exception { expectedFlag.on(true).version(2); assertJsonEquals(flagJson(expectedFlag, 2), flagJson(flag1)); } - + + @Test + public void deletesFlag() throws Exception { + final TestData td = TestData.dataSource(); + + try (final DataSource ds = td.build(clientContext("", new LDConfig.Builder().build(), updates))) { + final Future started = ds.start(); + assertThat(started.isDone(), is(true)); + assertThat(updates.valid, is(true)); + + td.update(td.flag("foo").on(false).valueForAll(LDValue.of("bar"))); + td.delete("foo"); + + assertThat(updates.upserts.size(), equalTo(2)); + UpsertParams up = updates.upserts.take(); + assertThat(up.kind, is(DataModel.FEATURES)); + assertThat(up.key, equalTo("foo")); + assertThat(up.item.getVersion(), equalTo(1)); + assertThat(up.item.getItem(), notNullValue()); + + up = updates.upserts.take(); + assertThat(up.kind, is(DataModel.FEATURES)); + assertThat(up.key, equalTo("foo")); + assertThat(up.item.getVersion(), equalTo(2)); + assertThat(up.item.getItem(), nullValue()); + } + } + @Test public void flagConfigSimpleBoolean() throws Exception { Function expectedBooleanFlag = fb -> diff --git a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataWithClientTest.java b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataWithClientTest.java index e063524..1e63bcb 100644 --- a/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataWithClientTest.java +++ b/lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/integrations/TestDataWithClientTest.java @@ -1,5 +1,7 @@ package com.launchdarkly.sdk.server.integrations; +import com.launchdarkly.sdk.EvaluationDetail; +import com.launchdarkly.sdk.EvaluationReason; import com.launchdarkly.sdk.LDContext; import com.launchdarkly.sdk.LDValue; import com.launchdarkly.sdk.server.Components; @@ -52,7 +54,23 @@ public void updatesFlag() throws Exception { assertThat(client.boolVariation("flag", LDContext.create("user"), false), is(true)); } } - + + @Test + public void deletesFlag() throws Exception { + td.update(td.flag("flag").on(true)); + + try (LDClient client = new LDClient(SDK_KEY, config)) { + assertThat(client.boolVariation("flag", LDContext.create("user"), false), is(true)); + + td.delete("flag"); + + final EvaluationDetail detail = client.boolVariationDetail("flag", LDContext.create("user"), false); + assertThat(detail.getValue(), is(false)); + assertThat(detail.isDefaultValue(), is(true)); + assertThat(detail.getReason().getErrorKind(), is(EvaluationReason.ErrorKind.FLAG_NOT_FOUND)); + } + } + @Test public void usesTargets() throws Exception { td.update(td.flag("flag").fallthroughVariation(false).variationForUser("user1", true));