Skip to content

Commit cbdde3e

Browse files
committed
fixup: pr feedback and tests
Signed-off-by: Todd Baert <[email protected]>
1 parent dc00422 commit cbdde3e

File tree

3 files changed

+145
-28
lines changed

3 files changed

+145
-28
lines changed

src/OpenFeature/Providers/Memory/Flag.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace OpenFeature.Providers.Memory
1010
/// <summary>
1111
/// Flag representation for the in-memory provider.
1212
/// </summary>
13-
public class Flag
13+
public interface Flag
1414
{
1515

1616
}
@@ -22,22 +22,22 @@ public sealed class Flag<T> : Flag
2222
{
2323
private Dictionary<string, T> Variants;
2424
private string DefaultVariant;
25-
private Func<EvaluationContext, string> ContextEvaluator;
25+
private Func<EvaluationContext, string>? ContextEvaluator;
2626

2727
/// <summary>
2828
/// Flag representation for the in-memory provider.
2929
/// </summary>
3030
/// <param name="variants">dictionary of variants and their corresponding values</param>
3131
/// <param name="defaultVariant">default variant (should match 1 key in variants dictionary)</param>
3232
/// <param name="contextEvaluator">optional context-sensitive evaluation function</param>
33-
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string> contextEvaluator = null)
33+
public Flag(Dictionary<string, T> variants, string defaultVariant, Func<EvaluationContext, string>? contextEvaluator = null)
3434
{
3535
this.Variants = variants;
3636
this.DefaultVariant = defaultVariant;
3737
this.ContextEvaluator = contextEvaluator;
3838
}
3939

