From cbdde3ef3c4f8c272f731340f88ddddd5584e6ba Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Fri, 9 Feb 2024 10:28:39 -0500 Subject: [PATCH] fixup: pr feedback and tests Signed-off-by: Todd Baert --- src/OpenFeature/Providers/Memory/Flag.cs | 10 +- .../Providers/Memory/InMemoryProvider.cs | 16 +- .../InMemoryProviderTests.cs | 147 ++++++++++++++++-- 3 files changed, 145 insertions(+), 28 deletions(-) diff --git a/src/OpenFeature/Providers/Memory/Flag.cs b/src/OpenFeature/Providers/Memory/Flag.cs index af51b120..e1a88287 100644 --- a/src/OpenFeature/Providers/Memory/Flag.cs +++ b/src/OpenFeature/Providers/Memory/Flag.cs @@ -10,7 +10,7 @@ namespace OpenFeature.Providers.Memory /// /// Flag representation for the in-memory provider. /// - public class Flag + public interface Flag { } @@ -22,7 +22,7 @@ public sealed class Flag : Flag { private Dictionary Variants; private string DefaultVariant; - private Func ContextEvaluator; + private Func? ContextEvaluator; /// /// Flag representation for the in-memory provider. @@ -30,14 +30,14 @@ public sealed class Flag : Flag /// dictionary of variants and their corresponding values /// default variant (should match 1 key in variants dictionary) /// optional context-sensitive evaluation function - public Flag(Dictionary variants, string defaultVariant, Func contextEvaluator = null) + public Flag(Dictionary variants, string defaultVariant, Func? contextEvaluator = null) { this.Variants = variants; this.DefaultVariant = defaultVariant; this.ContextEvaluator = contextEvaluator; } - internal ResolutionDetails Evaluate(string flagKey, T defaultValue, EvaluationContext evaluationContext) + internal ResolutionDetails Evaluate(string flagKey, T _, EvaluationContext? evaluationContext) { T value; if (this.ContextEvaluator == null) @@ -58,7 +58,7 @@ internal ResolutionDetails Evaluate(string flagKey, T defaultValue, Evaluatio } else { - var variant = this.ContextEvaluator.Invoke(evaluationContext); + var variant = this.ContextEvaluator.Invoke(evaluationContext ?? EvaluationContext.Empty); this.Variants.TryGetValue(variant, out value); if (value == null) { diff --git a/src/OpenFeature/Providers/Memory/InMemoryProvider.cs b/src/OpenFeature/Providers/Memory/InMemoryProvider.cs index 9aafd133..d7a9ffaf 100644 --- a/src/OpenFeature/Providers/Memory/InMemoryProvider.cs +++ b/src/OpenFeature/Providers/Memory/InMemoryProvider.cs @@ -32,7 +32,7 @@ public override Metadata GetMetadata() /// Construct a new InMemoryProvider. /// /// dictionary of Flags - public InMemoryProvider(IDictionary flags = null) + public InMemoryProvider(IDictionary? flags = null) { if (flags == null) { @@ -67,7 +67,7 @@ public async ValueTask UpdateFlags(IDictionary flags) public override Task> ResolveBooleanValue( string flagKey, bool defaultValue, - EvaluationContext context = null) + EvaluationContext? context = null) { return Task.FromResult(Resolve(flagKey, defaultValue, context)); } @@ -76,7 +76,7 @@ public override Task> ResolveBooleanValue( public override Task> ResolveStringValue( string flagKey, string defaultValue, - EvaluationContext context = null) + EvaluationContext? context = null) { return Task.FromResult(Resolve(flagKey, defaultValue, context)); } @@ -85,7 +85,7 @@ public override Task> ResolveStringValue( public override Task> ResolveIntegerValue( string flagKey, int defaultValue, - EvaluationContext context = null) + EvaluationContext? context = null) { return Task.FromResult(Resolve(flagKey, defaultValue, context)); } @@ -94,7 +94,7 @@ public override Task> ResolveIntegerValue( public override Task> ResolveDoubleValue( string flagKey, double defaultValue, - EvaluationContext context = null) + EvaluationContext? context = null) { return Task.FromResult(Resolve(flagKey, defaultValue, context)); } @@ -103,12 +103,12 @@ public override Task> ResolveDoubleValue( public override Task> ResolveStructureValue( string flagKey, Value defaultValue, - EvaluationContext context = null) + EvaluationContext? context = null) { return Task.FromResult(Resolve(flagKey, defaultValue, context)); } - private ResolutionDetails Resolve(string flagKey, T defaultValue, EvaluationContext context) + private ResolutionDetails Resolve(string flagKey, T defaultValue, EvaluationContext? context) { if (!this._flags.TryGetValue(flagKey, out var flag)) { @@ -118,7 +118,7 @@ private ResolutionDetails Resolve(string flagKey, T defaultValue, Evaluati { if (typeof(Flag).Equals(flag.GetType())) { - return (flag as Flag).Evaluate(flagKey, defaultValue, context); + return ((Flag)flag).Evaluate(flagKey, defaultValue, context); } else { diff --git a/test/OpenFeature.Tests/InMemoryProviderTests.cs b/test/OpenFeature.Tests/InMemoryProviderTests.cs index 61587cf4..799fd9db 100644 --- a/test/OpenFeature.Tests/InMemoryProviderTests.cs +++ b/test/OpenFeature.Tests/InMemoryProviderTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading; using OpenFeature.Constant; using OpenFeature.Error; using OpenFeature.Model; @@ -9,16 +10,133 @@ namespace OpenFeature.Tests { - // most of the in-memory tests are handled in the e2e suite public class InMemoryProviderTests { + private FeatureProvider commonProvider; + + public InMemoryProviderTests() + { + var provider = new InMemoryProvider(new Dictionary(){ + { + "boolean-flag", new Flag( + variants: new Dictionary(){ + { "on", true }, + { "off", false } + }, + defaultVariant: "on" + ) + }, + { + "string-flag", new Flag( + variants: new Dictionary(){ + { "greeting", "hi" }, + { "parting", "bye" } + }, + defaultVariant: "greeting" + ) + }, + { + "integer-flag", new Flag( + variants: new Dictionary(){ + { "one", 1 }, + { "ten", 10 } + }, + defaultVariant: "ten" + ) + }, + { + "float-flag", new Flag( + variants: new Dictionary(){ + { "tenth", 0.1 }, + { "half", 0.5 } + }, + defaultVariant: "half" + ) + }, + { + "object-flag", new Flag( + variants: new Dictionary(){ + { "empty", new Value() }, + { "template", new Value(Structure.Builder() + .Set("showImages", true) + .Set("title", "Check out these pics!") + .Set("imagesPerPage", 100).Build() + ) + } + }, + defaultVariant: "template" + ) + } + }); + + this.commonProvider = provider; + } + + [Fact] + public async void GetBoolean_ShouldEvaluate() + { + ResolutionDetails details = await this.commonProvider.ResolveBooleanValue("boolean-flag", false, EvaluationContext.Empty).ConfigureAwait(false); + Assert.True(details.Value); + Assert.Equal(Reason.Static, details.Reason); + Assert.Equal("on", details.Variant); + } + + [Fact] + public async void GetString_ShouldEvaluate() + { + ResolutionDetails details = await this.commonProvider.ResolveStringValue("string-flag", "nope", EvaluationContext.Empty).ConfigureAwait(false); + Assert.Equal("hi", details.Value); + Assert.Equal(Reason.Static, details.Reason); + Assert.Equal("greeting", details.Variant); + } + + [Fact] + public async void GetInt_ShouldEvaluate() + { + ResolutionDetails details = await this.commonProvider.ResolveIntegerValue("integer-flag", 13, EvaluationContext.Empty).ConfigureAwait(false); + Assert.Equal(10, details.Value); + Assert.Equal(Reason.Static, details.Reason); + Assert.Equal("ten", details.Variant); + } + + [Fact] + public async void GetDouble_ShouldEvaluate() + { + ResolutionDetails details = await this.commonProvider.ResolveDoubleValue("float-flag", 13, EvaluationContext.Empty).ConfigureAwait(false); + Assert.Equal(0.5, details.Value); + Assert.Equal(Reason.Static, details.Reason); + Assert.Equal("half", details.Variant); + } + + [Fact] + public async void GetStruct_ShouldEvaluate() + { + ResolutionDetails details = await this.commonProvider.ResolveStructureValue("object-flag", new Value(), EvaluationContext.Empty).ConfigureAwait(false); + Assert.Equal(true, details.Value.AsStructure["showImages"].AsBoolean); + Assert.Equal("Check out these pics!", details.Value.AsStructure["title"].AsString); + Assert.Equal(100, details.Value.AsStructure["imagesPerPage"].AsInteger); + Assert.Equal(Reason.Static, details.Reason); + Assert.Equal("template", details.Variant); + } + + [Fact] + public async void MissingFlag_ShouldThrow() + { + await Assert.ThrowsAsync(() => commonProvider.ResolveBooleanValue("missing-flag", false, EvaluationContext.Empty)).ConfigureAwait(false); + } + + [Fact] + public async void MismatchedFlag_ShouldThrow() + { + await Assert.ThrowsAsync(() => commonProvider.ResolveStringValue("boolean-flag", "nope", EvaluationContext.Empty)).ConfigureAwait(false); + } + [Fact] public async void PutConfiguration_shouldUpdateConfigAndRunHandlers() { - var handlerRuns = 0; var provider = new InMemoryProvider(new Dictionary(){ { - "boolean-flag", new Flag( + "old-flag", new Flag( variants: new Dictionary(){ { "on", true }, { "off", false } @@ -27,19 +145,13 @@ public async void PutConfiguration_shouldUpdateConfigAndRunHandlers() ) }}); - // setup client and handler and run initial eval - await Api.Instance.SetProviderAsync("mem-test", provider).ConfigureAwait(false); - var client = Api.Instance.GetClient("mem-test"); - client.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, (details) => - { - handlerRuns++; - }); - Assert.True(await client.GetBooleanValue("boolean-flag", false).ConfigureAwait(false)); + ResolutionDetails details = await provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty).ConfigureAwait(false); + Assert.True(details.Value); // update flags await provider.UpdateFlags(new Dictionary(){ { - "string-flag", new Flag( + "new-flag", new Flag( variants: new Dictionary(){ { "greeting", "hi" }, { "parting", "bye" } @@ -48,10 +160,15 @@ await provider.UpdateFlags(new Dictionary(){ ) }}).ConfigureAwait(false); + var res = await provider.GetEventChannel().Reader.ReadAsync().ConfigureAwait(false) as ProviderEventPayload; + Assert.Equal(ProviderEventTypes.ProviderConfigurationChanged, res.Type); + + await Assert.ThrowsAsync(() => provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty)).ConfigureAwait(false); + // new flag should be present, old gone (defaults), handler run. - Assert.Equal("hi", await client.GetStringValue("string-flag", "nope").ConfigureAwait(false)); - Assert.False(await client.GetBooleanValue("boolean-flag", false).ConfigureAwait(false)); - Assert.Equal(1, handlerRuns); + ResolutionDetails detailsAfter = await provider.ResolveStringValue("new-flag", "nope", EvaluationContext.Empty).ConfigureAwait(false); + Assert.True(details.Value); + Assert.Equal("hi", detailsAfter.Value); } } }