Skip to content

Commit 7cf2690

Browse files
Spike out test using XUnit MemberData
1 parent 901d319 commit 7cf2690

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

fixtures/evaluation.json

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
{
2+
"toggles": [
3+
{
4+
"name": "My Feature A",
5+
"slug": "feature-a",
6+
"isEnabled": true,
7+
"segments": [
8+
{ "key": "license", "value": "trial" }
9+
]
10+
},
11+
{
12+
"name": "My Feature B",
13+
"slug": "feature-b",
14+
"isEnabled": true,
15+
"segments": [
16+
{ "key": "region", "value": "us" },
17+
{ "key": "region", "value": "au" }
18+
]
19+
},
20+
{
21+
"name": "My Feature C",
22+
"slug": "feature-c",
23+
"isEnabled": true,
24+
"segments": []
25+
},
26+
{
27+
"name": "My Feature D",
28+
"slug": "feature-d",
29+
"isEnabled": false,
30+
"segments": []
31+
}
32+
],
33+
"cases": [
34+
{
35+
"description": "enabled when the evaluation context matches its required segment",
36+
"configuration": {
37+
"slug": "feature-a",
38+
"context": { "license": "trial" },
39+
"defaultValue": false
40+
},
41+
"expected": {
42+
"value": true
43+
}
44+
},
45+
{
46+
"description": "disabled when the evaluation context does not match its required segment",
47+
"configuration": {
48+
"slug": "feature-a",
49+
"context": { "license": "enterprise" },
50+
"defaultValue": false
51+
},
52+
"expected": {
53+
"value": false
54+
}
55+
},
56+
{
57+
"description": "disabled when no evaluation context is provided and toggle has required segments",
58+
"configuration": {
59+
"slug": "feature-a",
60+
"defaultValue": false
61+
},
62+
"expected": {
63+
"value": false
64+
}
65+
},
66+
{
67+
"description": "enabled when context matches one value from a multi-value segment key",
68+
"configuration": {
69+
"slug": "feature-b",
70+
"context": { "region": "us" },
71+
"defaultValue": false
72+
},
73+
"expected": {
74+
"value": true
75+
}
76+
},
77+
{
78+
"description": "enabled regardless of context when toggle has no required segments",
79+
"configuration": {
80+
"slug": "feature-c",
81+
"defaultValue": false
82+
},
83+
"expected": {
84+
"value": true
85+
}
86+
},
87+
{
88+
"description": "disabled when toggle is not enabled regardless of context",
89+
"configuration": {
90+
"slug": "feature-d",
91+
"context": {},
92+
"defaultValue": false
93+
},
94+
"expected": {
95+
"value": false
96+
}
97+
},
98+
{
99+
"description": "returns FLAG_NOT_FOUND and default value when flag key is not a slug",
100+
"configuration": {
101+
"slug": "not a slug",
102+
"defaultValue": true
103+
},
104+
"expected": {
105+
"value": true,
106+
"errorCode": "FLAG_NOT_FOUND"
107+
}
108+
},
109+
{
110+
"description": "returns FLAG_NOT_FOUND and default value when slug does not match any toggle",
111+
"configuration": {
112+
"slug": "unknown-feature",
113+
"defaultValue": true
114+
},
115+
"expected": {
116+
"value": true,
117+
"errorCode": "FLAG_NOT_FOUND"
118+
}
119+
}
120+
]
121+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System.Text.Json;
2+
using FluentAssertions;
3+
using FluentAssertions.Execution;
4+
using Microsoft.Extensions.Logging.Abstractions;
5+
using OpenFeature.Constant;
6+
using OpenFeature.Model;
7+
8+
namespace Octopus.OpenFeature.Provider.Tests;
9+
10+
public class FixtureEvaluationTests
11+
{
12+
public static TheoryData<FixtureTestData> GetTestCases()
13+
{
14+
var data = new TheoryData<FixtureTestData>();
15+
16+
var fixtureDir = Path.Combine(AppContext.BaseDirectory, "Fixtures");
17+
foreach (var file in Directory.GetFiles(fixtureDir, "*.json"))
18+
{
19+
var json = File.ReadAllText(file);
20+
var fixture = JsonSerializer.Deserialize<FixtureFile>(json, JsonSerializerOptions.Web)
21+
?? throw new InvalidOperationException($"Failed to deserialize fixture file: {file}");
22+
23+
foreach (var testCase in fixture.Cases)
24+
{
25+
data.Add(new FixtureTestData(fixture.Toggles, testCase));
26+
}
27+
28+
}
29+
30+
return data;
31+
}
32+
33+
[Theory]
34+
[MemberData(nameof(GetTestCases))]
35+
public void Evaluate(FixtureTestData testData)
36+
{
37+
var toggles = new FeatureToggles(testData.Toggles, []);
38+
var featureContext = new OctopusFeatureContext(toggles, NullLoggerFactory.Instance);
39+
40+
var evaluationContext = BuildContext(testData.Case.Configuration.Context);
41+
42+
var result = featureContext.Evaluate(
43+
testData.Case.Configuration.Slug,
44+
testData.Case.Configuration.DefaultValue,
45+
evaluationContext);
46+
47+
using var scope = new AssertionScope(testData.Case.Description);
48+
result.Value.Should().Be(testData.Case.Expected.Value);
49+
50+
if (testData.Case.Expected.ErrorCode is { } errorCode)
51+
{
52+
result.ErrorType.Should().Be(MapErrorCode(errorCode));
53+
}
54+
55+
else
56+
{
57+
result.ErrorType.Should().Be(ErrorType.None);
58+
}
59+
60+
}
61+
62+
static EvaluationContext? BuildContext(Dictionary<string, string>? context)
63+
{
64+
if (context is null)
65+
{
66+
return null;
67+
}
68+
69+
70+
var builder = EvaluationContext.Builder();
71+
foreach (var (key, value) in context)
72+
{
73+
74+
builder.Set(key, value);
75+
}
76+
77+
78+
return builder.Build();
79+
}
80+
81+
static ErrorType MapErrorCode(string errorCode) => errorCode switch
82+
{
83+
"FLAG_NOT_FOUND" => ErrorType.FlagNotFound,
84+
_ => ErrorType.General
85+
};
86+
}
87+
88+
public class FixtureTestData(FeatureToggleEvaluation[] toggles, FixtureCase @case)
89+
{
90+
public FeatureToggleEvaluation[] Toggles { get; } = toggles;
91+
public FixtureCase Case { get; } = @case;
92+
93+
// Controls the name shown in Test Explorer for each theory row
94+
public override string ToString() => Case.Description;
95+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Octopus.OpenFeature.Provider.Tests;
2+
3+
public record FixtureFile(
4+
FeatureToggleEvaluation[] Toggles,
5+
FixtureCase[] Cases);
6+
7+
public record FixtureCase(
8+
string Description,
9+
FixtureConfiguration Configuration,
10+
FixtureExpected Expected);
11+
12+
public record FixtureConfiguration(
13+
string Slug,
14+
Dictionary<string, string>? Context,
15+
bool DefaultValue);
16+
17+
public record FixtureExpected(
18+
bool Value,
19+
string? ErrorCode = null);

src/Octopus.OpenFeature.Provider.Tests/Octopus.OpenFeature.Provider.Tests.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,10 @@
2727
<ProjectReference Include="..\Octopus.OpenFeature.Provider\Octopus.OpenFeature.Provider.csproj" />
2828
</ItemGroup>
2929

30+
<ItemGroup>
31+
<None Include="..\..\fixtures\*.json" Link="Fixtures\%(Filename)%(Extension)">
32+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
33+
</None>
34+
</ItemGroup>
35+
3036
</Project>

0 commit comments

Comments
 (0)