Skip to content

Commit

Permalink
fixup: pr feedback and tests
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <[email protected]>
  • Loading branch information
toddbaert committed Feb 9, 2024
1 parent dc00422 commit cbdde3e
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 28 deletions.
10 changes: 5 additions & 5 deletions src/OpenFeature/Providers/Memory/Flag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace OpenFeature.Providers.Memory
/// <summary>
/// Flag representation for the in-memory provider.
/// </summary>
public class Flag
public interface Flag
{

}
Expand All @@ -22,22 +22,22 @@ public sealed class Flag<T> : Flag
{
private Dictionary<string, T> Variants;
private string DefaultVariant;
private Func<EvaluationContext, string> ContextEvaluator;
private Func<EvaluationContext, string>? ContextEvaluator;

/// <summary>
/// Flag representation for the in-memory provider.
/// </summary>
/// <param name="variants">dictionary of variants and their corresponding values</param>
/// <param name="defaultVariant">default variant (should match 1 key in variants dictionary)</param>
/// <param name="contextEvaluator">optional context-sensitive evaluation function</param>
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string> contextEvaluator = null)
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string>? contextEvaluator = null)
{
this.Variants = variants;
this.DefaultVariant = defaultVariant;
this.ContextEvaluator = contextEvaluator;
}

internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, EvaluationContext evaluationContext)
internal ResolutionDetails<T> Evaluate(string flagKey, T _, EvaluationContext? evaluationContext)
{
T value;
if (this.ContextEvaluator == null)
Expand All @@ -58,7 +58,7 @@ internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, Evaluatio
}
else
{

Check warning on line 60 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L60

Added line #L60 was not covered by tests
var variant = this.ContextEvaluator.Invoke(evaluationContext);
var variant = this.ContextEvaluator.Invoke(evaluationContext ?? EvaluationContext.Empty);
this.Variants.TryGetValue(variant, out value);

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check failure on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / e2e-tests

Converting null literal or possible null value to non-nullable type.

Check failure on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / e2e-tests

Converting null literal or possible null value to non-nullable type.

Check failure on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / e2e-tests

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / packaging

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / packaging

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View workflow job for this annotation

GitHub Actions / packaging

Converting null literal or possible null value to non-nullable type.

Check warning on line 62 in src/OpenFeature/Providers/Memory/Flag.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature/Providers/Memory/Flag.cs#L62

Added line #L62 was not covered by tests
if (value == null)
{
Expand Down
16 changes: 8 additions & 8 deletions src/OpenFeature/Providers/Memory/InMemoryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public override Metadata GetMetadata()
/// Construct a new InMemoryProvider.
/// </summary>
/// <param name="flags">dictionary of Flags</param>
public InMemoryProvider(IDictionary<string, Flag> flags = null)
public InMemoryProvider(IDictionary<string, Flag>? flags = null)
{
if (flags == null)
{
Expand Down Expand Up @@ -67,7 +67,7 @@ public async ValueTask UpdateFlags(IDictionary<string, Flag> flags)
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
string flagKey,
bool defaultValue,
EvaluationContext context = null)
EvaluationContext? context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}
Expand All @@ -76,7 +76,7 @@ public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
public override Task<ResolutionDetails<string>> ResolveStringValue(
string flagKey,
string defaultValue,
EvaluationContext context = null)
EvaluationContext? context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}
Expand All @@ -85,7 +85,7 @@ public override Task<ResolutionDetails<string>> ResolveStringValue(
public override Task<ResolutionDetails<int>> ResolveIntegerValue(
string flagKey,
int defaultValue,
EvaluationContext context = null)
EvaluationContext? context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}
Expand All @@ -94,7 +94,7 @@ public override Task<ResolutionDetails<int>> ResolveIntegerValue(
public override Task<ResolutionDetails<double>> ResolveDoubleValue(
string flagKey,
double defaultValue,
EvaluationContext context = null)
EvaluationContext? context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}
Expand All @@ -103,12 +103,12 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(
public override Task<ResolutionDetails<Value>> ResolveStructureValue(
string flagKey,
Value defaultValue,
EvaluationContext context = null)
EvaluationContext? context = null)
{
return Task.FromResult(Resolve(flagKey, defaultValue, context));
}

private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext context)
private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext? context)
{
if (!this._flags.TryGetValue(flagKey, out var flag))
{
Expand All @@ -118,7 +118,7 @@ private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, Evaluati
{
if (typeof(Flag<T>).Equals(flag.GetType()))
{
return (flag as Flag<T>).Evaluate(flagKey, defaultValue, context);
return ((Flag<T>)flag).Evaluate(flagKey, defaultValue, context);
}
else
{
Expand Down
147 changes: 132 additions & 15 deletions test/OpenFeature.Tests/InMemoryProviderTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<string, Flag>(){
{
"boolean-flag", new Flag<bool>(
variants: new Dictionary<string, bool>(){
{ "on", true },
{ "off", false }
},
defaultVariant: "on"
)
},
{
"string-flag", new Flag<string>(
variants: new Dictionary<string, string>(){
{ "greeting", "hi" },
{ "parting", "bye" }
},
defaultVariant: "greeting"
)
},
{
"integer-flag", new Flag<int>(
variants: new Dictionary<string, int>(){
{ "one", 1 },
{ "ten", 10 }
},
defaultVariant: "ten"
)
},
{
"float-flag", new Flag<double>(
variants: new Dictionary<string, double>(){
{ "tenth", 0.1 },
{ "half", 0.5 }
},
defaultVariant: "half"
)
},
{
"object-flag", new Flag<Value>(
variants: new Dictionary<string, Value>(){
{ "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<bool> 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<string> 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<int> 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<double> 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<Value> 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<FlagNotFoundException>(() => commonProvider.ResolveBooleanValue("missing-flag", false, EvaluationContext.Empty)).ConfigureAwait(false);
}

[Fact]
public async void MismatchedFlag_ShouldThrow()
{
await Assert.ThrowsAsync<TypeMismatchException>(() => commonProvider.ResolveStringValue("boolean-flag", "nope", EvaluationContext.Empty)).ConfigureAwait(false);
}

[Fact]
public async void PutConfiguration_shouldUpdateConfigAndRunHandlers()
{
var handlerRuns = 0;
var provider = new InMemoryProvider(new Dictionary<string, Flag>(){
{
"boolean-flag", new Flag<bool>(
"old-flag", new Flag<bool>(
variants: new Dictionary<string, bool>(){
{ "on", true },
{ "off", false }
Expand All @@ -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<bool> details = await provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty).ConfigureAwait(false);
Assert.True(details.Value);

// update flags
await provider.UpdateFlags(new Dictionary<string, Flag>(){
{
"string-flag", new Flag<string>(
"new-flag", new Flag<string>(
variants: new Dictionary<string, string>(){
{ "greeting", "hi" },
{ "parting", "bye" }
Expand All @@ -48,10 +160,15 @@ await provider.UpdateFlags(new Dictionary<string, Flag>(){
)
}}).ConfigureAwait(false);

var res = await provider.GetEventChannel().Reader.ReadAsync().ConfigureAwait(false) as ProviderEventPayload;
Assert.Equal(ProviderEventTypes.ProviderConfigurationChanged, res.Type);

await Assert.ThrowsAsync<FlagNotFoundException>(() => 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<string> detailsAfter = await provider.ResolveStringValue("new-flag", "nope", EvaluationContext.Empty).ConfigureAwait(false);
Assert.True(details.Value);
Assert.Equal("hi", detailsAfter.Value);
}
}
}

0 comments on commit cbdde3e

Please sign in to comment.