diff --git a/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java b/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java index 2b98d5e9915..4b723fac7fb 100644 --- a/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java +++ b/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java @@ -213,6 +213,11 @@ private static List> evaluateTestCases() { new TestCase<>("default") .flag("simple-string") .result(new Result<>("default").reason(ERROR.name()).errorCode(TARGETING_KEY_MISSING)), + // OF.7: Empty string is a valid targeting key - evaluation should proceed as normal + new TestCase<>("default") + .flag("simple-string") + .targetingKey("") + .result(new Result<>("test-value").reason(TARGETING_MATCH.name()).variant("on")), new TestCase<>("default") .flag("non-existent-flag") .targetingKey("user-123") diff --git a/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/util/TestCase.java b/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/util/TestCase.java index b19f85bf84e..15da4a72dea 100644 --- a/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/util/TestCase.java +++ b/products/feature-flagging/api/src/test/java/datadog/trace/api/openfeature/util/TestCase.java @@ -1,21 +1,53 @@ package datadog.trace.api.openfeature.util; import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.MutableContext; import dev.openfeature.sdk.Structure; import dev.openfeature.sdk.Value; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; public class TestCase { public Class type; public String flag; public E defaultValue; - public final MutableContext context = new MutableContext(); + private final MutableContext baseContext = new MutableContext(); + private String targetingKeyOverride; + private boolean hasTargetingKeyOverride = false; public Result result; + // Custom context wrapper that preserves empty targeting key (OF.7 compliance) + public final EvaluationContext context = new EvaluationContext() { + @Override + public String getTargetingKey() { + return hasTargetingKeyOverride ? targetingKeyOverride : baseContext.getTargetingKey(); + } + + @Override + public Value getValue(String key) { + return baseContext.getValue(key); + } + + @Override + public Set keySet() { + return baseContext.keySet(); + } + + @Override + public Map asMap() { + return baseContext.asMap(); + } + + @Override + public Map asObjectMap() { + return baseContext.asObjectMap(); + } + }; + @SuppressWarnings("unchecked") public TestCase(final E defaultValue) { this.type = (Class) defaultValue.getClass(); @@ -28,37 +60,39 @@ public TestCase flag(String flag) { } public TestCase targetingKey(final String targetingKey) { - context.setTargetingKey(targetingKey); + // Preserve the targeting key directly to support empty string (OF.7) + this.targetingKeyOverride = targetingKey; + this.hasTargetingKeyOverride = true; return this; } public TestCase context(final String key, final String value) { - context.add(key, value); + baseContext.add(key, value); return this; } public TestCase context(final String key, final Integer value) { - context.add(key, value); + baseContext.add(key, value); return this; } public TestCase context(final String key, final Double value) { - context.add(key, value); + baseContext.add(key, value); return this; } public TestCase context(final String key, final Boolean value) { - context.add(key, value); + baseContext.add(key, value); return this; } public TestCase context(final String key, final Structure value) { - context.add(key, value); + baseContext.add(key, value); return this; } public TestCase context(final String key, final List value) { - context.add(key, value); + baseContext.add(key, value); return this; }