40-
internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, EvaluationContext evaluationContext)
40+
internal ResolutionDetails<T> Evaluate(string flagKey, T _, EvaluationContext? evaluationContext)
4141
{
4242
T value;
4343
if (this.ContextEvaluator == null)
@@ -58,7 +58,7 @@ internal ResolutionDetails<T> Evaluate(string flagKey, T defaultValue, Evaluatio
5858
}
5959
else
6060
{
61-
var variant = this.ContextEvaluator.Invoke(evaluationContext);
61+
var variant = this.ContextEvaluator.Invoke(evaluationContext ?? EvaluationContext.Empty);
6262
this.Variants.TryGetValue(variant, out value);
6363
if (value == null)
6464
{

src/OpenFeature/Providers/Memory/InMemoryProvider.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public override Metadata GetMetadata()
3232
/// Construct a new InMemoryProvider.
3333
/// </summary>
3434
/// <param name="flags">dictionary of Flags</param>
35-
public InMemoryProvider(IDictionary<string, Flag> flags = null)
35+
public InMemoryProvider(IDictionary<string, Flag>? flags = null)
3636
{
3737
if (flags == null)
3838
{
@@ -67,7 +67,7 @@ public async ValueTask UpdateFlags(IDictionary<string, Flag> flags)
6767
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
6868
string flagKey,
6969
bool defaultValue,
70-
EvaluationContext context = null)
70+
EvaluationContext? context = null)
7171
{
7272
return Task.FromResult(Resolve(flagKey, defaultValue, context));
7373
}
@@ -76,7 +76,7 @@ public override Task<ResolutionDetails<bool>> ResolveBooleanValue(
7676
public override Task<ResolutionDetails<string>> ResolveStringValue(
7777
string flagKey,
7878
string defaultValue,
79-
EvaluationContext context = null)
79+
EvaluationContext? context = null)
8080
{
8181
return Task.FromResult(Resolve(flagKey, defaultValue, context));
8282
}
@@ -85,7 +85,7 @@ public override Task<ResolutionDetails<string>> ResolveStringValue(
8585
public override Task<ResolutionDetails<int>> ResolveIntegerValue(
8686
string flagKey,
8787
int defaultValue,
88-
EvaluationContext context = null)
88+
EvaluationContext? context = null)
8989
{
9090
return Task.FromResult(Resolve(flagKey, defaultValue, context));
9191
}
@@ -94,7 +94,7 @@ public override Task<ResolutionDetails<int>> ResolveIntegerValue(
9494
public override Task<ResolutionDetails<double>> ResolveDoubleValue(
9595
string flagKey,
9696
double defaultValue,
97-
EvaluationContext context = null)
97+
EvaluationContext? context = null)
9898
{
9999
return Task.FromResult(Resolve(flagKey, defaultValue, context));
100100
}
@@ -103,12 +103,12 @@ public override Task<ResolutionDetails<double>> ResolveDoubleValue(
103103
public override Task<ResolutionDetails<Value>> ResolveStructureValue(
104104
string flagKey,
105105
Value defaultValue,
106-
EvaluationContext context = null)
106+
EvaluationContext? context = null)
107107
{
108108
return Task.FromResult(Resolve(flagKey, defaultValue, context));
109109
}
110110

111-
private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext context)
111+
private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, EvaluationContext? context)
112112
{
113113
if (!this._flags.TryGetValue(flagKey, out var flag))
114114
{
@@ -118,7 +118,7 @@ private ResolutionDetails<T> Resolve<T>(string flagKey, T defaultValue, Evaluati
118118
{
119119
if (typeof(Flag<T>).Equals(flag.GetType()))
120120
{
121-
return (flag as Flag<T>).Evaluate(flagKey, defaultValue, context);
121+
return ((Flag<T>)flag).Evaluate(flagKey, defaultValue, context);
122122
}
123123
else
124124
{
+132-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
4+
using System.Threading;
45
using OpenFeature.Constant;
56
using OpenFeature.Error;
67
using OpenFeature.Model;
@@ -9,16 +10,133 @@
910

1011
namespace OpenFeature.Tests
1112
{
12-
// most of the in-memory tests are handled in the e2e suite
1313
public class InMemoryProviderTests
1414
{
15+
private FeatureProvider commonProvider;
16+
17+
public InMemoryProviderTests()
18+
{
19+
var provider = new InMemoryProvider(new Dictionary<string, Flag>(){
20+
{
21+
"boolean-flag", new Flag<bool>(
22+
variants: new Dictionary<string, bool>(){
23+
{ "on", true },
24+
{ "off", false }
25+
},
26+
defaultVariant: "on"
27+
)
28+
},
29+
{
30+
"string-flag", new Flag<string>(
31+
variants: new Dictionary<string, string>(){
32+
{ "greeting", "hi" },
33+
{ "parting", "bye" }
34+
},
35+
defaultVariant: "greeting"
36+
)
37+
},
38+
{
39+
"integer-flag", new Flag<int>(
40+
variants: new Dictionary<string, int>(){
41+
{ "one", 1 },
42+
{ "ten", 10 }
43+
},
44+
defaultVariant: "ten"
45+
)
46+
},
47+
{
48+
"float-flag", new Flag<double>(
49+
variants: new Dictionary<string, double>(){
50+
{ "tenth", 0.1 },
51+
{ "half", 0.5 }
52+
},
53+
defaultVariant: "half"
54+
)
55+
},
56+
{
57+
"object-flag", new Flag<Value>(
58+
variants: new Dictionary<string, Value>(){
59+
{ "empty", new Value() },
60+
{ "template", new Value(Structure.Builder()
61+
.Set("showImages", true)
62+
.Set("title", "Check out these pics!")
63+
.Set("imagesPerPage", 100).Build()
64+
)
65+
}
66+
},
67+
defaultVariant: "template"
68+
)
69+
}
70+
});
71+
72+
this.commonProvider = provider;
73+
}
74+
75+
[Fact]
76+
public async void GetBoolean_ShouldEvaluate()
77+
{
78+
ResolutionDetails<bool> details = await this.commonProvider.ResolveBooleanValue("boolean-flag", false, EvaluationContext.Empty).ConfigureAwait(false);
79+
Assert.True(details.Value);
80+
Assert.Equal(Reason.Static, details.Reason);
81+
Assert.Equal("on", details.Variant);
82+
}
83+
84+
[Fact]
85+
public async void GetString_ShouldEvaluate()
86+
{
87+
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValue("string-flag", "nope", EvaluationContext.Empty).ConfigureAwait(false);
88+
Assert.Equal("hi", details.Value);
89+
Assert.Equal(Reason.Static, details.Reason);
90+
Assert.Equal("greeting", details.Variant);
91+
}
92+
93+
[Fact]
94+
public async void GetInt_ShouldEvaluate()
95+
{
96+
ResolutionDetails<int> details = await this.commonProvider.ResolveIntegerValue("integer-flag", 13, EvaluationContext.Empty).ConfigureAwait(false);
97+
Assert.Equal(10, details.Value);
98+
Assert.Equal(Reason.Static, details.Reason);
99+
Assert.Equal("ten", details.Variant);
100+
}
101+
102+
[Fact]
103+
public async void GetDouble_ShouldEvaluate()
104+
{
105+
ResolutionDetails<double> details = await this.commonProvider.ResolveDoubleValue("float-flag", 13, EvaluationContext.Empty).ConfigureAwait(false);
106+
Assert.Equal(0.5, details.Value);
107+
Assert.Equal(Reason.Static, details.Reason);
108+
Assert.Equal("half", details.Variant);
109+
}
110+
111+
[Fact]
112+
public async void GetStruct_ShouldEvaluate()
113+
{
114+
ResolutionDetails<Value> details = await this.commonProvider.ResolveStructureValue("object-flag", new Value(), EvaluationContext.Empty).ConfigureAwait(false);
115+
Assert.Equal(true, details.Value.AsStructure["showImages"].AsBoolean);
116+
Assert.Equal("Check out these pics!", details.Value.AsStructure["title"].AsString);
117+
Assert.Equal(100, details.Value.AsStructure["imagesPerPage"].AsInteger);
118+
Assert.Equal(Reason.Static, details.Reason);
119+
Assert.Equal("template", details.Variant);
120+
}
121+
122+
[Fact]
123+
public async void MissingFlag_ShouldThrow()
124+
{
125+
await Assert.ThrowsAsync<FlagNotFoundException>(() => commonProvider.ResolveBooleanValue("missing-flag", false, EvaluationContext.Empty)).ConfigureAwait(false);
126+
}
127+
128+
[Fact]
129+
public async void MismatchedFlag_ShouldThrow()
130+
{
131+
await Assert.ThrowsAsync<TypeMismatchException>(() => commonProvider.ResolveStringValue("boolean-flag", "nope", EvaluationContext.Empty)).ConfigureAwait(false);
132+
}
133+
15134
[Fact]
16135
public async void PutConfiguration_shouldUpdateConfigAndRunHandlers()
17136
{
18-
var handlerRuns = 0;
19137
var provider = new InMemoryProvider(new Dictionary<string, Flag>(){
20138
{
21-
"boolean-flag", new Flag<bool>(
139+
"old-flag", new Flag<bool>(
22140
variants: new Dictionary<string, bool>(){
23141
{ "on", true },
24142
{ "off", false }
@@ -27,19 +145,13 @@ public async void PutConfiguration_shouldUpdateConfigAndRunHandlers()
27145
)
28146
}});
29147

30-
// setup client and handler and run initial eval
31-
await Api.Instance.SetProviderAsync("mem-test", provider).ConfigureAwait(false);
32-
var client = Api.Instance.GetClient("mem-test");
33-
client.AddHandler(ProviderEventTypes.ProviderConfigurationChanged, (details) =>
34-
{
35-
handlerRuns++;
36-
});
37-
Assert.True(await client.GetBooleanValue("boolean-flag", false).ConfigureAwait(false));
148+
ResolutionDetails<bool> details = await provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty).ConfigureAwait(false);
149+
Assert.True(details.Value);
38150

39151
// update flags
40152
await provider.UpdateFlags(new Dictionary<string, Flag>(){
41153
{
42-
"string-flag", new Flag<string>(
154+
"new-flag", new Flag<string>(
43155
variants: new Dictionary<string, string>(){
44156
{ "greeting", "hi" },
45157
{ "parting", "bye" }
@@ -48,10 +160,15 @@ await provider.UpdateFlags(new Dictionary<string, Flag>(){
48160
)
49161
}}).ConfigureAwait(false);
50162

163+
var res = await provider.GetEventChannel().Reader.ReadAsync().ConfigureAwait(false) as ProviderEventPayload;
164+
Assert.Equal(ProviderEventTypes.ProviderConfigurationChanged, res.Type);
165+
166+
await Assert.ThrowsAsync<FlagNotFoundException>(() => provider.ResolveBooleanValue("old-flag", false, EvaluationContext.Empty)).ConfigureAwait(false);
167+
51168
// new flag should be present, old gone (defaults), handler run.
52-
Assert.Equal("hi", await client.GetStringValue("string-flag", "nope").ConfigureAwait(false));
53-
Assert.False(await client.GetBooleanValue("boolean-flag", false).ConfigureAwait(false));
54-
Assert.Equal(1, handlerRuns);
169+
ResolutionDetails<string> detailsAfter = await provider.ResolveStringValue("new-flag", "nope", EvaluationContext.Empty).ConfigureAwait(false);
170+
Assert.True(details.Value);
171+
Assert.Equal("hi", detailsAfter.Value);
55172
}
56173
}
57174
}

0 commit comments

Comments
 (0)