Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 38 additions & 31 deletions src/OpenFeature/Providers/Memory/Flag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,41 +36,48 @@ public Flag(Dictionary<string, T> variants, string defaultVariant, Func<Evaluati

internal ResolutionDetails<T> Evaluate(string flagKey, T _, EvaluationContext? evaluationContext)
{
T? value;
if (this._contextEvaluator == null)
{
if (this._variants.TryGetValue(this._defaultVariant, out value))
{
return new ResolutionDetails<T>(
flagKey,
value,
variant: this._defaultVariant,
reason: Reason.Static,
flagMetadata: this._flagMetadata
);
}
else
{
throw new GeneralException($"variant {this._defaultVariant} not found");
}
return this.EvaluateDefaultVariant(flagKey);
}
else

string variant;
try
{
variant = this._contextEvaluator.Invoke(evaluationContext ?? EvaluationContext.Empty);
}
catch (Exception)
{
var variant = this._contextEvaluator.Invoke(evaluationContext ?? EvaluationContext.Empty);
if (!this._variants.TryGetValue(variant, out value))
{
throw new GeneralException($"variant {variant} not found");
}
else
{
return new ResolutionDetails<T>(
flagKey,
value,
variant: variant,
reason: Reason.TargetingMatch,
flagMetadata: this._flagMetadata
);
}
return this.EvaluateDefaultVariant(flagKey, Reason.Default);
}

if (!this._variants.TryGetValue(variant, out var value))
{
return this.EvaluateDefaultVariant(flagKey, Reason.Default);
}

return new ResolutionDetails<T>(
flagKey,
value,
variant: variant,
reason: Reason.TargetingMatch,
flagMetadata: this._flagMetadata
);
}

private ResolutionDetails<T> EvaluateDefaultVariant(string flagKey, string reason = Reason.Static)
{
if (this._variants.TryGetValue(this._defaultVariant, out var value))
{
return new ResolutionDetails<T>(
flagKey,
value,
variant: this._defaultVariant,
reason: reason,
flagMetadata: this._flagMetadata
);
}

throw new GeneralException($"variant {this._defaultVariant} not found");
}
}
108 changes: 106 additions & 2 deletions test/OpenFeature.Tests/Providers/Memory/InMemoryProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ public InMemoryProviderTests()
return "missing";
}
)
},
{
"evaluator-throws-flag", new Flag<bool>(
variants: new Dictionary<string, bool>(){
{ "on", true },
{ "off", false }
},
defaultVariant: "on",
(context) => {
throw new Exception("Cannot evaluate flag at the moment.");
}
)
}
});

Expand All @@ -113,6 +125,18 @@ public async Task GetBoolean_ShouldEvaluateWithReasonAndVariant()
Assert.Equal("on", details.Variant);
}

[Fact]
public async Task GetBoolean_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Act
ResolutionDetails<bool> details = await this.commonProvider.ResolveBooleanValueAsync("boolean-flag", false);

// Assert
Assert.True(details.Value);
Assert.Equal(Reason.Static, details.Reason);
Assert.Equal("on", details.Variant);
}

[Fact]
public async Task GetString_ShouldEvaluateWithReasonAndVariant()
{
Expand All @@ -122,6 +146,18 @@ public async Task GetString_ShouldEvaluateWithReasonAndVariant()
Assert.Equal("greeting", details.Variant);
}

[Fact]
public async Task GetString_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Act
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValueAsync("string-flag", "nope");

// Assert
Assert.Equal("hi", details.Value);
Assert.Equal(Reason.Static, details.Reason);
Assert.Equal("greeting", details.Variant);
}

[Fact]
public async Task GetInt_ShouldEvaluateWithReasonAndVariant()
{
Expand All @@ -131,6 +167,18 @@ public async Task GetInt_ShouldEvaluateWithReasonAndVariant()
Assert.Equal("ten", details.Variant);
}

[Fact]
public async Task GetInt_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Act
ResolutionDetails<int> details = await this.commonProvider.ResolveIntegerValueAsync("integer-flag", 13);

// Assert
Assert.Equal(10, details.Value);
Assert.Equal(Reason.Static, details.Reason);
Assert.Equal("ten", details.Variant);
}

[Fact]
public async Task GetDouble_ShouldEvaluateWithReasonAndVariant()
{
Expand All @@ -140,6 +188,18 @@ public async Task GetDouble_ShouldEvaluateWithReasonAndVariant()
Assert.Equal("half", details.Variant);
}

[Fact]
public async Task GetDouble_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Arrange
ResolutionDetails<double> details = await this.commonProvider.ResolveDoubleValueAsync("float-flag", 13);

// Assert
Assert.Equal(0.5, details.Value);
Assert.Equal(Reason.Static, details.Reason);
Assert.Equal("half", details.Variant);
}

[Fact]
public async Task GetStruct_ShouldEvaluateWithReasonAndVariant()
{
Expand All @@ -151,6 +211,20 @@ public async Task GetStruct_ShouldEvaluateWithReasonAndVariant()
Assert.Equal("template", details.Variant);
}

[Fact]
public async Task GetStruct_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Act
ResolutionDetails<Value> details = await this.commonProvider.ResolveStructureValueAsync("object-flag", new Value());

// Assert
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 Task GetString_ContextSensitive_ShouldEvaluateWithReasonAndVariant()
{
Expand All @@ -161,6 +235,18 @@ public async Task GetString_ContextSensitive_ShouldEvaluateWithReasonAndVariant(
Assert.Equal("internal", details.Variant);
}

[Fact]
public async Task GetString_ContextSensitive_WithNoEvaluationContext_ShouldEvaluateWithReasonAndVariant()
{
// Act
ResolutionDetails<string> details = await this.commonProvider.ResolveStringValueAsync("context-aware", "nope");

// Assert
Assert.Equal("EXTERNAL", details.Value);
Assert.Equal(Reason.Default, details.Reason);
Assert.Equal("external", details.Variant);
}

[Fact]
public async Task EmptyFlags_ShouldWork()
{
Expand Down Expand Up @@ -198,9 +284,27 @@ public async Task MissingDefaultVariant_ShouldThrow()
}

[Fact]
public async Task MissingEvaluatedVariant_ShouldThrow()
public async Task MissingEvaluatedVariant_ReturnsDefaultVariant()
{
await Assert.ThrowsAsync<GeneralException>(() => this.commonProvider.ResolveBooleanValueAsync("invalid-evaluator-flag", false, EvaluationContext.Empty));
// Act
var result = await this.commonProvider.ResolveBooleanValueAsync("invalid-evaluator-flag", false, EvaluationContext.Empty);

// Assert
Assert.True(result.Value);
Assert.Equal(Reason.Default, result.Reason);
Assert.Equal("on", result.Variant);
}

[Fact]
public async Task ContextEvaluatorThrows_ReturnsDefaultVariant()
{
// Act
var result = await this.commonProvider.ResolveBooleanValueAsync("evaluator-throws-flag", false, EvaluationContext.Empty);

// Assert
Assert.True(result.Value);
Assert.Equal(Reason.Default, result.Reason);
Assert.Equal("on", result.Variant);
}

[Fact]
Expand Down