including exceptions scenario (#2)

* including exceptions scenario

* Unit Test Cases
pull/7/head
Dishant Munjal 2019-09-24 08:53:33 +05:30 committed by GitHub
parent 07ed77042d
commit 63ecbe6a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 124 additions and 63 deletions

View File

@ -8,6 +8,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "..\..\test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "..\..\demo\DemoApp\DemoApp.csproj", "{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}"
ProjectSection(ProjectDependencies) = postProject
{CD4DFE6A-083B-478E-8377-77F474833E30} = {CD4DFE6A-083B-478E-8377-77F474833E30}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -26,10 +26,19 @@ namespace RulesEngine.ExpressionBuilders
}
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
{
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
var body = (BinaryExpression)e.Body;
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
try
{
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
var body = (BinaryExpression)e.Body;
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp);
}
catch (Exception ex)
{
var binaryExpression = Expression.And(Expression.Constant(true), Expression.Constant(false));
var exceptionMessage = ex.Message;
return Helpers.ToResultTreeExpression(rule, null, binaryExpression, typeParamExpressions, ruleInputExp, exceptionMessage);
}
}
}
}

View File

@ -23,9 +23,9 @@ namespace RulesEngine.HelperFunctions
/// <param name="typeParamExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param>
/// <returns>Expression of func</returns>
internal static Expression<Func<RuleInput, RuleResultTree>> ToResultTreeExpression(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
internal static Expression<Func<RuleInput, RuleResultTree>> ToResultTreeExpression(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp, string exceptionMessage = "")
{
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null);
var memberInit = ToResultTree(rule, childRuleResults, isSuccessExp, typeParamExpressions, null, exceptionMessage);
var lambda = Expression.Lambda<Func<RuleInput, RuleResultTree>>(memberInit, new[] { ruleInputExp });
return lambda;
}
@ -38,7 +38,7 @@ namespace RulesEngine.HelperFunctions
/// <param name="isSuccessExp">The is success exp.</param>
/// <param name="childRuleResultsblockexpr">The child rule results block expression.</param>
/// <returns></returns>
internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, BlockExpression childRuleResultsblockexpr)
internal static MemberInitExpression ToResultTree(Rule rule, IEnumerable<MemberInitExpression> childRuleResults, BinaryExpression isSuccessExp, IEnumerable<ParameterExpression> typeParamExpressions, BlockExpression childRuleResultsblockexpr, string exceptionMessage = "")
{
var createdType = typeof(RuleResultTree);
var ctor = Expression.New(createdType);
@ -47,10 +47,12 @@ namespace RulesEngine.HelperFunctions
var isSuccessProp = createdType.GetProperty(nameof(RuleResultTree.IsSuccess));
var childResultProp = createdType.GetProperty(nameof(RuleResultTree.ChildResults));
var inputProp = createdType.GetProperty(nameof(RuleResultTree.Input));
var exceptionProp = createdType.GetProperty(nameof(RuleResultTree.ExceptionMessage));
var rulePropBinding = Expression.Bind(ruleProp, Expression.Constant(rule));
var isSuccessPropBinding = Expression.Bind(isSuccessProp, isSuccessExp);
var inputBinding = Expression.Bind(inputProp, typeParamExpressions.FirstOrDefault());
var exceptionBinding = Expression.Bind(exceptionProp, Expression.Constant(exceptionMessage));
MemberInitExpression memberInit;
@ -59,16 +61,16 @@ namespace RulesEngine.HelperFunctions
var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults);
var childResultPropBinding = Expression.Bind(childResultProp, ruleResultTreeArr);
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding, exceptionBinding });
}
else if (childRuleResultsblockexpr != null)
{
var childResultPropBinding = Expression.Bind(childResultProp, childRuleResultsblockexpr);
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding });
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, childResultPropBinding, inputBinding, exceptionBinding });
}
else
{
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding });
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding, exceptionBinding });
}
return memberInit;

View File

@ -40,6 +40,11 @@ namespace RulesEngine.Models
/// </summary>
public object Input { get; set; }
/// <summary>
/// Gets the exception message in case an error is thrown during rules calculation.
/// </summary>
public string ExceptionMessage { get; set; }
/// <summary>
/// This method will return all the error and warning messages to caller
/// </summary>

View File

