Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FFM-5307] - .NET SDK - Fix for prereq identifier + update ff-test-cases #47

Merged
merged 1 commit into from
Nov 29, 2022
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
13 changes: 7 additions & 6 deletions client/api/Evaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ public string StringVariation(string key, dto.Target target, string defaultValue
private bool checkPreRequisite(FeatureConfig parentFeatureConfig, dto.Target target)
{
bool result = true;
List<Prerequisite> prerequisites = parentFeatureConfig.Prerequisites.ToList();
if ( prerequisites != null && prerequisites.Count > 0)
if (parentFeatureConfig.Prerequisites != null && parentFeatureConfig.Prerequisites.Count > 0)
{
List<Prerequisite> prerequisites = parentFeatureConfig.Prerequisites.ToList();

foreach (Prerequisite pqs in prerequisites)
{
FeatureConfig preReqFeatureConfig = this.repository.GetFlag(pqs.Feature);
Expand All @@ -105,7 +106,7 @@ private bool checkPreRequisite(FeatureConfig parentFeatureConfig, dto.Target tar
}

List<string> validPreReqVariations = pqs.Variations.ToList();
if (!validPreReqVariations.Contains(preReqEvaluatedVariation.Value))
if (!validPreReqVariations.Contains(preReqEvaluatedVariation.Identifier))
{
return false;
}
Expand Down Expand Up @@ -227,11 +228,11 @@ private bool IsTargetIncludedOrExcludedInSegment(List<string> segmentList, dto.T
return true;
}

// if we have rules, all should pass
// if we have rules, at least one should pass
if (segment.Rules != null)
{
Clause firstFailure = segment.Rules.FirstOrDefault(r => EvaluateClause(r, target) == false);
return firstFailure == null;
Clause firstSuccess = segment.Rules.FirstOrDefault(r => EvaluateClause(r, target));
return firstSuccess != null;
}
}
}
Expand Down
159 changes: 122 additions & 37 deletions tests/ff-server-sdk-test/EvaluatorTest.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using io.harness.cfsdk.client.api;
using io.harness.cfsdk.client.cache;
using io.harness.cfsdk.client.dto;
using io.harness.cfsdk.HarnessOpenAPIService;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;

[SetUpFixture]
public class SetupTracing
{
[OneTimeSetUp]
public void StartTest()
{
Trace.Listeners.Add(new ConsoleTraceListener());
}

[OneTimeTearDown]
public void EndTest()
{
Trace.Flush();
}
}

namespace ff_server_sdk_test
{
public class EvaluatorListener : IEvaluatorCallback
{
public void evaluationProcessed(FeatureConfig featureConfig, io.harness.cfsdk.client.dto.Target target, Variation variation)
public void evaluationProcessed(FeatureConfig featureConfig, io.harness.cfsdk.client.dto.Target target,
Variation variation)
{
var targetName = target != null ? target.Name : "_no_target";
Serilog.Log.Information($"processEvaluation {featureConfig.Feature}, {targetName}, {variation.Value} ");
}
}



[TestFixture]
public class EvaluatorTest
{
private static List<TestModel> testData = new List<TestModel>();
private static IRepository repository;
private static ICache cache;

Expand All @@ -37,73 +55,140 @@ static EvaluatorTest()
cache = new FeatureSegmentCache();
repository = new StorageRepository(cache, null, null);
evaluator = new Evaluator(repository, listener);
}

private static void LoadSegments(List<Segment> segments)
{
if (segments != null)
{
segments.ForEach(segment => { repository.SetSegment(segment.Identifier, segment); });
}
}

Assert.DoesNotThrow(() =>
private static void LoadFlags(List<FeatureConfig> flags)
{
if (flags != null)
{
foreach (string fileName in Directory.GetFiles("./ff-test-cases/tests", "*.json"))
flags.ForEach(flag => { repository.SetFlag(flag.Feature, flag); });
}
}


private static FeatureConfig FindFeatureConfig(string flagName, List<FeatureConfig> flags)
{
foreach (FeatureConfig nextFlag in flags)
{
if (nextFlag.Feature.Equals(flagName))
{
var testModel = JsonConvert.DeserializeObject<TestModel>(File.ReadAllText(fileName));
Assert.NotNull(testModel);
return nextFlag;
}
}
Assert.Fail("Could not find feature flag " + flagName);
return null;
}

string name = Path.GetFileName(fileName);
string feature = testModel.flag.Feature + name;
testModel.flag.Feature = feature;
testModel.testFile = name;

testData.Add(testModel);
private static List<string> GetTree(string path, string searchPattern)
{
List<string> found = new List<string>();
foreach (string file in Directory.GetFiles(path, searchPattern))
{
found.Add(file);
}

repository.SetFlag(testModel.flag.Feature, testModel.flag);
if (testModel.segments != null)
{
testModel.segments.ForEach(s =>
{
repository.SetSegment(s.Identifier, s);
});
}
}
});
foreach (string dir in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
{
List<string> subFound = GetTree(dir, searchPattern);
found.AddRange(subFound);
}

return found;
}

private static IEnumerable<TestCaseData> GenerateTestCases()
{
string baseTestPath = Path.GetFullPath("./ff-test-cases/tests/");

foreach (var test in testData)
foreach (string fileName in GetTree(baseTestPath, "*"))
{
foreach (var item in test.expected)
Console.WriteLine("Processing " + fileName);

var testModel = JsonConvert.DeserializeObject<TestModel>(File.ReadAllText(fileName),
new JsonSerializerSettings
{
ContractResolver = new LenientContractResolver()
});

Assert.NotNull(testModel);

foreach (Dictionary<string, object> nextTest in testModel.tests)
{
yield return new TestCaseData(test.flag.Feature, item.Key, item.Value, test);
object expected = nextTest.GetValueOrDefault("expected");
string target = (string)nextTest.GetValueOrDefault("target", null); // May be null
string flag = (string)nextTest.GetValueOrDefault("flag");
FeatureConfig feature = FindFeatureConfig(flag, testModel.flags);

LoadSegments(testModel.segments);
LoadFlags(testModel.flags);

string nUnitTestName = fileName.Replace(baseTestPath, "ff-test-cases ").Replace(".json", "");

nUnitTestName += "__with_flag_" + flag;
if (target != null)
{
nUnitTestName += "__with_target_" + target;
}

yield return new TestCaseData(nUnitTestName, target, expected, flag, feature.Kind, testModel);
}
}
}

[Test, Category("Evaluation Testing"), TestCaseSource("GenerateTestCases")]
public void ExecuteTestCases(string f, string identifier, bool result, TestModel test)
public void ExecuteTestCases(string testName, string targetIdentifier, object expected, string featureFlag, FeatureConfigKind kind, TestModel testModel)
{
io.harness.cfsdk.client.dto.Target target = null;
if (!identifier.Equals(noTarget))
if (!noTarget.Equals(targetIdentifier))
{
if (test.targets != null)
if (testModel.targets != null)
{
target = test.targets.Find(t => { return t.Identifier == identifier; });
target = testModel.targets.Find(t => { return t.Identifier == targetIdentifier; });
}
}
string feature = test.flag.Feature;
switch (test.flag.Kind)

object got = null;

switch (kind)
{
case FeatureConfigKind.Boolean:
bool res = evaluator.BoolVariation(feature, target, false);
Assert.AreEqual(res, result, $"Expected result for {feature} was {result}");
got = evaluator.BoolVariation(featureFlag, target, false);
break;
case FeatureConfigKind.Int:
double resInt = evaluator.NumberVariation(feature, target, 0);
got = evaluator.NumberVariation(featureFlag, target, 0);
break;
case FeatureConfigKind.String:
string resStr = evaluator.StringVariation(feature, target, "");
got = evaluator.StringVariation(featureFlag, target, "");
break;
case FeatureConfigKind.Json:
JObject resObj = evaluator.JsonVariation(feature, target, JObject.Parse("{val: 'default value'}"));
got = evaluator.JsonVariation(featureFlag, target, JObject.Parse("{val: 'default value'}"));
break;
}

Debug.Print(" TEST : {0}", testName);
Debug.Print(" FLAG : {0}", featureFlag);
Debug.Print(" TARGET : {0} ", targetIdentifier ?? "(none)");
Debug.Print("EXPECTED : {0} ({1})", expected, expected.GetType().Name);
Debug.Print(" GOT : {0} ({1})", got.ToString().Replace("\n", ""), got.GetType().Name);

if (kind == FeatureConfigKind.Json)
{
var expectedJson = JObject.Parse((string)expected);
Assert.AreEqual(expectedJson, got, $"Expected result for {featureFlag} was {expected}");
}
else
{
Assert.AreEqual(expected, got, $"Expected result for {featureFlag} was {expected}");
}
}

}
Expand Down
29 changes: 27 additions & 2 deletions tests/ff-server-sdk-test/TestModel.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using io.harness.cfsdk.HarnessOpenAPIService;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace ff_server_sdk_test
{
public class LenientContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.Required = Required.Default;
return property;
}

protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.ItemRequired = Required.Default;
return contract;
}

protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
{
var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);
property.Required = Required.Default;
return property;
}
}

public class TestModel
{
public string testFile;
public FeatureConfig flag;
public List<FeatureConfig> flags;
public List<io.harness.cfsdk.client.dto.Target> targets;
public List<Segment> segments;
public Dictionary<string, bool> expected;
public List<Dictionary<string, object>> tests;
public TestModel()
{
}
Expand Down
25 changes: 2 additions & 23 deletions tests/ff-server-sdk-test/ff-server-sdk-test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,9 @@
<None Remove="Microsoft.NET.Test.Sdk" />
</ItemGroup>
<ItemGroup>
<None Update="ff-test-cases\tests\on_off_no_rules.json">
<Content Include="ff-test-cases\tests\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\off_flag.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\segment_varmap_target.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\segment_includes_target.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\rules_priority.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\bool_on_simple_rule.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\prereq.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ff-test-cases\tests\off_off_no_rules.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ff-netF48-server-sdk.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion tests/ff-server-sdk-test/ff-test-cases
Submodule ff-test-cases updated 93 files
+22 −0 .github/workflows/ci.yml
+1 −0 .gitignore
+100 −0 Makefile
+134 −0 README.md
+617 −0 client-v1.yaml
+3 −0 go.mod
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_False_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_False_Off_True_Should_Return_True.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_True_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOffRules/DefaultOffRules_Type_Bool_State_Disabled_On_True_Off_True_Should_Return_True.json
+621 −0 ...OffRules/DefaultOffRules_Type_JSON_State_Disabled_On_complexJSON_Off_emptyJSON_Should_Return_validJSON.json
+621 −0 ...ltOffRules/DefaultOffRules_Type_JSON_State_Disabled_On_validJSON_Off_emptyJSON_Should_Return_emptyJSON.json
+621 −0 tests/DefaultOffRules/DefaultOffRules_Type_Number_State_Disabled_On_011_Off_2_Should_Return_2.json
+621 −0 tests/DefaultOffRules/DefaultOffRules_Type_Number_State_Disabled_On_324523_Off_011_Should_Return_011.json
+621 −0 ...ultOffRules/DefaultOffRules_Type_String_State_Disabled_On_sonnet19_Off_sonnet53_Should_Return_sonnet53.json
+621 −0 ...ultOffRules/DefaultOffRules_Type_String_State_Disabled_On_sonnet53_Off_sonnet19_Should_Return_sonnet19.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_False_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_False_Off_True_Should_Return_True.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_True_Off_False_Should_Return_False.json
+615 −0 tests/DefaultOnRules/DefaultOnRules_Type_Bool_State_Enabled_On_True_Off_True_Should_Return_True.json
+621 −0 ...tOnRules/DefaultOnRules_Type_JSON_State_Enabled_On_complexJSON_Off_emptyJSON_Should_Return_complexJSON.json
+621 −0 ...faultOnRules/DefaultOnRules_Type_JSON_State_Enabled_On_validJSON_Off_emptyJSON_Should_Return_validJSON.json
+621 −0 tests/DefaultOnRules/DefaultOnRules_Type_Number_State_Enabled_On_011_Off_2_Should_Return_011.json
+621 −0 tests/DefaultOnRules/DefaultOnRules_Type_Number_State_Enabled_On_324523_Off_011_Should_Return_324523.json
+621 −0 ...efaultOnRules/DefaultOnRules_Type_String_State_Enabled_On_sonnet19_Off_sonnet53_Should_Return_sonnet19.json
+621 −0 ...efaultOnRules/DefaultOnRules_Type_String_State_Enabled_On_sonnet53_Off_sonnet19_Should_Return_sonnet53.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_EqualsThree_Should_Return_false_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_EqualsThree_Should_Return_true_for_Target_one.json
+634 −0 ...pRules/GroupRules_Type_Bool_State_Enabled_GroupWithMultipleOrRules_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_InOneTwoThree_Should_Return_true_for_Target_three.json
+634 −0 ...oupRules/GroupRules_Type_Bool_State_Enabled_IncludeAndExcludeGroup_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_IncludedOnly_Should_Return_true_for_Target_three.json
+634 −0 ...pRules/GroupRules_Type_Bool_State_Enabled_StartsWithTExcludesThree_Should_Return_true_for_Target_three.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_StartsWithT_Should_Return_false_for_Target_one.json
+634 −0 tests/GroupRules/GroupRules_Type_Bool_State_Enabled_StartsWithT_Should_Return_true_for_Target_three.json
+622 −0 ...rerequisites/Type_BoolBool_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_False.json
+622 −0 ...Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_True.json
+622 −0 .../Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_False.json
+622 −0 ...s/Prerequisites/Type_BoolBool_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_True.json
+622 −0 ...Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_False.json
+622 −0 .../Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_False_Prerequisites_AlwaysOff_Should_Return_True.json
+622 −0 ...s/Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_False.json
+622 −0 tests/Prerequisites/Type_BoolInt_State_Enabled_On_True_Off_True_Prerequisites_AlwaysOn_Should_Return_True.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntBool_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntInt_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntJson_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOff_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_011.json
+628 −0 tests/Prerequisites/Type_IntString_State_Enabled_On_2_Off_011_Prerequisites_AlwaysOn_Should_Return_2.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOff_Should_Return_Com.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOff_Should_Return_Val.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOn_Should_Return_Com.json
+628 −0 tests/Prerequisites/Type_JsonJson_State_Enabled_On_com_Off_val_Prerequisites_AlwaysOn_Should_Return_Val.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOff_Should_Return_off.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOff_Should_Return_on.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOn_Should_Return_Off.json
+628 −0 tests/Prerequisites/Type_StrStr_State_Enabled_On_s19_Off_s53_Prerequisites_AlwaysOn_Should_Return_On.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Disabled_Should_Return_Default_Off_Value.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_false_for_Target_four.json
+659 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_false_for_Target_six.json
+638 −0 tests/TargetRules/TargetRules_Type_Bool_State_Enabled_Should_Return_true_for_Target_three.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_complexJSON_for_Target_five.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_emptyJSON_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_JSON_State_Enabled_Should_Return_validJSON_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_2_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_324523_for_Target_five.json
+665 −0 tests/TargetRules/TargetRules_Type_Number_State_Enabled_Should_Return_sonnet53_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_sonnet53_for_Target_anonymous.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_sonnet53_for_Target_four.json
+665 −0 tests/TargetRules/TargetRules_Type_String_State_Enabled_Should_Return_thehobbit_for_Target_five.json
+49 −46 tests/bool_on_simple_rule.json
+33 −30 tests/off_flag.json
+33 −30 tests/off_off_no_rules.json
+33 −30 tests/on_off_no_rules.json
+96 −48 tests/prereq.json
+63 −60 tests/rules_priority.json
+48 −45 tests/segment_includes_target.json
+0 −78 tests/segment_varmap_target.json
+6 −6 tests/test_empty_or_missing_target_attributes.json
+215 −0 types.gen.go
+16 −0 types.go
+121 −0 validator.go
+10 −0 validator_test.go