From 60396991d72cb8398d2c8bf98f71ac46a515c514 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Sun, 23 Nov 2025 10:09:51 +0200 Subject: [PATCH 1/4] 0.3.0 Signed-off-by: liran2000 --- providers/statsig/README.md | 3 + providers/statsig/pom.xml | 14 +- .../providers/statsig/ContextTransformer.java | 7 +- .../providers/statsig/StatsigProvider.java | 75 ++++----- .../statsig/StatsigProviderConfig.java | 4 +- .../statsig/StatsigProviderTest.java | 147 +++++++++++------- 6 files changed, 140 insertions(+), 110 deletions(-) diff --git a/providers/statsig/README.md b/providers/statsig/README.md index a1eb57ce3..1ce663687 100644 --- a/providers/statsig/README.md +++ b/providers/statsig/README.md @@ -74,4 +74,7 @@ As it is limited, evaluation context based tests are limited. See [statsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java) for more information. +## Release Notes +### 0.3.0 + Migrated to according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps). diff --git a/providers/statsig/pom.xml b/providers/statsig/pom.xml index e7f6d79cb..b8890c5fc 100644 --- a/providers/statsig/pom.xml +++ b/providers/statsig/pom.xml @@ -10,7 +10,7 @@ dev.openfeature.contrib.providers statsig - 0.2.1 + 0.3.0 statsig Statsig provider for Java @@ -19,8 +19,9 @@ com.statsig - serversdk - 1.18.1 + javacore + 0.12.1 + uber @@ -36,5 +37,12 @@ test + + org.mockito + mockito-core + 5.20.0 + test + + diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java index bc5d75ce5..485a9c967 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java @@ -1,6 +1,6 @@ package dev.openfeature.contrib.providers.statsig; -import com.statsig.sdk.StatsigUser; +import com.statsig.StatsigUser; import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.Structure; import dev.openfeature.sdk.Value; @@ -23,7 +23,8 @@ static StatsigUser transform(EvaluationContext ctx) { if (ctx.getTargetingKey() == null) { throw new TargetingKeyMissingError("targeting key is required."); } - StatsigUser user = new StatsigUser(ctx.getTargetingKey()); + StatsigUser.Builder user = new StatsigUser.Builder() + .setUserID(ctx.getTargetingKey()); Map customMap = new HashMap<>(); ctx.asObjectMap().forEach((k, v) -> { switch (k) { @@ -64,6 +65,6 @@ static StatsigUser transform(EvaluationContext ctx) { privateAttributesStructure.asObjectMap().forEach((k, v) -> privateMap.put(k, String.valueOf(v))); user.setPrivateAttributes(privateMap); } - return user; + return user.build(); } } diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java index 8c1dd5a8f..75f91842b 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java @@ -1,11 +1,10 @@ package dev.openfeature.contrib.providers.statsig; -import com.statsig.sdk.APIFeatureGate; -import com.statsig.sdk.DynamicConfig; -import com.statsig.sdk.EvaluationReason; -import com.statsig.sdk.Layer; -import com.statsig.sdk.Statsig; -import com.statsig.sdk.StatsigUser; +import com.statsig.DynamicConfig; +import com.statsig.FeatureGate; +import com.statsig.Layer; +import com.statsig.Statsig; +import com.statsig.StatsigUser; import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.EventProvider; import dev.openfeature.sdk.Metadata; @@ -14,14 +13,11 @@ import dev.openfeature.sdk.Structure; import dev.openfeature.sdk.Value; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; /** Provider implementation for Statsig. */ @Slf4j @@ -33,6 +29,9 @@ public class StatsigProvider extends EventProvider { private static final String FEATURE_CONFIG_KEY = "feature_config"; private final StatsigProviderConfig statsigProviderConfig; + @Getter + private Statsig statsig; + /** * Constructor. * @@ -50,8 +49,8 @@ public StatsigProvider(StatsigProviderConfig statsigProviderConfig) { */ @Override public void initialize(EvaluationContext evaluationContext) throws Exception { - Future initFuture = - Statsig.initializeAsync(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions()); + statsig = new Statsig(statsigProviderConfig.getSdkKey(), statsigProviderConfig.getOptions()); + CompletableFuture initFuture = statsig.initialize(); initFuture.get(); statsigProviderConfig.postInit(); @@ -74,13 +73,14 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY); String reason = null; if (featureConfigValue == null) { - APIFeatureGate featureGate = Statsig.getFeatureGate(user, key); - reason = featureGate.getReason().getReason(); + FeatureGate featureGate = statsig.getFeatureGate(user, key); + reason = featureGate.getEvaluationDetails().getReason(); + evaluatedValue = featureGate.getValue(); // in case of evaluation failure, remain with default value. - if (!assumeFailure(featureGate)) { - evaluatedValue = featureGate.getValue(); - } +// if (!assumeFailure(featureGate)) { +// evaluatedValue = featureGate.getValue(); +// } } else { FeatureConfig featureConfig = parseFeatureConfig(ctx); switch (featureConfig.getType()) { @@ -107,13 +107,13 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa https://github.com/statsig-io/java-server-sdk/issues/22#issuecomment-2002346349 failure is assumed by reason, since success status is not returned. */ - private boolean assumeFailure(APIFeatureGate featureGate) { - EvaluationReason reason = featureGate.getReason(); - return EvaluationReason.DEFAULT.equals(reason) - || EvaluationReason.UNINITIALIZED.equals(reason) - || EvaluationReason.UNRECOGNIZED.equals(reason) - || EvaluationReason.UNSUPPORTED.equals(reason); - } +// private boolean assumeFailure(FeatureGate featureGate) { +// EvaluationReason reason = featureGate.getEvaluationDetails().getReason(); +// return EvaluationReason.DEFAULT.equals(reason) +// || EvaluationReason.UNINITIALIZED.equals(reason) +// || EvaluationReason.UNRECOGNIZED.equals(reason) +// || EvaluationReason.UNSUPPORTED.equals(reason); +// } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { @@ -198,12 +198,12 @@ public ProviderEvaluation getObjectEvaluation(String key, Value defaultVa @SneakyThrows protected DynamicConfig fetchDynamicConfig(StatsigUser user, FeatureConfig featureConfig) { - return Statsig.getConfigAsync(user, featureConfig.getName()).get(); + return statsig.getDynamicConfig(user, featureConfig.getName()); } @SneakyThrows protected Layer fetchLayer(StatsigUser user, FeatureConfig featureConfig) { - return Statsig.getLayerAsync(user, featureConfig.getName()).get(); + return statsig.getLayer(user, featureConfig.getName()); } private Value toValue(DynamicConfig dynamicConfig) { @@ -211,13 +211,6 @@ private Value toValue(DynamicConfig dynamicConfig) { mutableContext.add("name", dynamicConfig.getName()); mutableContext.add("value", Structure.mapToStructure(dynamicConfig.getValue())); mutableContext.add("ruleID", dynamicConfig.getRuleID()); - mutableContext.add("groupName", dynamicConfig.getGroupName()); - List secondaryExposures = new ArrayList<>(); - dynamicConfig.getSecondaryExposures().forEach(secondaryExposure -> { - Value value = Value.objectToValue(secondaryExposure); - secondaryExposures.add(value); - }); - mutableContext.add("secondaryExposures", secondaryExposures); return new Value(mutableContext); } @@ -227,17 +220,11 @@ private Value toValue(Layer layer) { mutableContext.add("value", Structure.mapToStructure(layer.getValue())); mutableContext.add("ruleID", layer.getRuleID()); mutableContext.add("groupName", layer.getGroupName()); - List secondaryExposures = new ArrayList<>(); - layer.getSecondaryExposures().forEach(secondaryExposure -> { - Value value = Value.objectToValue(secondaryExposure); - secondaryExposures.add(value); - }); - mutableContext.add("secondaryExposures", secondaryExposures); - mutableContext.add("allocatedExperiment", layer.getAllocatedExperiment()); + mutableContext.add("allocatedExperiment", layer.getAllocatedExperimentName()); return new Value(mutableContext); } - @NotNull private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) { + private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) { Value featureConfigValue = ctx.getValue(FEATURE_CONFIG_KEY); if (featureConfigValue == null) { throw new IllegalArgumentException("feature config not found at evaluation context."); @@ -262,8 +249,10 @@ private Value toValue(Layer layer) { @SneakyThrows @Override public void shutdown() { - log.info("shutdown"); - Statsig.shutdown(); + log.info("shutdown begin"); + CompletableFuture shutdownFuture = statsig.shutdown(); + shutdownFuture.get(); + log.info("shutdown end"); } /** Feature config, as required for evaluation. */ diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java index 0f43402f6..bf1e90c6a 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java @@ -1,6 +1,6 @@ package dev.openfeature.contrib.providers.statsig; -import com.statsig.sdk.StatsigOptions; +import com.statsig.StatsigOptions; import lombok.Builder; import lombok.Getter; @@ -10,7 +10,7 @@ public class StatsigProviderConfig { @Builder.Default - private StatsigOptions options = new StatsigOptions(); + private StatsigOptions options = new StatsigOptions.Builder().build(); // Only holding temporary for initialization private String sdkKey; diff --git a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java index f13155159..6e0f005cd 100644 --- a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java +++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java @@ -7,17 +7,18 @@ import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_LOCALE; import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_PRIVATE_ATTRIBUTES; import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_USER_AGENT; +import static org.apache.commons.lang3.builder.ToStringStyle.NO_CLASS_NAME_STYLE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import com.statsig.sdk.DynamicConfig; -import com.statsig.sdk.Layer; -import com.statsig.sdk.Statsig; -import com.statsig.sdk.StatsigOptions; -import com.statsig.sdk.StatsigUser; +import com.statsig.DynamicConfig; +import com.statsig.Layer; +import com.statsig.StatsigOptions; +import com.statsig.StatsigUser; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.FlagEvaluationDetails; import dev.openfeature.sdk.ImmutableContext; @@ -30,9 +31,11 @@ import java.util.HashMap; import java.util.Map; import lombok.SneakyThrows; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ReflectionUtils; /** * StatsigProvider test, based on local config file evaluation. Configuration file test by statsig @@ -60,12 +63,14 @@ class StatsigProviderTest { @BeforeAll static void setUp() { String sdkKey = "test"; - StatsigOptions statsigOptions = new StatsigOptions(); - statsigOptions.setLocalMode(true); - StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder() - .sdkKey(sdkKey) - .options(statsigOptions) + StatsigOptions statsigOptions = new StatsigOptions.Builder() +// .setLocalMode(true) .build(); + StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder() + .sdkKey(sdkKey) + .options(statsigOptions) + .build(); + statsigProvider = spy(new StatsigProvider(statsigProviderConfig)); OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider); client = OpenFeatureAPI.getInstance().getClient(); @@ -74,53 +79,64 @@ static void setUp() { @SneakyThrows private static void buildFlags() { - Statsig.overrideGate(FLAG_NAME, true); + + boolean res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() + .setUserID(TARGETING_KEY) + .build(), FLAG_NAME); + + System.out.println("Initial flag evaluation: " + res); + + statsigProvider.getStatsig().overrideGate(FLAG_NAME, true); + + res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() + .setUserID(TARGETING_KEY) + .build(), FLAG_NAME); + + System.out.println("Overridden flag evaluation: " + res); + Map configMap = new HashMap<>(); configMap.put("boolean", true); configMap.put("alias", "test"); configMap.put("revision", INT_FLAG_VALUE); configMap.put("price", DOUBLE_FLAG_VALUE); - Statsig.overrideConfig("product", configMap); - Statsig.overrideLayer("product", configMap); + statsigProvider.getStatsig().overrideDynamicConfig("product", configMap); + statsigProvider.getStatsig().overrideLayer("product", configMap); ArrayList> secondaryExposures = new ArrayList<>(); secondaryExposures.add(Collections.singletonMap("test-exposure", "test-exposure-value")); - DynamicConfig dynamicConfig = new DynamicConfig( - "object-config-name", - Collections.singletonMap("value-key", "test-value"), - "test-rule-id", - "test-group-name", - secondaryExposures); + + DynamicConfig dynamicConfig = mock(DynamicConfig.class); + when(dynamicConfig.getName()).thenReturn("object-config-name"); + when(dynamicConfig.getValue()).thenReturn(Collections.singletonMap("value-key", "test-value")); + when(dynamicConfig.getRuleID()).thenReturn("test-rule-id"); doAnswer(invocation -> { - if ("object-config-name" - .equals(invocation - .getArgument(1, StatsigProvider.FeatureConfig.class) - .getName())) { - return dynamicConfig; - } - return invocation.callRealMethod(); - }) + if ("object-config-name" + .equals(invocation + .getArgument(1, StatsigProvider.FeatureConfig.class) + .getName())) { + return dynamicConfig; + } + return invocation.callRealMethod(); + }) .when(statsigProvider) .fetchDynamicConfig(any(), any()); - Layer layer = new Layer( - "layer-name", - "test-rule-id", - "test-group-name", - Collections.singletonMap("value-key", "test-value"), - secondaryExposures, - "allocated", - null); + // Mock Layer + Layer layer = mock(Layer.class); + when(layer.getName()).thenReturn("layer-name"); + when(layer.getValue()).thenReturn(Collections.singletonMap("value-key", "test-value")); + when(layer.getRuleID()).thenReturn("test-rule-id"); + doAnswer(invocation -> { - if ("layer-name" - .equals(invocation - .getArgument(1, StatsigProvider.FeatureConfig.class) - .getName())) { - return layer; - } - return invocation.callRealMethod(); - }) + if ("layer-name" + .equals(invocation + .getArgument(1, StatsigProvider.FeatureConfig.class) + .getName())) { + return layer; + } + return invocation.callRealMethod(); + }) .when(statsigProvider) .fetchLayer(any(), any()); } @@ -137,6 +153,13 @@ void getBooleanEvaluation() { assertEquals(false, flagEvaluationDetails.getValue()); assertEquals("ERROR", flagEvaluationDetails.getReason()); + boolean res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() + .setUserID(TARGETING_KEY) + .build(), FLAG_NAME); + + System.out.println("Overridden flag evaluation: " + res); + + MutableContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(TARGETING_KEY); assertEquals( @@ -197,8 +220,7 @@ void getObjectConfigEvaluation() { .getObjectEvaluation("dummy", new Value("fallback"), evaluationContext) .getValue(); - String expectedObjectEvaluation = "{groupName=test-group-name, name=object-config-name, secondaryExposures=" - + "[{test-exposure=test-exposure-value}], ruleID=test-rule-id, value={value-key=test-value}}"; + String expectedObjectEvaluation = "{name=object-config-name, ruleID=test-rule-id, value={value-key=test-value}}"; assertEquals( expectedObjectEvaluation, objectEvaluation.asStructure().asObjectMap().toString()); @@ -216,8 +238,7 @@ void getObjectLayerEvaluation() { .getObjectEvaluation("dummy", new Value("fallback"), evaluationContext) .getValue(); - String expectedObjectEvaluation = "{groupName=test-group-name, name=layer-name, secondaryExposures=" - + "[{test-exposure=test-exposure-value}], allocatedExperiment=allocated, ruleID=test-rule-id, " + String expectedObjectEvaluation = "{groupName=null, name=layer-name, allocatedExperiment=null, ruleID=test-rule-id, " + "value={value-key=test-value}}"; assertEquals( expectedObjectEvaluation, @@ -407,19 +428,27 @@ void contextTransformTest() { HashMap customMap = new HashMap<>(); customMap.put(customPropertyKey, customPropertyValue); - StatsigUser expectedUser = new StatsigUser(evaluationContext.getTargetingKey()); - expectedUser.setEmail(email); - expectedUser.setCountry(country); - expectedUser.setUserAgent(userAgent); - expectedUser.setIp(ip); - expectedUser.setAppVersion(appVersion); - Map privateAttributesMap = new HashMap<>(); - privateAttributesMap.put(CONTEXT_LOCALE, locale); - expectedUser.setPrivateAttributes(privateAttributesMap); - expectedUser.setCustomIDs(customMap); + StatsigUser expectedUser = new StatsigUser.Builder() + .setUserID(evaluationContext.getTargetingKey()) + .setEmail(email) + .setCountry(country) + .setUserAgent(userAgent) + .setIp(ip) + .setAppVersion(appVersion) + .setPrivateAttributes(Collections.singletonMap(CONTEXT_LOCALE, locale)) + .setCustomIDs(customMap) + .build(); StatsigUser transformedUser = ContextTransformer.transform(evaluationContext); - // equals not implemented for User, using toString - assertEquals(expectedUser.toString(), transformedUser.toString()); + assertEquals(expectedUser.getUserID(), transformedUser.getUserID()); + assertEquals(expectedUser.getEmail(), transformedUser.getEmail()); + assertEquals(expectedUser.getCountry(), transformedUser.getCountry()); + assertEquals(expectedUser.getUserAgent(), transformedUser.getUserAgent()); + assertEquals(expectedUser.getIp(), transformedUser.getIp()); + assertEquals(expectedUser.getAppVersion(), transformedUser.getAppVersion()); + assertEquals( + expectedUser.getPrivateAttributes(), + transformedUser.getPrivateAttributes()); + assertEquals(expectedUser.getCustomIDs(), transformedUser.getCustomIDs()); } } From 54872537f63cf008b985b39090b2e8f41344c009 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Sun, 23 Nov 2025 11:02:14 +0200 Subject: [PATCH 2/4] 0.3.0 Signed-off-by: liran2000 --- providers/statsig/README.md | 2 +- .../providers/statsig/ContextTransformer.java | 3 +- .../providers/statsig/StatsigProvider.java | 21 ----- .../statsig/StatsigProviderTest.java | 94 ++++++++----------- 4 files changed, 40 insertions(+), 80 deletions(-) diff --git a/providers/statsig/README.md b/providers/statsig/README.md index 1ce663687..2144aef7d 100644 --- a/providers/statsig/README.md +++ b/providers/statsig/README.md @@ -77,4 +77,4 @@ for more information. ## Release Notes ### 0.3.0 - Migrated to according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps). + Migrated to Java Core according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps). diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java index 485a9c967..2b0faf3d0 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java @@ -23,8 +23,7 @@ static StatsigUser transform(EvaluationContext ctx) { if (ctx.getTargetingKey() == null) { throw new TargetingKeyMissingError("targeting key is required."); } - StatsigUser.Builder user = new StatsigUser.Builder() - .setUserID(ctx.getTargetingKey()); + StatsigUser.Builder user = new StatsigUser.Builder().setUserID(ctx.getTargetingKey()); Map customMap = new HashMap<>(); ctx.asObjectMap().forEach((k, v) -> { switch (k) { diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java index 75f91842b..14fdbc34f 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java @@ -12,7 +12,6 @@ import dev.openfeature.sdk.ProviderEvaluation; import dev.openfeature.sdk.Structure; import dev.openfeature.sdk.Value; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.CompletableFuture; import lombok.AllArgsConstructor; import lombok.Getter; @@ -64,9 +63,6 @@ public Metadata getMetadata() { @SneakyThrows @Override - @SuppressFBWarnings( - value = {"NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"}, - justification = "reason can be null") public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { StatsigUser user = ContextTransformer.transform(ctx); Boolean evaluatedValue = defaultValue; @@ -76,11 +72,6 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa FeatureGate featureGate = statsig.getFeatureGate(user, key); reason = featureGate.getEvaluationDetails().getReason(); evaluatedValue = featureGate.getValue(); - - // in case of evaluation failure, remain with default value. -// if (!assumeFailure(featureGate)) { -// evaluatedValue = featureGate.getValue(); -// } } else { FeatureConfig featureConfig = parseFeatureConfig(ctx); switch (featureConfig.getType()) { @@ -103,18 +94,6 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa .build(); } - /* - https://github.com/statsig-io/java-server-sdk/issues/22#issuecomment-2002346349 - failure is assumed by reason, since success status is not returned. - */ -// private boolean assumeFailure(FeatureGate featureGate) { -// EvaluationReason reason = featureGate.getEvaluationDetails().getReason(); -// return EvaluationReason.DEFAULT.equals(reason) -// || EvaluationReason.UNINITIALIZED.equals(reason) -// || EvaluationReason.UNRECOGNIZED.equals(reason) -// || EvaluationReason.UNSUPPORTED.equals(reason); -// } - @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { StatsigUser user = ContextTransformer.transform(ctx); diff --git a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java index 6e0f005cd..d76ddbcdb 100644 --- a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java +++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java @@ -7,7 +7,6 @@ import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_LOCALE; import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_PRIVATE_ATTRIBUTES; import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_USER_AGENT; -import static org.apache.commons.lang3.builder.ToStringStyle.NO_CLASS_NAME_STYLE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -31,11 +30,9 @@ import java.util.HashMap; import java.util.Map; import lombok.SneakyThrows; -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.ReflectionUtils; /** * StatsigProvider test, based on local config file evaluation. Configuration file test by statsig @@ -64,12 +61,12 @@ class StatsigProviderTest { static void setUp() { String sdkKey = "test"; StatsigOptions statsigOptions = new StatsigOptions.Builder() -// .setLocalMode(true) + // .setLocalMode(true) .build(); StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder() - .sdkKey(sdkKey) - .options(statsigOptions) - .build(); + .sdkKey(sdkKey) + .options(statsigOptions) + .build(); statsigProvider = spy(new StatsigProvider(statsigProviderConfig)); OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider); @@ -79,21 +76,7 @@ static void setUp() { @SneakyThrows private static void buildFlags() { - - boolean res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() - .setUserID(TARGETING_KEY) - .build(), FLAG_NAME); - - System.out.println("Initial flag evaluation: " + res); - statsigProvider.getStatsig().overrideGate(FLAG_NAME, true); - - res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() - .setUserID(TARGETING_KEY) - .build(), FLAG_NAME); - - System.out.println("Overridden flag evaluation: " + res); - Map configMap = new HashMap<>(); configMap.put("boolean", true); configMap.put("alias", "test"); @@ -111,14 +94,14 @@ private static void buildFlags() { when(dynamicConfig.getRuleID()).thenReturn("test-rule-id"); doAnswer(invocation -> { - if ("object-config-name" - .equals(invocation - .getArgument(1, StatsigProvider.FeatureConfig.class) - .getName())) { - return dynamicConfig; - } - return invocation.callRealMethod(); - }) + if ("object-config-name" + .equals(invocation + .getArgument(1, StatsigProvider.FeatureConfig.class) + .getName())) { + return dynamicConfig; + } + return invocation.callRealMethod(); + }) .when(statsigProvider) .fetchDynamicConfig(any(), any()); @@ -129,14 +112,14 @@ private static void buildFlags() { when(layer.getRuleID()).thenReturn("test-rule-id"); doAnswer(invocation -> { - if ("layer-name" - .equals(invocation - .getArgument(1, StatsigProvider.FeatureConfig.class) - .getName())) { - return layer; - } - return invocation.callRealMethod(); - }) + if ("layer-name" + .equals(invocation + .getArgument(1, StatsigProvider.FeatureConfig.class) + .getName())) { + return layer; + } + return invocation.callRealMethod(); + }) .when(statsigProvider) .fetchLayer(any(), any()); } @@ -153,13 +136,12 @@ void getBooleanEvaluation() { assertEquals(false, flagEvaluationDetails.getValue()); assertEquals("ERROR", flagEvaluationDetails.getReason()); - boolean res = statsigProvider.getStatsig().checkGate(new StatsigUser.Builder() - .setUserID(TARGETING_KEY) - .build(), FLAG_NAME); + boolean res = statsigProvider + .getStatsig() + .checkGate(new StatsigUser.Builder().setUserID(TARGETING_KEY).build(), FLAG_NAME); System.out.println("Overridden flag evaluation: " + res); - MutableContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(TARGETING_KEY); assertEquals( @@ -220,7 +202,8 @@ void getObjectConfigEvaluation() { .getObjectEvaluation("dummy", new Value("fallback"), evaluationContext) .getValue(); - String expectedObjectEvaluation = "{name=object-config-name, ruleID=test-rule-id, value={value-key=test-value}}"; + String expectedObjectEvaluation = + "{name=object-config-name, ruleID=test-rule-id, value={value-key=test-value}}"; assertEquals( expectedObjectEvaluation, objectEvaluation.asStructure().asObjectMap().toString()); @@ -238,8 +221,9 @@ void getObjectLayerEvaluation() { .getObjectEvaluation("dummy", new Value("fallback"), evaluationContext) .getValue(); - String expectedObjectEvaluation = "{groupName=null, name=layer-name, allocatedExperiment=null, ruleID=test-rule-id, " - + "value={value-key=test-value}}"; + String expectedObjectEvaluation = + "{groupName=null, name=layer-name, allocatedExperiment=null, ruleID=test-rule-id, " + + "value={value-key=test-value}}"; assertEquals( expectedObjectEvaluation, objectEvaluation.asStructure().asObjectMap().toString()); @@ -429,15 +413,15 @@ void contextTransformTest() { HashMap customMap = new HashMap<>(); customMap.put(customPropertyKey, customPropertyValue); StatsigUser expectedUser = new StatsigUser.Builder() - .setUserID(evaluationContext.getTargetingKey()) - .setEmail(email) - .setCountry(country) - .setUserAgent(userAgent) - .setIp(ip) - .setAppVersion(appVersion) - .setPrivateAttributes(Collections.singletonMap(CONTEXT_LOCALE, locale)) - .setCustomIDs(customMap) - .build(); + .setUserID(evaluationContext.getTargetingKey()) + .setEmail(email) + .setCountry(country) + .setUserAgent(userAgent) + .setIp(ip) + .setAppVersion(appVersion) + .setPrivateAttributes(Collections.singletonMap(CONTEXT_LOCALE, locale)) + .setCustomIDs(customMap) + .build(); StatsigUser transformedUser = ContextTransformer.transform(evaluationContext); assertEquals(expectedUser.getUserID(), transformedUser.getUserID()); @@ -446,9 +430,7 @@ void contextTransformTest() { assertEquals(expectedUser.getUserAgent(), transformedUser.getUserAgent()); assertEquals(expectedUser.getIp(), transformedUser.getIp()); assertEquals(expectedUser.getAppVersion(), transformedUser.getAppVersion()); - assertEquals( - expectedUser.getPrivateAttributes(), - transformedUser.getPrivateAttributes()); + assertEquals(expectedUser.getPrivateAttributes(), transformedUser.getPrivateAttributes()); assertEquals(expectedUser.getCustomIDs(), transformedUser.getCustomIDs()); } } From 104c0625f8c4dc174113253d694fd59664439aea Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 25 Nov 2025 08:16:07 +0200 Subject: [PATCH 3/4] updates Signed-off-by: liran2000 --- providers/statsig/README.md | 7 ++++++- .../contrib/providers/statsig/StatsigProvider.java | 6 ++++-- .../contrib/providers/statsig/StatsigProviderTest.java | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/providers/statsig/README.md b/providers/statsig/README.md index 2144aef7d..a7e804d75 100644 --- a/providers/statsig/README.md +++ b/providers/statsig/README.md @@ -77,4 +77,9 @@ for more information. ## Release Notes ### 0.3.0 - Migrated to Java Core according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps). + - Migrated to Java Core according to [Migration guide](https://docs.statsig.com/server-core/migration-guides/java#java-migration-steps). + + The provider usage is basically unchanged, the underlying implementation is changed. + As the initialization code may change, can refer to the migration guide for details. + +- group and secondaryExposures usage removed. diff --git a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java index 14fdbc34f..70d1adef5 100644 --- a/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java +++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java @@ -229,8 +229,10 @@ private static FeatureConfig parseFeatureConfig(EvaluationContext ctx) { @Override public void shutdown() { log.info("shutdown begin"); - CompletableFuture shutdownFuture = statsig.shutdown(); - shutdownFuture.get(); + if (statsig != null) { + CompletableFuture shutdownFuture = statsig.shutdown(); + shutdownFuture.get(); + } log.info("shutdown end"); } diff --git a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java index d76ddbcdb..60eccf27f 100644 --- a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java +++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java @@ -61,7 +61,6 @@ class StatsigProviderTest { static void setUp() { String sdkKey = "test"; StatsigOptions statsigOptions = new StatsigOptions.Builder() - // .setLocalMode(true) .build(); StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder() .sdkKey(sdkKey) From eef84932c5422227c31d383a0dc0b338c4a06677 Mon Sep 17 00:00:00 2001 From: liran2000 Date: Tue, 25 Nov 2025 08:25:35 +0200 Subject: [PATCH 4/4] updates Signed-off-by: liran2000 --- .../contrib/providers/statsig/StatsigProviderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java index 60eccf27f..bdb42e54b 100644 --- a/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java +++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java @@ -60,8 +60,7 @@ class StatsigProviderTest { @BeforeAll static void setUp() { String sdkKey = "test"; - StatsigOptions statsigOptions = new StatsigOptions.Builder() - .build(); + StatsigOptions statsigOptions = new StatsigOptions.Builder().build(); StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder() .sdkKey(sdkKey) .options(statsigOptions)