From 435d5f721fe344fb17c6be2adac4440c2190f51f Mon Sep 17 00:00:00 2001 From: Vladimir Petrusevici Date: Wed, 18 Oct 2023 22:31:52 +0300 Subject: [PATCH] Add Flagsmith provider Signed-off-by: Vladimir Petrusevici --- .release-please-manifest.json | 3 +- DotnetSdkContrib.sln | 32 +- build/Common.props | 2 +- release-please-config.json | 10 + .../FlagsmithProvider.cs | 208 +++++++++ ...Feature.Contrib.Providers.Flagsmith.csproj | 32 ++ .../openfeature-icon.png | Bin 0 -> 15657 bytes .../FlagsmithProviderTest.cs | 400 ++++++++++++++++++ ...re.Contrib.Providers.Flagsmith.Test.csproj | 14 + 9 files changed, 690 insertions(+), 11 deletions(-) create mode 100644 src/OpenFeature.Contrib.Providers.Flagsmith/FlagsmithProvider.cs create mode 100644 src/OpenFeature.Contrib.Providers.Flagsmith/OpenFeature.Contrib.Providers.Flagsmith.csproj create mode 100644 src/OpenFeature.Contrib.Providers.Flagsmith/openfeature-icon.png create mode 100644 test/OpenFeature.Contrib.Providers.Flagsmith.Test/FlagsmithProviderTest.cs create mode 100644 test/OpenFeature.Contrib.Providers.Flagsmith.Test/OpenFeature.Contrib.Providers.Flagsmith.Test.csproj diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f3c91174..e503230b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,5 +1,6 @@ { "src/OpenFeature.Contrib.Hooks.Otel": "0.1.1", "src/OpenFeature.Contrib.Providers.Flagd": "0.1.7", - "src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.1.4" + "src/OpenFeature.Contrib.Providers.GOFeatureFlag": "0.1.4", + "src/OpenFeature.Contrib.Providers.Flagsmith": "0.1.0" } \ No newline at end of file diff --git a/DotnetSdkContrib.sln b/DotnetSdkContrib.sln index dea93120..91f68f30 100644 --- a/DotnetSdkContrib.sln +++ b/DotnetSdkContrib.sln @@ -5,28 +5,29 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0E563821-BD08-4B7F-BF9D-395CAD80F026}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd", "src\OpenFeature.Contrib.Providers.Flagd\OpenFeature.Contrib.Providers.Flagd.csproj", "{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagd", "src\OpenFeature.Contrib.Providers.Flagd\OpenFeature.Contrib.Providers.Flagd.csproj", "{6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Hooks.Otel", "src\OpenFeature.Contrib.Hooks.Otel\OpenFeature.Contrib.Hooks.Otel.csproj", "{82D10BAE-F1EE-432A-BD5D-DECAD07A84FE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Hooks.Otel", "src\OpenFeature.Contrib.Hooks.Otel\OpenFeature.Contrib.Hooks.Otel.csproj", "{82D10BAE-F1EE-432A-BD5D-DECAD07A84FE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B6D3230B-5E4D-4FF1-868E-2F4E325C84FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Hooks.Otel.Test", "test\OpenFeature.Contrib.Hooks.Otel.Test\OpenFeature.Contrib.Hooks.Otel.Test.csproj", "{199FA48A-06EF-4E15-8206-C095D1455A99}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Hooks.Otel.Test", "test\OpenFeature.Contrib.Hooks.Otel.Test\OpenFeature.Contrib.Hooks.Otel.Test.csproj", "{199FA48A-06EF-4E15-8206-C095D1455A99}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagd.Test", "test\OpenFeature.Contrib.Providers.Flagd.Test\OpenFeature.Contrib.Providers.Flagd.Test.csproj", "{206323A0-7334-4723-8394-C31C150B95DC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagd.Test", "test\OpenFeature.Contrib.Providers.Flagd.Test\OpenFeature.Contrib.Providers.Flagd.Test.csproj", "{206323A0-7334-4723-8394-C31C150B95DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag", "src\OpenFeature.Contrib.Providers.GOFeatureFlag\OpenFeature.Contrib.Providers.GOFeatureFlag.csproj", "{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.GOFeatureFlag", "src\OpenFeature.Contrib.Providers.GOFeatureFlag\OpenFeature.Contrib.Providers.GOFeatureFlag.csproj", "{F7BE205B-0375-4EC5-9B18-FAFEF7A78D71}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.GOFeatureFlag.Test", "test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj", "{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.GOFeatureFlag.Test", "test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test\OpenFeature.Contrib.Providers.GOFeatureFlag.Test.csproj", "{4041B63F-9CF6-4886-8FC7-BD1A7E45F859}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Contrib.Providers.Flagsmith", "src\OpenFeature.Contrib.Providers.Flagsmith\OpenFeature.Contrib.Providers.Flagsmith.csproj", "{47008BEE-7888-4B9B-8884-712A922C3F9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Contrib.Providers.Flagsmith.Test", "test\OpenFeature.Contrib.Providers.Flagsmith.Test\OpenFeature.Contrib.Providers.Flagsmith.Test.csproj", "{C3BA23C2-BEC3-4683-A64A-C914C3D8037E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -52,6 +53,17 @@ Global {4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Debug|Any CPU.Build.0 = Debug|Any CPU {4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.ActiveCfg = Release|Any CPU {4041B63F-9CF6-4886-8FC7-BD1A7E45F859}.Release|Any CPU.Build.0 = Release|Any CPU + {47008BEE-7888-4B9B-8884-712A922C3F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47008BEE-7888-4B9B-8884-712A922C3F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47008BEE-7888-4B9B-8884-712A922C3F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47008BEE-7888-4B9B-8884-712A922C3F9B}.Release|Any CPU.Build.0 = Release|Any CPU + {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3BA23C2-BEC3-4683-A64A-C914C3D8037E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {6F8FF25A-F22B-4083-B3F9-B4B9BB6FB699} = {0E563821-BD08-4B7F-BF9D-395CAD80F026} @@ -60,5 +72,7 @@ Global {206323A0-7334-4723-8394-C31C150B95DC} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE} {F7BE205B-0375-4EC5-9B18-FAFEF7A78D71} = {0E563821-BD08-4B7F-BF9D-395CAD80F026} {4041B63F-9CF6-4886-8FC7-BD1A7E45F859} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE} + {47008BEE-7888-4B9B-8884-712A922C3F9B} = {0E563821-BD08-4B7F-BF9D-395CAD80F026} + {C3BA23C2-BEC3-4683-A64A-C914C3D8037E} = {B6D3230B-5E4D-4FF1-868E-2F4E325C84FE} EndGlobalSection EndGlobal diff --git a/build/Common.props b/build/Common.props index 0cdf34da..fc34c474 100644 --- a/build/Common.props +++ b/build/Common.props @@ -25,6 +25,6 @@ --> [1.0.0,2.0) - [0.5,) + [1.2,) \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json index 430ab3bb..c7415e3e 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -31,6 +31,16 @@ "extra-files": [ "OpenFeature.Contrib.Providers.GOFeatureFlag.csproj" ] + }, + "src/OpenFeature.Contrib.Providers.Flagsmith": { + "package-name": "OpenFeature.Contrib.Providers.Flagsmith", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "versioning": "default", + "extra-files": [ + "OpenFeature.Contrib.Providers.Flagsmith.csproj" + ] } }, "changelog-sections": [ diff --git a/src/OpenFeature.Contrib.Providers.Flagsmith/FlagsmithProvider.cs b/src/OpenFeature.Contrib.Providers.Flagsmith/FlagsmithProvider.cs new file mode 100644 index 00000000..ffc05886 --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Flagsmith/FlagsmithProvider.cs @@ -0,0 +1,208 @@ +using Flagsmith; +using OpenFeature.Constant; +using OpenFeature.Model; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Trait = Flagsmith.Trait; +using OpenFeature.Error; +using System; + +namespace OpenFeature.Contrib.Providers.Flagsmith +{ + /// + /// FlagsmithProvider is the .NET provider implementation for the feature flag solution Flagsmith. + /// + public class FlagsmithProvider : FeatureProvider + { + private readonly static Metadata Metadata = new("Flagsmith Provider"); + internal readonly IFlagsmithClient _flagsmithClient; + /// + /// Creates new instance of + /// + /// Flagsmith client options. You can just use class + public FlagsmithProvider(IFlagsmithConfiguration options) + { + _flagsmithClient = new FlagsmithClient(options); + } + + /// + /// Creates new instance of + /// + /// Flagsmith client options. You can just use class + /// Http client that will be used for flagsmith requests. You also can use it to register as Typed HttpClient with as abstraction + public FlagsmithProvider(IFlagsmithConfiguration options, HttpClient httpClient) + { + _flagsmithClient = new FlagsmithClient(options, httpClient); + } + + + /// + /// Creates new instance of + /// + /// Precreated Flagsmith client. You can just use class. + public FlagsmithProvider(IFlagsmithClient flagsmithClient) + { + _flagsmithClient = flagsmithClient; + } + + private Task GetFlags(EvaluationContext ctx = null) + { + var key = ctx?.GetValue("targetingKey")?.AsString; + return string.IsNullOrEmpty(key) + ? _flagsmithClient.GetEnvironmentFlags() + : _flagsmithClient.GetIdentityFlags(key, ctx.AsDictionary().Select(x => new Trait(x.Key, x.Value.AsObject) as ITrait).ToList()); + } + + + /// + public override Metadata GetMetadata() => Metadata; + + /// + + public override async Task> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null) + { + var flags = await GetFlags(context); + var isFlagEnabled = await flags.IsFeatureEnabled(flagKey); + if (!isFlagEnabled) + { + return new ResolutionDetails(flagKey, defaultValue, reason: Reason.Disabled); + } + + var stringValue = await flags.GetFeatureValue(flagKey); + if (bool.TryParse(stringValue, out var parsedValue)) + { + return new ResolutionDetails(flagKey, parsedValue); + } + throw new TypeMismatchException("Failed to parse value in boolean type"); + + } + + /// + + public override async Task> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null) + { + + var flags = await GetFlags(context); + var isFlagEnabled = await flags.IsFeatureEnabled(flagKey); + if (!isFlagEnabled) + { + return new ResolutionDetails(flagKey, defaultValue, reason: Reason.Disabled); + } + + var stringValue = await flags.GetFeatureValue(flagKey); + return new ResolutionDetails(flagKey, stringValue); + } + + /// + public override async Task> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null) + { + + var flags = await GetFlags(context); + var isFlagEnabled = await flags.IsFeatureEnabled(flagKey); + if (!isFlagEnabled) + { + return new ResolutionDetails(flagKey, defaultValue, reason: Reason.Disabled); + } + + var stringValue = await flags.GetFeatureValue(flagKey); + if(int.TryParse(stringValue, out var parsedValue)) + { + return new ResolutionDetails(flagKey, parsedValue); + } + throw new TypeMismatchException("Failed to parse value in int type"); + } + + /// + public override async Task> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null) + { + + var flags = await GetFlags(context); + var isFlagEnabled = await flags.IsFeatureEnabled(flagKey); + if (!isFlagEnabled) + { + return new ResolutionDetails(flagKey, defaultValue, reason: Reason.Disabled); + } + + var stringValue = await flags.GetFeatureValue(flagKey); + if (double.TryParse(stringValue, out var parsedValue)) + { + return new ResolutionDetails(flagKey, parsedValue); + } + throw new TypeMismatchException("Failed to parse value in double type"); + } + + + /// + public override async Task> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null) + { + + var flags = await GetFlags(context); + var isFlagEnabled = await flags.IsFeatureEnabled(flagKey); + if (!isFlagEnabled) + { + return new ResolutionDetails(flagKey, defaultValue, reason: Reason.Disabled); + } + var stringValue = await flags.GetFeatureValue(flagKey); + + try + { + var mappedValue = JsonNode.Parse(stringValue); + var value = ConvertValue(mappedValue); + if (value is not null) + { + return new ResolutionDetails(flagKey, value); + + } + } + catch(Exception ex) + { + throw new TypeMismatchException("Failed to parse value in structure type", ex); + } + throw new TypeMismatchException("Failed to parse value in structure type"); + } + + /// + /// convertValue is converting the object return by the proxy response in the right type. + /// + /// The value we have received + /// A converted object + private Value ConvertValue(JsonNode node) + { + if(node == null) + return null; + if (node is JsonArray jsonArray) + { + var arr = new List(); + foreach (var item in jsonArray) + { + var convertedValue = ConvertValue(item); + if (convertedValue != null) arr.Add(convertedValue); + } + return new Value(arr); + } + + if (node is JsonObject jsonObject) + { + var dict = jsonObject.ToDictionary(x => x.Key, x => ConvertValue(x.Value)); + + return new Value(new Structure(dict)); + } + + if (node.AsValue().TryGetValue(out var jsonElement)) + { + if (jsonElement.ValueKind == JsonValueKind.False || jsonElement.ValueKind == JsonValueKind.True) + return new Value(jsonElement.GetBoolean()); + if (jsonElement.ValueKind == JsonValueKind.Number) + return new Value(jsonElement.GetDouble()); + + if (jsonElement.ValueKind == JsonValueKind.String) + return new Value(jsonElement.ToString()); + } + return null; + } + } +} \ No newline at end of file diff --git a/src/OpenFeature.Contrib.Providers.Flagsmith/OpenFeature.Contrib.Providers.Flagsmith.csproj b/src/OpenFeature.Contrib.Providers.Flagsmith/OpenFeature.Contrib.Providers.Flagsmith.csproj new file mode 100644 index 00000000..12698004 --- /dev/null +++ b/src/OpenFeature.Contrib.Providers.Flagsmith/OpenFeature.Contrib.Providers.Flagsmith.csproj @@ -0,0 +1,32 @@ + + + + netstandard20 + OpenFeature.Contrib.Providers.Flagsmith + 0.1.0 + + $(VersionNumber) + $(VersionNumber) + $(VersionNumber) + Flagsmith provider for .NET + https://openfeature.dev + https://github.com/open-feature/dotnet-sdk-contrib + Vladimir Petrusevici + + + + + + <_Parameter1>$(MSBuildProjectName).Test + + + + + + + + + + latest + + diff --git a/src/OpenFeature.Contrib.Providers.Flagsmith/openfeature-icon.png b/src/OpenFeature.Contrib.Providers.Flagsmith/openfeature-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..10753d9173bda2c47c86f850e55cc472f0e22c56 GIT binary patch literal 15657 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 new () + { + ApiUrl = "https://edge.api.flagsmith.com/api/v1/", + EnvironmentKey = string.Empty, + EnableClientSideEvaluation = false, + EnvironmentRefreshIntervalSeconds = 60, + EnableAnalytics = false, + Retries = 1 + }; + [Fact] + public void CreateFlagmithProvider_WithValidCredetials_CreatesInstanceSuccessfully() + { + // Arrange + var config = GetDefaultFlagsmithConfiguration(); + + // Act + var flagsmithProvider = new FlagsmithProvider(config); + + + // Assert + Assert.NotNull(flagsmithProvider._flagsmithClient); + } + + [Fact] + public void CreateFlagmithProvider_WithValidCredetialsAndCustomHttpClient_CreatesInstanceSuccessfully() + { + // Arrange + var config = GetDefaultFlagsmithConfiguration(); + using var httpClient = new HttpClient(); + // Act + var flagsmithProvider = new FlagsmithProvider(config, httpClient); + + + // Assert + Assert.NotNull(flagsmithProvider._flagsmithClient); + } + + [Fact] + public async Task GetBooleanValue_ForEnabledFeatureWithValidFormat_ReturnCorrectValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("true"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveBooleanValue("example-feature", false); + + // Assert + Assert.True(result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Null(result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetBooleanValue_ForDisabledFeatureWithValidFormat_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("false"); + flags.IsFeatureEnabled("example-feature").Returns(false); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveBooleanValue("example-feature", true); + + // Assert + Assert.True(result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Equal(Reason.Disabled, result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetBooleanValue_ForEnabledFeatureWithWrongFormatValue_ThrowsTypeMismatch() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("hreni"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act and Assert + await Assert.ThrowsAsync( () => flagsmithProvider.ResolveBooleanValue("example-feature", true)); + + } + + + [Fact] + public async Task GetDoubleValue_ForEnabledFeatureWithValidFormat_ReturnCorrectValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("32,334"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveDoubleValue("example-feature", 32.22); + + // Assert + Assert.Equal(32.334, result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Null(result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + + [Fact] + public async Task GetDoubleValue_ForDisabledFeatureWithValidFormat_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("4112"); + flags.IsFeatureEnabled("example-feature").Returns(false); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveDoubleValue("example-feature", -32.22); + + // Assert + Assert.Equal(-32.22, result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Equal(Reason.Disabled, result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetDoubleValue_ForEnabledFeatureWithWrongFormatValue_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("hreni"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act and Assert + await Assert.ThrowsAsync(() => flagsmithProvider.ResolveDoubleValue("example-feature", 2222.22133)); + } + + + + [Fact] + public async Task GetStringValue_ForEnabledFeatureWithValidFormat_ReturnCorrectValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("example"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveStringValue("example-feature", "example"); + + // Assert + Assert.Equal("example", result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Null(result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + + [Fact] + public async Task GetStringValue_ForDisabledFeatureWithValidFormat_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("4112"); + flags.IsFeatureEnabled("example-feature").Returns(false); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveStringValue("example-feature", "3333a"); + + // Assert + Assert.Equal("3333a", result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Equal(Reason.Disabled, result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + + [Fact] + public async Task GetIntValue_ForEnabledFeatureWithValidFormat_ReturnCorrectValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("232"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveIntegerValue("example-feature", 32); + + // Assert + Assert.Equal(232, result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Null(result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetIntValue_ForDisabledFeatureWithValidFormat_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("4112"); + flags.IsFeatureEnabled("example-feature").Returns(false); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveIntegerValue("example-feature", -32); + + // Assert + Assert.Equal(-32, result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Equal(Reason.Disabled, result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetIntValue_ForEnabledFeatureWithWrongFormatValue_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("hreni"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act and Assert + await Assert.ThrowsAsync(() => flagsmithProvider.ResolveIntegerValue("example-feature", 2222)); + } + + [Fact] + public async Task GetStructureValue_ForEnabledFeatureWithValidFormat_ReturnCorrectValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + var expectedValue = + """ + { + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML" + ] + }, + "GlossSee": "markup" + } + } + } + } + } + """; + flags.GetFeatureValue("example-feature").Returns(expectedValue); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var defaultObject = new Value(Structure.Empty); + + var result = await flagsmithProvider.ResolveStructureValue("example-feature", defaultObject); + + // Assert + var glossary = result.Value.AsStructure.GetValue("glossary"); + Assert.True(glossary.IsStructure); + Assert.Equal("example glossary", glossary.AsStructure.GetValue("title").AsString); + var glossDiv = glossary.AsStructure.GetValue("GlossDiv"); + Assert.True(glossDiv.IsStructure); + var glossList = glossDiv.AsStructure.GetValue("GlossList"); + Assert.True(glossList.IsStructure); + var glossEntry = glossList.AsStructure.GetValue("GlossEntry"); + Assert.True(glossEntry.IsStructure); + Assert.Equal("SGML", glossEntry.AsStructure.GetValue("SortAs").AsString); + var glossDef = glossEntry.AsStructure.GetValue("GlossDef"); + Assert.True(glossDef.IsStructure); + var glossSeeAlso = glossDef.AsStructure.GetValue("GlossSeeAlso"); + Assert.True(glossSeeAlso.IsList); + Assert.Equal(2, glossSeeAlso.AsList.Count); + Assert.Equal("GML", glossSeeAlso.AsList.First().AsString); + Assert.Equal("XML", glossSeeAlso.AsList.Last().AsString); + + Assert.Equal("example-feature", result.FlagKey); + Assert.Null(result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetStructureValue_ForDisabledFeatureWithValidFormat_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("4112"); + flags.IsFeatureEnabled("example-feature").Returns(false); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var defaultObject = new Value("default"); + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act + var result = await flagsmithProvider.ResolveStructureValue("example-feature", defaultObject); + + // Assert + Assert.Equal(defaultObject, result.Value); + Assert.Equal("example-feature", result.FlagKey); + Assert.Equal(Reason.Disabled, result.Reason); + Assert.Equal(ErrorType.None, result.ErrorType); + } + + [Fact] + public async Task GetStructureValue_ForEnabledFeatureWithWrongFormatValue_ReturnDefaultValue() + { + // Arrange + var flagsmithClient = Substitute.For(); + var flags = Substitute.For(); + flags.GetFeatureValue("example-feature").Returns("hreni"); + flags.IsFeatureEnabled("example-feature").Returns(true); + flagsmithClient.GetEnvironmentFlags().Returns(flags); + + var defaultObject = new Value("default"); + var flagsmithProvider = new FlagsmithProvider(flagsmithClient); + + // Act and Assert + await Assert.ThrowsAsync(() => flagsmithProvider.ResolveStructureValue("example-feature", defaultObject)); + } + } + + public class ExampleConfig + { + public string ExampleText { get; set; } + public int ExampleInt { get; set; } + } + +} diff --git a/test/OpenFeature.Contrib.Providers.Flagsmith.Test/OpenFeature.Contrib.Providers.Flagsmith.Test.csproj b/test/OpenFeature.Contrib.Providers.Flagsmith.Test/OpenFeature.Contrib.Providers.Flagsmith.Test.csproj new file mode 100644 index 00000000..e35f8e7e --- /dev/null +++ b/test/OpenFeature.Contrib.Providers.Flagsmith.Test/OpenFeature.Contrib.Providers.Flagsmith.Test.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + latest + + \ No newline at end of file