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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RulesEngine.UnitTest", "..\..\test\RulesEngine.UnitTest\RulesEngine.UnitTest.csproj", "{50E0C2A5-E2C8-4B12-8C0E-B69F698A82BF}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoApp", "..\..\demo\DemoApp\DemoApp.csproj", "{57BB8C07-799A-4F87-A7CC-D3D3F694DD02}" 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -25,11 +25,20 @@ namespace RulesEngine.ExpressionBuilders
_reSettings = reSettings; _reSettings = reSettings;
} }
internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp) internal override Expression<Func<RuleInput, RuleResultTree>> BuildExpressionForRule(Rule rule, IEnumerable<ParameterExpression> typeParamExpressions, ParameterExpression ruleInputExp)
{
try
{ {
var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) }; var config = new ParsingConfig { CustomTypeProvider = new CustomTypeProvider(_reSettings.CustomTypes) };
var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression); var e = DynamicExpressionParser.ParseLambda(config, typeParamExpressions.ToArray(), null, rule.Expression);
var body = (BinaryExpression)e.Body; var body = (BinaryExpression)e.Body;
return Helpers.ToResultTreeExpression(rule, null, body, typeParamExpressions, ruleInputExp); 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="typeParamExpressions">The type parameter expressions.</param>
/// <param name="ruleInputExp">The rule input exp.</param> /// <param name="ruleInputExp">The rule input exp.</param>
/// <returns>Expression of func</returns> /// <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 }); var lambda = Expression.Lambda<Func<RuleInput, RuleResultTree>>(memberInit, new[] { ruleInputExp });
return lambda; return lambda;
} }
@ -38,7 +38,7 @@ namespace RulesEngine.HelperFunctions
/// <param name="isSuccessExp">The is success exp.</param> /// <param name="isSuccessExp">The is success exp.</param>
/// <param name="childRuleResultsblockexpr">The child rule results block expression.</param> /// <param name="childRuleResultsblockexpr">The child rule results block expression.</param>
/// <returns></returns> /// <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 createdType = typeof(RuleResultTree);
var ctor = Expression.New(createdType); var ctor = Expression.New(createdType);
@ -47,10 +47,12 @@ namespace RulesEngine.HelperFunctions
var isSuccessProp = createdType.GetProperty(nameof(RuleResultTree.IsSuccess)); var isSuccessProp = createdType.GetProperty(nameof(RuleResultTree.IsSuccess));
var childResultProp = createdType.GetProperty(nameof(RuleResultTree.ChildResults)); var childResultProp = createdType.GetProperty(nameof(RuleResultTree.ChildResults));
var inputProp = createdType.GetProperty(nameof(RuleResultTree.Input)); var inputProp = createdType.GetProperty(nameof(RuleResultTree.Input));
var exceptionProp = createdType.GetProperty(nameof(RuleResultTree.ExceptionMessage));
var rulePropBinding = Expression.Bind(ruleProp, Expression.Constant(rule)); var rulePropBinding = Expression.Bind(ruleProp, Expression.Constant(rule));
var isSuccessPropBinding = Expression.Bind(isSuccessProp, isSuccessExp); var isSuccessPropBinding = Expression.Bind(isSuccessProp, isSuccessExp);
var inputBinding = Expression.Bind(inputProp, typeParamExpressions.FirstOrDefault()); var inputBinding = Expression.Bind(inputProp, typeParamExpressions.FirstOrDefault());
var exceptionBinding = Expression.Bind(exceptionProp, Expression.Constant(exceptionMessage));
MemberInitExpression memberInit; MemberInitExpression memberInit;
@ -59,16 +61,16 @@ namespace RulesEngine.HelperFunctions
var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults); var ruleResultTreeArr = Expression.NewArrayInit(typeof(RuleResultTree), childRuleResults);
var childResultPropBinding = Expression.Bind(childResultProp, ruleResultTreeArr); 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) else if (childRuleResultsblockexpr != null)
{ {
var childResultPropBinding = Expression.Bind(childResultProp, childRuleResultsblockexpr); 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 else
{ {
memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding }); memberInit = Expression.MemberInit(ctor, new[] { rulePropBinding, isSuccessPropBinding, inputBinding, exceptionBinding });
} }
return memberInit; return memberInit;

View File

@ -40,6 +40,11 @@ namespace RulesEngine.Models
/// </summary> /// </summary>
public object Input { get; set; } 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> /// <summary>
/// This method will return all the error and warning messages to caller /// This method will return all the error and warning messages to caller
/// </summary> /// </summary>

View File

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

View File

@ -2,19 +2,12 @@
"WorkflowName": "inputWorkflow", "WorkflowName": "inputWorkflow",
"Rules": [ "Rules": [
{ {
"RuleName": "Rule1", "RuleName": "GiveDiscount10",
"Operator": "AndAlso", "SuccessEvent": "10",
"ErrorMessage": "One or more adjust rules failed.", "ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error", "ErrorType": "Error",
"Rules": [ "RuleExpressionType": "LambdaExpression",
{ "Expression": "input1.country == \"india\" AND input1.loyalityFactor <= 2 AND input1.totalPurchasesToDate >= 5000 AND input2.totalOrders > 2 AND input3.noOfVisitsPerMonth > 2"
"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"
}
]
} }
] ]
} }

View File

@ -8,25 +8,28 @@
"ErrorType": "Error", "ErrorType": "Error",
"Rules": [ "Rules": [
{ {
"RuleName": "SubRule1", "RuleName": "GiveDiscount10",
"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\"))", "SuccessEvent": "10",
"ErrorMessage": "SubError message 1", "ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error", "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", "RuleName": "GiveDiscount20",
"Expression": "1 == 1", "SuccessEvent": "20",
"ErrorMessage": "SubError message 2", "ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error", "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", "RuleName": "GiveDiscount25",
"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\"))", "SuccessEvent": "25",
"ErrorMessage": "SubError message 3", "ErrorMessage": "One or more adjust rules failed.",
"ErrorType": "Error", "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"
}
]
}