diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index b3ba04b13..cca686378 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -31,6 +31,8 @@ components:
- z4kn4fein
- laliconfigcat
- novalisdenahi
+ providers/statsig:
+ - liran2000
ignored-authors:
- renovate-bot
diff --git a/pom.xml b/pom.xml
index 52333289e..246856afa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
providers/unleash
providers/flipt
providers/configcat
+ providers/statsig
diff --git a/providers/statsig/CHANGELOG.md b/providers/statsig/CHANGELOG.md
new file mode 100644
index 000000000..825c32f0d
--- /dev/null
+++ b/providers/statsig/CHANGELOG.md
@@ -0,0 +1 @@
+# Changelog
diff --git a/providers/statsig/README.md b/providers/statsig/README.md
new file mode 100644
index 000000000..861706574
--- /dev/null
+++ b/providers/statsig/README.md
@@ -0,0 +1,73 @@
+# Unofficial Statsig OpenFeature Provider for Java
+
+[Statsig](https://statsig.com/) OpenFeature Provider can provide usage for Statsig via OpenFeature Java SDK.
+
+## Installation
+
+
+
+```xml
+
+
+ dev.openfeature.contrib.providers
+ statsig
+ 0.0.1
+
+```
+
+
+
+## Concepts
+* Boolean evaluation gets [gate](https://docs.statsig.com/server/javaSdk#checking-a-gate) status.
+* String/Integer/Double evaluations evaluation gets [Dynamic config](https://docs.statsig.com/server/javaSdk#reading-a-dynamic-config) or [Layer](https://docs.statsig.com/server/javaSdk#getting-an-layerexperiment) evaluation.
+ As the key represents an inner attribute, feature config is required as a parameter with data needed for evaluation.
+ For an example of dynamic config of product alias, need to differentiate between dynamic config or layer, and the dynamic config name.
+* Object evaluation gets a structure representing the dynamic config or layer.
+* [Private Attributes](https://docs.statsig.com/server/javaSdk#private-attributes) are supported as 'privateAttributes' context key.
+
+## Usage
+Statsig OpenFeature Provider is based on [Statsig Java SDK documentation](https://docs.statsig.com/server/javaSdk).
+
+### Usage Example
+
+```
+StatsigOptions statsigOptions = new StatsigOptions();
+StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder().sdkKey(sdkKey)
+ .options(statsigOptions).build();
+statsigProvider = new StatsigProvider(statsigProviderConfig);
+OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);
+
+boolean featureEnabled = client.getBooleanValue(FLAG_NAME, false);
+
+MutableContext evaluationContext = new MutableContext();
+MutableContext featureConfig = new MutableContext();
+featureConfig.add("type", "CONFIG");
+featureConfig.add("name", "product");
+evaluationContext.add("feature_config", featureConfig);
+String value = statsigProvider.getStringEvaluation("alias", "fallback", evaluationContext).getValue());
+
+MutableContext evaluationContext = new MutableContext();
+evaluationContext.setTargetingKey("test-id");
+evaluationContext.add("Email", "a@b.com");
+MutableContext privateAttributes = new MutableContext();
+privateAttributes.add("locale", locale);
+evaluationContext.add("privateAttributes", privateAttributes);
+featureEnabled = client.getBooleanValue(USERS_FLAG_NAME, false, evaluationContext);
+```
+
+See [StatsigProviderTest](./src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java)
+for more information.
+
+## Notes
+Some Statsig custom operations are supported from the Statsig client via:
+
+```java
+Statsig...
+```
+
+## Statsig Provider Tests Strategies
+
+Unit test based on Statsig [Local Overrides](https://docs.statsig.com/server/javaSdk#local-overrides) and mocking.
+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.
diff --git a/providers/statsig/lombok.config b/providers/statsig/lombok.config
new file mode 100644
index 000000000..bcd1afdae
--- /dev/null
+++ b/providers/statsig/lombok.config
@@ -0,0 +1,5 @@
+# This file is needed to avoid errors throw by findbugs when working with lombok.
+lombok.addSuppressWarnings = true
+lombok.addLombokGeneratedAnnotation = true
+config.stopBubbling = true
+lombok.extern.findbugs.addSuppressFBWarnings = true
diff --git a/providers/statsig/pom.xml b/providers/statsig/pom.xml
new file mode 100644
index 000000000..6455763ab
--- /dev/null
+++ b/providers/statsig/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+
+ dev.openfeature.contrib
+ parent
+ 0.1.0
+ ../../pom.xml
+
+ dev.openfeature.contrib.providers
+ statsig
+ 0.0.1
+
+ statsig
+ Statsig provider for Java
+ https://statsig.com/
+
+
+
+ com.statsig
+ serversdk
+ 1.10.0
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.11
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.22.1
+ 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
new file mode 100644
index 000000000..1ae16bbb8
--- /dev/null
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/ContextTransformer.java
@@ -0,0 +1,69 @@
+package dev.openfeature.contrib.providers.statsig;
+
+import com.statsig.sdk.StatsigUser;
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.Structure;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.TargetingKeyMissingError;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Transformer from OpenFeature context to statsig User.
+ */
+class ContextTransformer {
+ public static final String CONTEXT_APP_VERSION = "appVersion";
+ public static final String CONTEXT_COUNTRY = "country";
+ public static final String CONTEXT_EMAIL = "email";
+ public static final String CONTEXT_IP = "ip";
+ public static final String CONTEXT_LOCALE = "locale";
+ public static final String CONTEXT_USER_AGENT = "userAgent";
+ public static final String CONTEXT_PRIVATE_ATTRIBUTES = "privateAttributes";
+
+ static StatsigUser transform(EvaluationContext ctx) {
+ if (ctx.getTargetingKey() == null) {
+ throw new TargetingKeyMissingError("targeting key is required.");
+ }
+ StatsigUser user = new StatsigUser(ctx.getTargetingKey());
+ Map customMap = new HashMap<>();
+ ctx.asObjectMap().forEach((k, v) -> {
+ switch (k) {
+ case CONTEXT_APP_VERSION:
+ user.setAppVersion(String.valueOf(v));
+ break;
+ case CONTEXT_COUNTRY:
+ user.setCountry(String.valueOf(v));
+ break;
+ case CONTEXT_EMAIL:
+ user.setEmail(String.valueOf(v));
+ break;
+ case CONTEXT_IP:
+ user.setIp(String.valueOf(v));
+ break;
+ case CONTEXT_USER_AGENT:
+ user.setUserAgent(String.valueOf(v));
+ break;
+ case CONTEXT_LOCALE:
+ user.setLocale(String.valueOf(v));
+ break;
+ default:
+ if (!CONTEXT_PRIVATE_ATTRIBUTES.equals(k)) {
+ customMap.put(k, String.valueOf(v));
+ }
+ break;
+ }
+ });
+ user.setCustomIDs(customMap);
+
+ Map privateMap = new HashMap<>();
+ Value privateAttributes = ctx.getValue(CONTEXT_PRIVATE_ATTRIBUTES);
+ if (privateAttributes != null && privateAttributes.isStructure()) {
+ Structure privateAttributesStructure = privateAttributes.asStructure();
+ privateAttributesStructure.asObjectMap().forEach((k, v) -> privateMap.put(k, String.valueOf(v)));
+ user.setPrivateAttributes(privateMap);
+ }
+ return user;
+ }
+
+}
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
new file mode 100644
index 000000000..bff88088e
--- /dev/null
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProvider.java
@@ -0,0 +1,296 @@
+package dev.openfeature.contrib.providers.statsig;
+
+import com.statsig.sdk.DynamicConfig;
+import com.statsig.sdk.Layer;
+import com.statsig.sdk.Statsig;
+import com.statsig.sdk.StatsigUser;
+import dev.openfeature.sdk.EvaluationContext;
+import dev.openfeature.sdk.EventProvider;
+import dev.openfeature.sdk.Metadata;
+import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.ProviderState;
+import dev.openfeature.sdk.Structure;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.GeneralError;
+import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provider implementation for Statsig.
+ */
+@Slf4j
+public class StatsigProvider extends EventProvider {
+
+ @Getter
+ private static final String NAME = "Statsig";
+
+ private static final String PROVIDER_NOT_YET_INITIALIZED = "provider not yet initialized";
+ private static final String UNKNOWN_ERROR = "unknown error";
+ private static final String FEATURE_CONFIG_KEY = "feature_config";
+
+ private final StatsigProviderConfig statsigProviderConfig;
+
+ @Getter
+ private ProviderState state = ProviderState.NOT_READY;
+
+ private final AtomicBoolean isInitialized = new AtomicBoolean(false);
+
+ /**
+ * Constructor.
+ * @param statsigProviderConfig StatsigProvider Config
+ */
+ public StatsigProvider(StatsigProviderConfig statsigProviderConfig) {
+ this.statsigProviderConfig = statsigProviderConfig;
+ }
+
+ /**
+ * Initialize the provider.
+ * @param evaluationContext evaluation context
+ * @throws Exception on error
+ */
+ @Override
+ public void initialize(EvaluationContext evaluationContext) throws Exception {
+ boolean initialized = isInitialized.getAndSet(true);
+ if (initialized && ProviderState.READY.equals(state)) {
+ log.debug("already initialized");
+ return;
+ }
+
+ Future initFuture = Statsig.initializeAsync(statsigProviderConfig.getSdkKey(),
+ statsigProviderConfig.getOptions());
+ initFuture.get();
+
+ statsigProviderConfig.postInit();
+ state = ProviderState.READY;
+ log.info("finished initializing provider, state: {}", state);
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return () -> NAME;
+ }
+
+ @SneakyThrows
+ @Override
+ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) {
+ verifyEvaluation();
+ StatsigUser user = ContextTransformer.transform(ctx);
+ Future featureOn = Statsig.checkGateAsync(user, key);
+ Boolean evaluatedValue = featureOn.get();
+ return ProviderEvaluation.builder()
+ .value(evaluatedValue)
+ .build();
+ }
+
+ @Override
+ public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) {
+ verifyEvaluation();
+ StatsigUser user = ContextTransformer.transform(ctx);
+ FeatureConfig featureConfig = parseFeatureConfig(ctx);
+ String evaluatedValue = defaultValue;
+ switch (featureConfig.getType()) {
+ case CONFIG:
+ DynamicConfig dynamicConfig = fetchDynamicConfig(user, featureConfig);
+ evaluatedValue = dynamicConfig.getString(key, defaultValue);
+ break;
+ case LAYER:
+ Layer layer = fetchLayer(user, featureConfig);
+ evaluatedValue = layer.getString(key, defaultValue);
+ break;
+ default:
+ break;
+ }
+ return ProviderEvaluation.builder()
+ .value(evaluatedValue)
+ .build();
+ }
+
+ @Override
+ public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) {
+ verifyEvaluation();
+ StatsigUser user = ContextTransformer.transform(ctx);
+ FeatureConfig featureConfig = parseFeatureConfig(ctx);
+ Integer evaluatedValue = defaultValue;
+ switch (featureConfig.getType()) {
+ case CONFIG:
+ DynamicConfig dynamicConfig = fetchDynamicConfig(user, featureConfig);
+ evaluatedValue = dynamicConfig.getInt(key, defaultValue);
+ break;
+ case LAYER:
+ Layer layer = fetchLayer(user, featureConfig);
+ evaluatedValue = layer.getInt(key, defaultValue);
+ break;
+ default:
+ break;
+ }
+ return ProviderEvaluation.builder()
+ .value(evaluatedValue)
+ .build();
+ }
+
+ @Override
+ public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) {
+ verifyEvaluation();
+ StatsigUser user = ContextTransformer.transform(ctx);
+ FeatureConfig featureConfig = parseFeatureConfig(ctx);
+ Double evaluatedValue = defaultValue;
+ switch (featureConfig.getType()) {
+ case CONFIG:
+ DynamicConfig dynamicConfig = fetchDynamicConfig(user, featureConfig);
+ evaluatedValue = dynamicConfig.getDouble(key, defaultValue);
+ break;
+ case LAYER:
+ Layer layer = fetchLayer(user, featureConfig);
+ evaluatedValue = layer.getDouble(key, defaultValue);
+ break;
+ default:
+ break;
+ }
+ return ProviderEvaluation.builder()
+ .value(evaluatedValue)
+ .build();
+ }
+
+ @SneakyThrows
+ @Override
+ public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext ctx) {
+ verifyEvaluation();
+ StatsigUser user = ContextTransformer.transform(ctx);
+ FeatureConfig featureConfig = parseFeatureConfig(ctx);
+ Value evaluatedValue = defaultValue;
+ switch (featureConfig.getType()) {
+ case CONFIG:
+ DynamicConfig dynamicConfig = fetchDynamicConfig(user, featureConfig);
+ evaluatedValue = toValue(dynamicConfig);
+ break;
+ case LAYER:
+ Layer layer = fetchLayer(user, featureConfig);
+ evaluatedValue = toValue(layer);
+ break;
+ default:
+ break;
+ }
+ return ProviderEvaluation.builder()
+ .value(evaluatedValue)
+ .build();
+ }
+
+ @SneakyThrows
+ protected DynamicConfig fetchDynamicConfig(StatsigUser user, FeatureConfig featureConfig) {
+ return Statsig.getConfigAsync(user, featureConfig.getName()).get();
+ }
+
+ @SneakyThrows
+ protected Layer fetchLayer(StatsigUser user, FeatureConfig featureConfig) {
+ return Statsig.getLayerAsync(user, featureConfig.getName()).get();
+ }
+
+ private Value toValue(DynamicConfig dynamicConfig) {
+ MutableContext mutableContext = new MutableContext();
+ 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);
+ }
+
+ private Value toValue(Layer layer) {
+ MutableContext mutableContext = new MutableContext();
+ mutableContext.add("name", layer.getName());
+ 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());
+ return new Value(mutableContext);
+ }
+
+ @NotNull
+ 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.");
+ }
+ if (!featureConfigValue.isStructure()) {
+ throw new IllegalArgumentException("feature config is not a structure.");
+ }
+ Structure featureConfigStructure = featureConfigValue.asStructure();
+ Value typeValue = featureConfigStructure.getValue("type");
+ if (typeValue == null) {
+ throw new IllegalArgumentException("feature config type is missing");
+ }
+ FeatureConfig.Type type = FeatureConfig.Type.valueOf(typeValue.asString());
+ Value nameValue = featureConfigStructure.getValue("name");
+ if (nameValue == null) {
+ throw new IllegalArgumentException("feature config name is missing");
+ }
+ String name = nameValue.asString();
+ return new FeatureConfig(type, name);
+ }
+
+ private void verifyEvaluation() throws ProviderNotReadyError, GeneralError {
+ if (!ProviderState.READY.equals(state)) {
+
+ /*
+ According to spec Requirement 2.4.5:
+ "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready."
+ https://github.com/open-feature/spec/blob/main/specification/sections/02-providers.md#requirement-245
+ */
+ if (ProviderState.NOT_READY.equals(state)) {
+ throw new ProviderNotReadyError(PROVIDER_NOT_YET_INITIALIZED);
+ }
+ throw new GeneralError(UNKNOWN_ERROR);
+ }
+ }
+
+ @SneakyThrows
+ @Override
+ public void shutdown() {
+ log.info("shutdown");
+ Statsig.shutdown();
+ state = ProviderState.NOT_READY;
+ }
+
+ /**
+ * Feature config, as required for evaluation.
+ */
+ @AllArgsConstructor
+ @Getter
+ public static class FeatureConfig {
+
+ /**
+ * Type.
+ * CONFIG: Dynamic Config
+ * LAYER: Layer
+ */
+ public enum Type {
+ CONFIG, LAYER
+ }
+
+ private Type type;
+ private String name;
+ }
+}
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
new file mode 100644
index 000000000..58d5b9fdc
--- /dev/null
+++ b/providers/statsig/src/main/java/dev/openfeature/contrib/providers/statsig/StatsigProviderConfig.java
@@ -0,0 +1,23 @@
+package dev.openfeature.contrib.providers.statsig;
+
+import com.statsig.sdk.StatsigOptions;
+import lombok.Builder;
+import lombok.Getter;
+
+
+/**
+ * Configuration for initializing statsig provider.
+ */
+@Getter
+@Builder
+public class StatsigProviderConfig {
+
+ private StatsigOptions options;
+
+ // Only holding temporary for initialization
+ private String sdkKey;
+
+ public void postInit() {
+ sdkKey = null; // for security, not holding key in memory for long-term
+ }
+}
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
new file mode 100644
index 000000000..6fa5a3a17
--- /dev/null
+++ b/providers/statsig/src/test/java/dev/openfeature/contrib/providers/statsig/StatsigProviderTest.java
@@ -0,0 +1,338 @@
+package dev.openfeature.contrib.providers.statsig;
+
+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 dev.openfeature.sdk.Client;
+import dev.openfeature.sdk.ImmutableContext;
+import dev.openfeature.sdk.MutableContext;
+import dev.openfeature.sdk.OpenFeatureAPI;
+import dev.openfeature.sdk.ProviderEvaluation;
+import dev.openfeature.sdk.Value;
+import dev.openfeature.sdk.exceptions.ProviderNotReadyError;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_APP_VERSION;
+import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_COUNTRY;
+import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_EMAIL;
+import static dev.openfeature.contrib.providers.statsig.ContextTransformer.CONTEXT_IP;
+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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+/**
+ * StatsigProvider test, based on local config file evaluation.
+ * Configuration file test by statsig tests.
+ */
+class StatsigProviderTest {
+
+ public static final String FLAG_NAME = "enabledFeature";
+ public static final String CONFIG_FLAG_NAME = "alias";
+ public static final String LAYER_FLAG_NAME = "alias";
+ public static final String CONFIG_FLAG_VALUE = "test";
+ public static final String INT_FLAG_NAME = "revision";
+ public static final String LAYER_INT_FLAG_NAME = "revision";
+ public static final Integer INT_FLAG_VALUE = 5;
+ public static final String DOUBLE_FLAG_NAME = "price";
+ public static final String LAYER_DOUBLE_FLAG_NAME = "price";
+ public static final Double DOUBLE_FLAG_VALUE = 3.14;
+ public static final String USERS_FLAG_NAME = "userIdMatching";
+ public static final String PROPERTIES_FLAG_NAME = "emailMatching";
+ private static StatsigProvider statsigProvider;
+ private static Client client;
+
+ @SneakyThrows
+ @BeforeAll
+ static void setUp() {
+ String sdkKey = "test";
+ StatsigOptions statsigOptions = new StatsigOptions();
+ statsigOptions.setLocalMode(true);
+ StatsigProviderConfig statsigProviderConfig = StatsigProviderConfig.builder().sdkKey(sdkKey)
+ .options(statsigOptions).build();
+ statsigProvider = spy(new StatsigProvider(statsigProviderConfig));
+ OpenFeatureAPI.getInstance().setProviderAndWait(statsigProvider);
+ client = OpenFeatureAPI.getInstance().getClient();
+ buildFlags();
+ }
+
+ @SneakyThrows
+ private static void buildFlags() {
+ Statsig.overrideGate(FLAG_NAME, true);
+ Map configMap = new HashMap<>();
+ configMap.put("alias", "test");
+ configMap.put("revision", INT_FLAG_VALUE);
+ configMap.put("price", DOUBLE_FLAG_VALUE);
+ Statsig.overrideConfig("product", configMap);
+ Statsig.overrideLayer("product", configMap);
+
+ ArrayList