@ -13,6 +13,7 @@ using System.Dynamic;
using System.IO;
using System.Linq;
using Xunit;
using Newtonsoft.Json.Converters;
namespace RulesEngine.UnitTest
{
@ -32,12 +33,12 @@ namespace RulesEngine.UnitTest
public void RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
dynamic input2 = GetInput();
input2.value1 = "val1";
var result = re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
var result = re.ExecuteRule("inputWorkflowReference", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
@ -47,12 +48,12 @@ namespace RulesEngine.UnitTest
public void ExecuteRule_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
@ -62,27 +63,43 @@ namespace RulesEngine.UnitTest
public void ExecuteRule_SingleObject_ReturnsListOfRuleResultTree(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
var result = re.ExecuteRule("inputWorkflow",input);
var result = re.ExecuteRule("inputWorkflow",input1);
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
[Theory]
[InlineData("rules3.json")]
public void ExecuteRule_ExceptionScenario_RulesInvalid(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
Assert.False(string.IsNullOrEmpty(result[0].ExceptionMessage) || string.IsNullOrWhiteSpace(result[0].ExceptionMessage));
}
[Theory]
[InlineData("rules2.json")]
public void ExecuteRule_ReturnsListOfRuleResultTree_ResultMessage(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.value1 = "val1";
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
List<RuleResultTree> result = re.ExecuteRule("inputWorkflow", input);
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
Assert.NotNull(result.First().GetMessages());
@ -116,7 +133,7 @@ namespace RulesEngine.UnitTest
public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName)
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input = GetInput1();
Assert.Throws<ArgumentException>(() => { re.ExecuteRule("inputWorkflow1", new List<dynamic>() { input }.AsEnumerable(), new object[] { }); });
}
@ -128,11 +145,11 @@ namespace RulesEngine.UnitTest
{
var re = GetRulesEngine(ruleFileName);
dynamic input = GetInput();
dynamic input2 = GetInput();
input2.valueX = "hello";
dynamic input1 = GetInput1();
dynamic input2 = GetInput2();
dynamic input3 = GetInput3();
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input, input2 }.AsEnumerable(), new object[] { });
var result = re.ExecuteRule("inputWorkflow", new List<dynamic>() { input1, input2, input3 }.AsEnumerable(), new object[] { });
Assert.NotNull(result);
Assert.IsType<List<RuleResultTree>>(result);
}
@ -159,14 +176,27 @@ namespace RulesEngine.UnitTest
return new RulesEngine(new string[] { data, injectWorkflowStr}, mockLogger.Object);
}
private dynamic GetInput()
private dynamic GetInput1()
{
dynamic input = new ExpandoObject();
input.value1 = "value1";
input.value2 = "value2";
input.value3 = 1;
input.value4 = new { subValue = "subValue", nullValue = default(string) };
return input;
var converter = new ExpandoObjectConverter();
var basicInfo = "{\"name\": \"Dishant\",\"email\": \"dishantmunjal@live.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 10000}";
return JsonConvert.DeserializeObject<ExpandoObject>(basicInfo, converter);
}
private dynamic GetInput2()
{
var converter = new ExpandoObjectConverter();
var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}";
return JsonConvert.DeserializeObject<ExpandoObject>(orderInfo, converter);
}
private dynamic GetInput3()
{
var converter = new ExpandoObjectConverter();
var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}";
return JsonConvert.DeserializeObject<ExpandoObject>(telemetryInfo, converter);
}
}
}

View File

@ -23,6 +23,9 @@
<None Update="TestData\rules1.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\rules3.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestData\rules2.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@ -2,19 +2,12 @@
"WorkflowName": "inputWorkflow",
"Rules": [
{
"RuleName": "Rule1",
"Operator": "AndAlso",
"RuleName": "GiveDiscount10",
"SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"Rules": [
{
"RuleName": "SubRule1",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 1",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
}
]
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
}
]
}
}

View File

@ -8,25 +8,28 @@
"ErrorType": "Error",
"Rules": [
{
"RuleName": "SubRule1",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 1",
"RuleName": "GiveDiscount10",
"SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
},
{
"RuleName": "SubRule2",
"Expression": "1 == 1",
"ErrorMessage": "SubError message 2",
"RuleName": "GiveDiscount20",
"SuccessEvent": "20",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country == \"india\" AND input1.loyalityFactor == 3 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
},
{
"RuleName": "SubRule3",
"Expression": "input1.Request_RequestType == \"vod\" AND input1.Labor_BillingCode == \"billable\" AND ((input1.Request_RegistrationStatus == \"cancelled with t&e\" AND input1.Request_Status == \"cancelled\") OR (input1.Request_Status != \"cancelled\"))",
"ErrorMessage": "SubError message 3",
"RuleName": "GiveDiscount25",
"SuccessEvent": "25",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression"
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.country != \"india\" AND input1.loyalityFactor >= 2 AND input1.totalPurchasesToDate >= 10000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 5"
}
]
}

View File

@ -0,0 +1,13 @@
{
"WorkflowName": "inputWorkflow",
"Rules": [
{
"RuleName": "GiveDiscount10",
"SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error",
"RuleExpressionType": "LambdaExpression",
"Expression": "input1.couy == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
}
]
}