diff --git a/.github/component_owners.yml b/.github/component_owners.yml
index ec79e018..eaed7942 100644
--- a/.github/component_owners.yml
+++ b/.github/component_owners.yml
@@ -18,6 +18,8 @@ components:
src/OpenFeature.Contrib.Providers.FeatureManagement:
- ericpattison
- toddbaert
+ src/OpenFeature.Contrib.Providers.Statsig:
+ - jenshenneberg
# test/
test/OpenFeature.Contrib.Hooks.Otel.Test:
@@ -37,6 +39,8 @@ components:
test/OpenFeature.Contrib.Providers.FeatureManagement.Test:
- ericpattison
- toddbaert
+ test/src/OpenFeature.Contrib.Providers.Statsig.Test:
+ - jenshenneberg
ignored-authors:
- renovate-bot
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 3c8fc3fe..ea82e80a 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -4,5 +4,6 @@
"src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.1.5",
"src/OpenFeature.Contrib.Providers.Flagsmith": "0.1.5",
"src/OpenFeature.Contrib.Providers.ConfigCat": "0.0.2",
- "src/OpenFeature.Contrib.Providers.FeatureManagement": "0.0.1"
+ "src/OpenFeature.Contrib.Providers.FeatureManagement": "0.0.1",
+ "src/OpenFeature.Contrib.Providers.Statsig": "0.0.1"
}
\ No newline at end of file
diff --git a/DotnetSdkContrib.sln b/DotnetSdkContrib.sln
index 21d89cdb..2c8566d1 100644
--- a/DotnetSdkContrib.sln
+++ b/DotnetSdkContrib.sln
@@ -37,6 +37,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Provide
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd.E2e.ProcessTest", "test\OpenFeature.Contrib.Providers.Flagd.E2e.ProcessTest\OpenFeature.Contrib.Providers.Flagd.E2e.ProcessTest.csproj", "{B8C5376B-BAFE-48B8-ABC1-111A93C033F2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Statsig", "src\OpenFeature.Contrib.Providers.Statsig\OpenFeature.Contrib.Providers.Statsig.csproj", "{4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Statsig.Test", "test\OpenFeature.Contrib.Providers.Statsig.Test\OpenFeature.Contrib.Providers.Statsig.Test.csproj", "{F3080350-B0AB-4D59-B416-50CC38C99087}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -103,6 +107,14 @@ Global
{B8C5376B-BAFE-48B8-ABC1-111A93C033F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8C5376B-BAFE-48B8-ABC1-111A93C033F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8C5376B-BAFE-48B8-ABC1-111A93C033F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3080350-B0AB-4D59-B416-50CC38C99087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3080350-B0AB-4D59-B416-50CC38C99087}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3080350-B0AB-4D59-B416-50CC38C99087}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3080350-B0AB-4D59-B416-50CC38C99087}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -123,5 +135,7 @@ Global
{4A2C6E0F-8A23-454F-8019-AE3DD91AA193} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{2ACD9150-A8F4-450E-B49A-C628895992BF} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
{B8C5376B-BAFE-48B8-ABC1-111A93C033F2} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
+ {4C7C0E2D-6ECC-4D17-BC5D-18F6BA6F872A} = {0E563821-BD08-4B7F-BF9D-395CAD80F026}
+ {F3080350-B0AB-4D59-B416-50CC38C99087} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}
EndGlobalSection
EndGlobal
diff --git a/build/Common.props b/build/Common.props
index ef35f745..52e8ce0f 100644
--- a/build/Common.props
+++ b/build/Common.props
@@ -24,10 +24,10 @@
Refer to https://docs.microsoft.com/nuget/concepts/package-versioning for semver syntax.
-->
- [1.4,)
+ [1.5,)
-
+
\ No newline at end of file
diff --git a/src/OpenFeature.Contrib.Providers.Statsig/EvaluationContextExtensions.cs b/src/OpenFeature.Contrib.Providers.Statsig/EvaluationContextExtensions.cs
new file mode 100644
index 00000000..4c1495ab
--- /dev/null
+++ b/src/OpenFeature.Contrib.Providers.Statsig/EvaluationContextExtensions.cs
@@ -0,0 +1,65 @@
+using OpenFeature.Model;
+using Statsig;
+
+namespace OpenFeature.Contrib.Providers.Statsig
+{
+ internal static class EvaluationContextExtensions
+ {
+ //These keys match the keys of the statsiguser object as descibed here
+ //https://docs.statsig.com/client/concepts/user
+ internal const string CONTEXT_APP_VERSION = "appVersion";
+ internal const string CONTEXT_COUNTRY = "country";
+ internal const string CONTEXT_EMAIL = "email";
+ internal const string CONTEXT_IP = "ip";
+ internal const string CONTEXT_LOCALE = "locale";
+ internal const string CONTEXT_USER_AGENT = "userAgent";
+ internal const string CONTEXT_PRIVATE_ATTRIBUTES = "privateAttributes";
+
+ public static StatsigUser AsStatsigUser(this EvaluationContext evaluationContext)
+ {
+ if (evaluationContext == null)
+ return null;
+
+ var user = new StatsigUser() { UserID = evaluationContext.TargetingKey };
+ foreach (var item in evaluationContext)
+ {
+ switch (item.Key)
+ {
+ case CONTEXT_APP_VERSION:
+ user.AppVersion = item.Value.AsString;
+ break;
+ case CONTEXT_COUNTRY:
+ user.Country = item.Value.AsString;
+ break;
+ case CONTEXT_EMAIL:
+ user.Email = item.Value.AsString;
+ break;
+ case CONTEXT_IP:
+ user.IPAddress = item.Value.AsString;
+ break;
+ case CONTEXT_USER_AGENT:
+ user.UserAgent = item.Value.AsString;
+ break;
+ case CONTEXT_LOCALE:
+ user.Locale = item.Value.AsString;
+ break;
+ case CONTEXT_PRIVATE_ATTRIBUTES:
+ if (item.Value.IsStructure)
+ {
+ var privateAttributes = item.Value.AsStructure;
+ foreach (var items in privateAttributes)
+ {
+ user.AddPrivateAttribute(items.Key, items.Value);
+ }
+ }
+ break;
+
+ default:
+ user.AddCustomProperty(item.Key, item.Value.AsObject);
+ break;
+ }
+ }
+ return user;
+ }
+ }
+}
diff --git a/src/OpenFeature.Contrib.Providers.Statsig/OpenFeature.Contrib.Providers.Statsig.csproj b/src/OpenFeature.Contrib.Providers.Statsig/OpenFeature.Contrib.Providers.Statsig.csproj
new file mode 100644
index 00000000..c77c1812
--- /dev/null
+++ b/src/OpenFeature.Contrib.Providers.Statsig/OpenFeature.Contrib.Providers.Statsig.csproj
@@ -0,0 +1,29 @@
+
+
+
+ OpenFeature.Contrib.Provider.Statsig
+ 0.0.1
+ $(VersionNumber)
+ preview
+ $(VersionNumber)
+ $(VersionNumber)
+ Statsig provider for .NET
+ README.md
+ Jens Kjær Henneberg
+
+
+
+
+ <_Parameter1>$(MSBuildProjectName).Test
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenFeature.Contrib.Providers.Statsig/README.md b/src/OpenFeature.Contrib.Providers.Statsig/README.md
new file mode 100644
index 00000000..60d996b1
--- /dev/null
+++ b/src/OpenFeature.Contrib.Providers.Statsig/README.md
@@ -0,0 +1,96 @@
+# Statsig Feature Flag .NET Provider
+
+The Statsig Flag provider allows you to connect to Statsig. Please note this is a minimal implementation - only `ResolveBooleanValue` is implemented.
+
+# .Net SDK usage
+
+## Install dependencies
+
+The first things we will do is install the **Open Feature SDK** and the **Statsig Feature Flag provider**.
+
+### .NET Cli
+```shell
+dotnet add package OpenFeature.Contrib.Providers.Statsig
+```
+### Package Manager
+
+```shell
+NuGet\Install-Package OpenFeature.Contrib.Providers.Statsig
+```
+### Package Reference
+
+```xml
+
+```
+### Packet cli
+
+```shell
+paket add OpenFeature.Contrib.Providers.Statsig
+```
+
+### Cake
+
+```shell
+// Install OpenFeature.Contrib.Providers.Statsig as a Cake Addin
+#addin nuget:?package=OpenFeature.Contrib.Providers.Statsig
+
+// Install OpenFeature.Contrib.Providers.Statsig as a Cake Tool
+#tool nuget:?package=OpenFeature.Contrib.Providers.Statsig
+```
+
+## Using the Statsig Provider with the OpenFeature SDK
+
+The following example shows how to use the Statsig provider with the OpenFeature SDK.
+
+```csharp
+using OpenFeature;
+using OpenFeature.Contrib.Providers.Statsig;
+using System;
+
+StatsigProvider statsigProvider = new StatsigProvider("#YOUR-SDK-KEY#");
+
+// Set the statsigProvider as the provider for the OpenFeature SDK
+await Api.Instance.SetProviderAsync(statsigProvider);
+
+IFeatureClient client = OpenFeature.Api.Instance.GetClient();
+
+bool isMyAwesomeFeatureEnabled = await client.GetBooleanValue("isMyAwesomeFeatureEnabled", false);
+
+if (isMyAwesomeFeatureEnabled)
+{
+ Console.WriteLine("New Feature enabled!");
+}
+
+```
+
+### Customizing the Statsig Provider
+
+The Statsig provider can be customized by passing a `Action` object to the constructor.
+
+```csharp
+var statsigProvider = new StatsigProvider("#YOUR-SDK-KEY#", options => options.LocalMode = true);
+```
+
+For a full list of options see the [Statsig documentation](https://docs.statsig.com/server/dotnetSDK#statsig-options).
+
+## EvaluationContext and Statsig User relationship
+
+Statsig has the concept of a [StatsigUser](https://docs.statsig.com/client/concepts/user) where you can evaluate a flag based on properties. The OpenFeature SDK has the concept of an EvaluationContext which is a dictionary of string keys and values. The Statsig provider will map the EvaluationContext to a StatsigUser.
+
+The following parameters are mapped to the corresponding Statsig pre-defined parameters
+
+| EvaluationContext Key | Statsig User Parameter |
+|-----------------------|---------------------------|
+| `appVersion` | `AppVersion` |
+| `country` | `Country` |
+| `email` | `Email` |
+| `ip` | `Ip` |
+| `locale` | `Locale` |
+| `userAgent` | `UserAgent` |
+| `privateAttributes` | `PrivateAttributes` |
+
+## Known issues and limitations
+- Only `ResolveBooleanValue` implemented for now
+
+- Gate BooleanEvaluation with default value true cannot fallback to true.
+ https://github.com/statsig-io/dotnet-sdk/issues/33
diff --git a/src/OpenFeature.Contrib.Providers.Statsig/StatsigProvider.cs b/src/OpenFeature.Contrib.Providers.Statsig/StatsigProvider.cs
new file mode 100644
index 00000000..3287302e
--- /dev/null
+++ b/src/OpenFeature.Contrib.Providers.Statsig/StatsigProvider.cs
@@ -0,0 +1,107 @@
+using OpenFeature.Constant;
+using OpenFeature.Error;
+using OpenFeature.Model;
+using Statsig;
+using Statsig.Server;
+using System;
+using System.Threading.Tasks;
+
+namespace OpenFeature.Contrib.Providers.Statsig
+{
+ ///
+ /// An OpenFeature which enables the use of the Statsig Server-Side SDK for .NET
+ /// with OpenFeature.
+ ///
+ ///
+ /// var provider = new StatsigProvider("my-sdk-key"), new StatsigProviderOptions(){LocalMode = false});
+ ///
+ /// OpenFeature.Api.Instance.SetProvider(provider);
+ ///
+ /// var client = OpenFeature.Api.Instance.GetClient();
+ ///
+ public sealed class StatsigProvider : FeatureProvider
+ {
+ volatile bool initialized = false;
+ private readonly Metadata _providerMetadata = new Metadata("Statsig provider");
+ private readonly string _sdkKey = "secret-"; //Dummy sdk key that works with local mode
+ private readonly StatsigServerOptions _options;
+ internal readonly ServerDriver ServerDriver;
+
+ ///
+ /// Creates new instance of
+ ///
+ /// SDK Key to access Statsig.
+ /// The action used to configure the client.
+ public StatsigProvider(string sdkKey = null, Action configurationAction = null)
+ {
+ if (sdkKey != null)
+ {
+ _sdkKey = sdkKey;
+ }
+ _options = new StatsigServerOptions();
+ configurationAction?.Invoke(_options);
+ ServerDriver = new ServerDriver(_sdkKey, _options);
+ }
+
+ ///
+ public override Metadata GetMetadata() => _providerMetadata;
+
+ ///
+ public override Task> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null)
+ {
+ //TODO: defaultvalue = true not yet supported due to https://github.com/statsig-io/dotnet-sdk/issues/33
+ if (defaultValue == true)
+ throw new FeatureProviderException(ErrorType.General, "defaultvalue = true not supported (https://github.com/statsig-io/dotnet-sdk/issues/33)");
+ if (GetStatus() != ProviderStatus.Ready)
+ return Task.FromResult(new ResolutionDetails(flagKey, defaultValue, ErrorType.ProviderNotReady));
+ var result = ServerDriver.CheckGateSync(context.AsStatsigUser(), flagKey);
+ return Task.FromResult(new ResolutionDetails(flagKey, result));
+ }
+
+ ///
+ public override Task> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override Task> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override Task> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override Task> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public override ProviderStatus GetStatus()
+ {
+ return initialized ? ProviderStatus.Ready : ProviderStatus.NotReady;
+ }
+
+ ///
+ public override async Task Initialize(EvaluationContext context)
+ {
+ var initResult = await ServerDriver.Initialize();
+ if (initResult == InitializeResult.Success || initResult == InitializeResult.LocalMode || initResult == InitializeResult.AlreadyInitialized)
+ {
+ initialized = true;
+ }
+ }
+
+ ///
+ public override Task Shutdown()
+ {
+ return ServerDriver.Shutdown();
+ }
+ }
+}
diff --git a/src/OpenFeature.Contrib.Providers.Statsig/version.txt b/src/OpenFeature.Contrib.Providers.Statsig/version.txt
new file mode 100644
index 00000000..8a9ecc2e
--- /dev/null
+++ b/src/OpenFeature.Contrib.Providers.Statsig/version.txt
@@ -0,0 +1 @@
+0.0.1
\ No newline at end of file
diff --git a/test/OpenFeature.Contrib.Providers.Statsig.Test/EvaluationContextExtensionsTests.cs b/test/OpenFeature.Contrib.Providers.Statsig.Test/EvaluationContextExtensionsTests.cs
new file mode 100644
index 00000000..6e678f83
--- /dev/null
+++ b/test/OpenFeature.Contrib.Providers.Statsig.Test/EvaluationContextExtensionsTests.cs
@@ -0,0 +1,113 @@
+using AutoFixture.Xunit2;
+using OpenFeature.Model;
+using System.Collections.Generic;
+using Xunit;
+
+namespace OpenFeature.Contrib.Providers.Statsig.Test
+{
+ public class EvaluationContextExtensionsTests
+ {
+ [Theory]
+ [AutoData]
+ public void AsStatsigUser_ShouldMapUserIdSuccessfully(string userId)
+ {
+ // Arrange
+ var evaluationContext = EvaluationContext.Builder().SetTargetingKey(userId).Build();
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.NotNull(statsigUser);
+ Assert.Equal(userId, statsigUser.UserID);
+ }
+
+ [Theory]
+ [AutoData]
+ public void AsStatsigUser_ShouldMapString(string key, string value)
+ {
+ // Arrange
+ var evaluationContext = EvaluationContext.Builder().Set(key, value).Build();
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.NotNull(statsigUser);
+ Assert.True(statsigUser.CustomProperties.TryGetValue(key, out var mappedValue));
+ Assert.Equal(value, mappedValue as string);
+ }
+
+
+ [Theory]
+ [AutoData]
+ public void AsStatsigUser_ShouldMapStatsigProperties(string appVersion, string country, string email, string ipAddress, string locale, string userAgent)
+ {
+ // Arrange
+ var evaluationContext = EvaluationContext.Builder()
+ .Set(EvaluationContextExtensions.CONTEXT_APP_VERSION, appVersion)
+ .Set(EvaluationContextExtensions.CONTEXT_COUNTRY, country)
+ .Set(EvaluationContextExtensions.CONTEXT_EMAIL, email)
+ .Set(EvaluationContextExtensions.CONTEXT_IP, ipAddress)
+ .Set(EvaluationContextExtensions.CONTEXT_LOCALE, locale)
+ .Set(EvaluationContextExtensions.CONTEXT_USER_AGENT, userAgent).Build();
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.NotNull(statsigUser);
+ Assert.Equal(statsigUser.AppVersion, appVersion);
+ Assert.Equal(statsigUser.Country, country);
+ Assert.Equal(statsigUser.Email, email);
+ Assert.Equal(statsigUser.IPAddress, ipAddress);
+ Assert.Equal(statsigUser.Locale, locale);
+ Assert.Equal(statsigUser.UserAgent, userAgent);
+ }
+
+ [Theory]
+ [AutoData]
+ public void AsStatsigUser_ShouldMapPrivateData(string key, string value)
+ {
+ var privateProperties = new Dictionary() { { key, new Value(value) } };
+
+ // Arrange
+ var evaluationContext = EvaluationContext.Builder().Set(EvaluationContextExtensions.CONTEXT_PRIVATE_ATTRIBUTES, new Structure(privateProperties)).Build();
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.NotNull(statsigUser);
+ Assert.True(statsigUser.PrivateAttributes.TryGetValue(key, out var mappedValue));
+ Assert.Equal(value, (mappedValue as Value)?.AsString);
+ }
+
+ [Fact]
+ public void AsStatsigUser_ShouldHandleNullEvaluationContext()
+ {
+ // Arrange
+ EvaluationContext evaluationContext = null;
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.Null(statsigUser);
+ }
+
+ [Fact]
+ public void AsStatsigUser_ShouldHandleEmptyUserID()
+ {
+ // Arrange
+ var evaluationContext = EvaluationContext.Builder().SetTargetingKey("").Build();
+
+ // Act
+ var statsigUser = evaluationContext.AsStatsigUser();
+
+ // Assert
+ Assert.NotNull(statsigUser);
+ Assert.Equal("", statsigUser.UserID);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/OpenFeature.Contrib.Providers.Statsig.Test/OpenFeature.Contrib.Providers.Statsig.Test.csproj b/test/OpenFeature.Contrib.Providers.Statsig.Test/OpenFeature.Contrib.Providers.Statsig.Test.csproj
new file mode 100644
index 00000000..ccfd29a0
--- /dev/null
+++ b/test/OpenFeature.Contrib.Providers.Statsig.Test/OpenFeature.Contrib.Providers.Statsig.Test.csproj
@@ -0,0 +1,10 @@
+
+
+ latest
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/OpenFeature.Contrib.Providers.Statsig.Test/StatsigProviderTest.cs b/test/OpenFeature.Contrib.Providers.Statsig.Test/StatsigProviderTest.cs
new file mode 100644
index 00000000..e08ef74e
--- /dev/null
+++ b/test/OpenFeature.Contrib.Providers.Statsig.Test/StatsigProviderTest.cs
@@ -0,0 +1,80 @@
+using AutoFixture.Xunit2;
+using OpenFeature.Constant;
+using OpenFeature.Error;
+using OpenFeature.Model;
+using System.Threading.Tasks;
+using Xunit;
+namespace OpenFeature.Contrib.Providers.Statsig.Test;
+
+public class StatsigProviderTest
+{
+ private StatsigProvider statsigProvider;
+
+ public StatsigProviderTest()
+ {
+ statsigProvider = new StatsigProvider("secret-", x => x.LocalMode = true);
+ }
+
+ [Fact]
+ public async Task StatsigProvider_Initialized_HasCorrectStatusAsync()
+ {
+ Assert.Equal(ProviderStatus.NotReady, statsigProvider.GetStatus());
+ await statsigProvider.Initialize(null);
+ Assert.Equal(ProviderStatus.Ready, statsigProvider.GetStatus());
+ }
+
+ [Theory]
+ [InlineAutoData(true, true)]
+ [InlineAutoData(false, false)]
+ public async Task GetBooleanValue_ForFeatureWithContext(bool flagValue, bool expectedValue, string userId, string flagName)
+ {
+ // Arrange
+ await statsigProvider.Initialize(null);
+ var ec = EvaluationContext.Builder().SetTargetingKey(userId).Build();
+ statsigProvider.ServerDriver.OverrideGate(flagName, flagValue, userId);
+
+ // Act & Assert
+ Assert.Equal(expectedValue, statsigProvider.ResolveBooleanValue(flagName, false, ec).Result.Value);
+ }
+
+ [Theory]
+ [InlineAutoData(true, false)]
+ [InlineAutoData(false, false)]
+ public async Task GetBooleanValue_ForFeatureWithNoContext_ReturnsFalse(bool flagValue, bool expectedValue, string flagName)
+ {
+ // Arrange
+ await statsigProvider.Initialize(null);
+ statsigProvider.ServerDriver.OverrideGate(flagName, flagValue);
+
+ // Act & Assert
+ Assert.Equal(expectedValue, statsigProvider.ResolveBooleanValue(flagName, false).Result.Value);
+ }
+
+ [Theory]
+ [AutoData]
+ public async Task GetBooleanValue_ForFeatureWithDefaultTrue_ThrowsException(string flagName)
+ {
+ // Arrange
+ await statsigProvider.Initialize(null);
+
+ // Act & Assert
+ Assert.ThrowsAny(() => statsigProvider.ResolveBooleanValue(flagName, true).Result.Value);
+ }
+
+ [Fact]
+ public async Task TestConcurrentInitilization_DoesntThrowException()
+ {
+ // Arrange
+ var concurrencyTestClass = new StatsigProvider();
+ const int numberOfThreads = 50;
+
+ // Act & Assert
+ var tasks = new Task[numberOfThreads];
+ for (int i = 0; i < numberOfThreads; i++)
+ {
+ tasks[i] = concurrencyTestClass.Initialize(null);
+ }
+
+ await Task.WhenAll(tasks);
+ }
+}