diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 4dea1592..a9794050 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -34,4 +34,4 @@ jobs:
cp spec/specification/assets/gherkin/evaluation.feature test/OpenFeature.E2ETests/Features/
- name: Run Tests
- run: dotnet test test/OpenFeature.E2ETests/ --configuration Release --logger GitHubActions
+ run: dotnet test test/OpenFeature.E2ETests/ --logger GitHubActions
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);
}
}
}