diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f34c2..9a69486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [5.0.2] +- Fixed Scoped Params returning incorrect results in some corner case scenarios + ## [5.0.1] - Added option to disable automatic type registry for input parameters in reSettings - Added option to make expression case sensitive in reSettings diff --git a/src/RulesEngine/RulesEngine.csproj b/src/RulesEngine/RulesEngine.csproj index 77879a4..ce0b245 100644 --- a/src/RulesEngine/RulesEngine.csproj +++ b/src/RulesEngine/RulesEngine.csproj @@ -2,14 +2,15 @@ net6.0;netstandard2.0 - 5.0.1 + 5.0.2 Copyright (c) Microsoft Corporation. LICENSE https://github.com/microsoft/RulesEngine Abbas Cyclewala Rules Engine is a package for abstracting business logic/rules/policies out of the system. This works in a very simple way by giving you an ability to put your rules in a store outside the core logic of the system thus ensuring that any change in rules doesn't affect the core system. - https://github.com/microsoft/RulesEngine/blob/main/CHANGELOG.md - BRE, Rules Engine, Abstraction + https://github.com/microsoft/RulesEngine/blob/main/CHANGELOG.md + BRE, Rules Engine, Abstraction + README.md @@ -27,11 +28,12 @@ - + + - + diff --git a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs index 93b7aac..ca6a1eb 100644 --- a/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs +++ b/test/RulesEngine.UnitTest/BusinessRuleEngineTest.cs @@ -532,6 +532,280 @@ namespace RulesEngine.UnitTest Assert.All(result, c => Assert.True(c.IsSuccess)); } + [Theory] + [InlineData("rules11.json")] + public async Task RulesEngineWithGlobalParam_RunsSuccessfully(string ruleFileName) + { + + var re = GetRulesEngine(ruleFileName, new ReSettings() { }); + + var input1 = new[] { + new { + Value= 0.13259286, + ChangeDateTime= "2023-07-28T19:57:07.432339Z" + }, + new { + Value= 0.09435427, + ChangeDateTime= "2023-07-28T19:58:04.536459Z" + }, + new { + Value= 0.14896593, + ChangeDateTime= "2023-07-28T19:59:08.682072Z" + }, + new { + Value= 0.12852388, + ChangeDateTime= "2023-07-28T20:00:06.78036Z" + }, + new { + Value= 0.17011189, + ChangeDateTime= "2023-07-28T20:00:54.873615Z" + }, + new { + Value= 0.0532116, + ChangeDateTime= "2023-07-28T20:02:52.04049Z" + }, + new { + Value= 0.04064374, + ChangeDateTime= "2023-07-28T20:03:54.168499Z" + }, + new { + Value= 0.03748944, + ChangeDateTime= "2023-07-28T20:03:54.194786Z" + }, + new { + Value= 0.07752395, + ChangeDateTime= "2023-07-28T20:06:32.451464Z" + }, + new { + Value= 0.07294922, + ChangeDateTime= "2023-07-28T20:07:38.691755Z" + }, + new { + Value= 0.09892442, + ChangeDateTime= "2023-07-28T20:08:37.98802Z" + }, + new { + Value= 0.06370641, + ChangeDateTime= "2023-07-28T20:05:41.358461Z" + }, + new { + Value= 0.07550429, + ChangeDateTime= "2023-07-28T20:09:48.129748Z" + }, + new { + Value= 0.0653021, + ChangeDateTime= "2023-07-28T20:10:48.274482Z" + }, + new { + Value= 0.09304246, + ChangeDateTime= "2023-07-28T20:11:49.436983Z" + }, + new { + Value= 0.0797422, + ChangeDateTime= "2023-07-28T20:12:53.609118Z" + }, + new { + Value= 0.08211832, + ChangeDateTime= "2023-07-28T20:13:52.699728Z" + }, + new { + Value= 0.06955433, + ChangeDateTime= "2023-07-28T20:15:03.843289Z" + }, + new { + Value= 0.07626661, + ChangeDateTime= "2023-07-28T20:15:03.870057Z" + }, + new { + Value= 0.05033984, + ChangeDateTime= "2023-07-28T20:16:17.032262Z" + }, + new { + Value= 0.05202596, + ChangeDateTime= "2023-07-28T20:17:20.172669Z" + }, + new { + Value= 0.06861198, + ChangeDateTime= "2023-07-28T20:18:32.303309Z" + }, + new { + Value= 0.04935532, + ChangeDateTime= "2023-07-28T20:19:33.451426Z" + }, + new { + Value= 0.04073699, + ChangeDateTime= "2023-07-28T20:20:37.737395Z" + }, + new { + Value= 0.02164916, + ChangeDateTime= "2023-07-28T20:21:38.883635Z" + }, + new { + Value= 0.01334031, + ChangeDateTime= "2023-07-28T20:22:40.053193Z" + }, + new { + Value= 0.0336915, + ChangeDateTime= "2023-07-28T20:23:44.240297Z" + }, + new { + Value= 0.04870055, + ChangeDateTime= "2023-07-28T20:26:33.584756Z" + }, + new { + Value= 0.07125243, + ChangeDateTime= "2023-07-28T20:28:11.7889Z" + }, + new { + Value= 0.04904275, + ChangeDateTime= "2023-07-28T20:24:40.346216Z" + }, + new { + Value= 0.03625701, + ChangeDateTime= "2023-07-28T20:27:20.707478Z" + }, + new { + Value= 0.05703328, + ChangeDateTime= "2023-07-28T20:28:57.876436Z" + }, + new { + Value= 0.04364996, + ChangeDateTime= "2023-07-28T20:25:43.496357Z" + }, + new { + Value= 0.07558272, + ChangeDateTime= "2023-07-28T20:30:11.023295Z" + }, + new { + Value= 0.03073958, + ChangeDateTime= "2023-07-28T20:33:00.347672Z" + }, + new { + Value= 0.0341309, + ChangeDateTime= "2023-07-28T20:33:59.790621Z" + }, + new { + Value= 0.05270871, + ChangeDateTime= "2023-07-28T20:31:15.166193Z" + }, + new { + Value= 0.09138862, + ChangeDateTime= "2023-07-28T20:32:08.259273Z" + }, + new { + Value= 0.15922104, + ChangeDateTime= "2023-07-28T20:35:12.963809Z" + }, + new { + Value= 0.11383641, + ChangeDateTime= "2023-07-28T20:36:26.120815Z" + }, + new { + Value= 0.12404025, + ChangeDateTime= "2023-07-28T20:37:37.27212Z" + }, + new { + Value= 0.06010197, + ChangeDateTime= "2023-07-28T20:38:47.409412Z" + }, + new { + Value= 0.08396237, + ChangeDateTime= "2023-07-28T20:39:37.504217Z" + }, + new { + Value= 0.06731881, + ChangeDateTime= "2023-07-28T20:40:27.588895Z" + }, + new { + Value= 0.05617253, + ChangeDateTime= "2023-07-28T20:41:33.760373Z" + }, + new { + Value= 0.0585155, + ChangeDateTime= "2023-07-28T20:42:26.847144Z" + }, + new { + Value= 0.06793098, + ChangeDateTime= "2023-07-28T20:43:36.988904Z" + }, + new { + Value= 0.06879344, + ChangeDateTime= "2023-07-28T20:44:46.133926Z" + }, + new { + Value= 0.06931814, + ChangeDateTime= "2023-07-28T20:45:50.275932Z" + }, + new { + Value= 0.04802603, + ChangeDateTime= "2023-07-28T20:46:36.367289Z" + }, + new { + Value= 0.04036225, + ChangeDateTime= "2023-07-28T20:47:27.484188Z" + }, + new { + Value= 0.04968483, + ChangeDateTime= "2023-07-28T20:48:13.582228Z" + }, + new { + Value= 0.0773483, + ChangeDateTime= "2023-07-28T19:49:16.354277Z" + }, + new { + Value= 0.08710921, + ChangeDateTime= "2023-07-28T19:48:25.253743Z" + }, + new { + Value= 0.07665287, + ChangeDateTime= "2023-07-28T19:50:25.496642Z" + }, + new { + Value= 0.06121748, + ChangeDateTime= "2023-07-28T19:51:20.644955Z" + }, + new { + Value= 0.04179136, + ChangeDateTime= "2023-07-28T19:52:26.793369Z" + }, + new { + Value= 0.13522345, + ChangeDateTime= "2023-07-28T19:54:19.051669Z" + }, + new { + Value= 0.08536856, + ChangeDateTime= "2023-07-28T19:56:04.287806Z" + }, + new { + Value= 0.05041369, + ChangeDateTime= "2023-07-28T19:53:18.901696Z" + }, + new { + Value= 0.1627249, + ChangeDateTime= "2023-07-28T19:55:13.160235Z" + }, + new { + Value= 0.05, + ChangeDateTime= "2023-07-28T19:54:03.2197Z" + }, + new { + Value= 0.05, + ChangeDateTime= "2023-07-28T19:56:00.802023Z" + }, + new { + Value= 0.02792705297470093, + ChangeDateTime= "2023-07-28T20:49:03.6825337Z" + } + }.ToList(); + + var result = await re.ExecuteAllRulesAsync("MyWorkflow", new RuleParameter("input1", input1)); + + Assert.NotNull(result); + Assert.False(result[0].IsSuccess); + Assert.True(result[1].IsSuccess); + } + + [Fact] public async Task ExecuteRule_RuntimeError_ShouldReturnAsErrorMessage() @@ -768,6 +1042,10 @@ namespace RulesEngine.UnitTest Assert.False(re.ContainsWorkflow(NotExistedWorkflowName)); } + + + + [Theory] [InlineData(typeof(RulesEngine), typeof(IRulesEngine))] public void Class_PublicMethods_ArePartOfInterface(Type classType, Type interfaceType) diff --git a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj index 7020080..288910d 100644 --- a/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj +++ b/test/RulesEngine.UnitTest/RulesEngine.UnitTest.csproj @@ -27,6 +27,9 @@ PreserveNewest + + Always + PreserveNewest diff --git a/test/RulesEngine.UnitTest/ScopedParamsTest.cs b/test/RulesEngine.UnitTest/ScopedParamsTest.cs index ebbbb34..9067773 100644 --- a/test/RulesEngine.UnitTest/ScopedParamsTest.cs +++ b/test/RulesEngine.UnitTest/ScopedParamsTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using RulesEngine.Models; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -10,6 +11,14 @@ using Xunit; namespace RulesEngine.UnitTest { + [ExcludeFromCodeCoverage] + public class MyObject + { + public string Name { get; set; } + + public int Count { get; set; } + } + [ExcludeFromCodeCoverage] public class ScopedParamsTest { @@ -142,6 +151,34 @@ namespace RulesEngine.UnitTest } + [Theory] + [InlineData("LocalParam_CorrectAnswer")] + public async Task LocalParam_GivesCorrectAnswer(string workflowName) + { + var workflow = GetWorkflowList(); + + var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { } }; + var bre = new RulesEngine(workflow, reSettingsWithCustomTypes); + + var myObject = new MyObject() { + Name = "My Object", + Count = 2 + }; + + var rp1 = new RuleParameter("myObj", myObject); + + List resultList = await bre.ExecuteAllRulesAsync(workflowName, rp1); + Assert.True(resultList[0].IsSuccess); + + myObject.Count = 3; + + resultList = await bre.ExecuteAllRulesAsync(workflowName, rp1); + Assert.False(resultList[0].IsSuccess); + + } + + + private void CheckResultTreeContainsAllInputs(string workflowName, List result) { var workflow = GetWorkflowList().Single(c => c.WorkflowName == workflowName); @@ -393,6 +430,32 @@ namespace RulesEngine.UnitTest Expression = "globalParam1.ToUpper() == \"HELLO\"" } } + }, + new Workflow { + WorkflowName = "LocalParam_CorrectAnswer", + Rules = new List { + new Rule + { + RuleName = "Test Rule", + LocalParams = new List + { + new LocalParam + { + Name = "threshold", + Expression = "3" + }, + new LocalParam + { + Name = "myList", + Expression = "new int[]{ 1, 2, 3, 4, 5 }" + } + }, + SuccessEvent = "Count is within tolerance.", + ErrorMessage = "Not as expected.", + Expression = "myList.Where(x => x < threshold).Contains(myObj.Count)", + RuleExpressionType = RuleExpressionType.LambdaExpression + } + } } }; } diff --git a/test/RulesEngine.UnitTest/TestData/rules11.json b/test/RulesEngine.UnitTest/TestData/rules11.json new file mode 100644 index 0000000..ceda59e --- /dev/null +++ b/test/RulesEngine.UnitTest/TestData/rules11.json @@ -0,0 +1,51 @@ +{ + "WorkflowName": "MyWorkflow", + "WorkflowsToInject": null, + "RuleExpressionType": 0, + "GlobalParams": [ + { + "Name": "threshold", + "Expression": "double.Parse(\u00220.25\u0022)" + } + ], + "Rules": [ + { + "RuleName": "Activation", + "Properties": null, + "Operator": null, + "ErrorMessage": null, + "Enabled": true, + "RuleExpressionType": 0, + "WorkflowsToInject": null, + "Rules": null, + "LocalParams": [ + { + "Name": "ruleCount", + "Expression": "int.Parse(\u002215\u0022)" + } + ], + "Expression": "input1.Count \u003E= ruleCount \u0026\u0026 input1.Where(x =\u003E x.Value \u003E= threshold).Count() \u003E= ruleCount", + "Actions": null, + "SuccessEvent": null + }, + { + "RuleName": "Deactivation", + "Properties": null, + "Operator": null, + "ErrorMessage": null, + "Enabled": true, + "RuleExpressionType": 0, + "WorkflowsToInject": null, + "Rules": null, + "LocalParams": [ + { + "Name": "ruleCount", + "Expression": "int.Parse(\u002230\u0022)" + } + ], + "Expression": "input1.Count \u003E= ruleCount \u0026\u0026 input1.OrderByDescending(o =\u003E o.ChangeDateTime).Take(ruleCount).All(a =\u003E a.Value \u003C threshold)", + "Actions": null, + "SuccessEvent": null + } + ] +} \ No newline at end of file