// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using RulesEngine.Models; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Xunit; namespace RulesEngine.UnitTest { [ExcludeFromCodeCoverage] public class MyObject { public string Name { get; set; } public int Count { get; set; } } [ExcludeFromCodeCoverage] public class ScopedParamsTest { [Theory] [InlineData("NoLocalAndGlobalParams")] [InlineData("LocalParamsOnly")] [InlineData("GlobalParamsOnly")] [InlineData("GlobalAndLocalParams")] [InlineData("GlobalParamReferencedInLocalParams")] [InlineData("GlobalParamReferencedInNextGlobalParams")] [InlineData("LocalParamReferencedInNextLocalParams")] [InlineData("GlobalParamAndLocalParamsInNestedRules")] public async Task BasicWorkflows_ReturnsTrue(string workflowName) { var workflow = GetWorkflowList(); var engine = new RulesEngine(); engine.AddWorkflow(workflow); var input1 = new { trueValue = true, falseValue = false }; var result = await engine.ExecuteAllRulesAsync(workflowName, input1); Assert.True(result.All(c => c.IsSuccess)); CheckResultTreeContainsAllInputs(workflowName, result); } [Theory] [InlineData("GlobalAndLocalParams")] public async Task WorkflowUpdate_GlobalParam_ShouldReflect(string workflowName) { var workflow = GetWorkflowList(); var engine = new RulesEngine(); engine.AddWorkflow(workflow); var input1 = new { trueValue = true, falseValue = false }; var result = await engine.ExecuteAllRulesAsync(workflowName, input1); Assert.True(result.All(c => c.IsSuccess)); var workflowToUpdate = workflow.Single(c => c.WorkflowName == workflowName); engine.RemoveWorkflow(workflowName); workflowToUpdate.GlobalParams.First().Expression = "true == false"; engine.AddWorkflow(workflowToUpdate); var result2 = await engine.ExecuteAllRulesAsync(workflowName, input1); Assert.True(result2.All(c => c.IsSuccess == false)); } [Theory] [InlineData("GlobalParamsOnly", new[] { false })] [InlineData("LocalParamsOnly", new[] { false, true })] [InlineData("GlobalAndLocalParams", new[] { false })] public async Task DisabledScopedParam_ShouldReflect(string workflowName, bool[] outputs) { var workflow = GetWorkflowList(); var engine = new RulesEngine(new string[] { }, new ReSettings { EnableScopedParams = false }); engine.AddWorkflow(workflow); var input1 = new { trueValue = true, falseValue = false }; var result = await engine.ExecuteAllRulesAsync(workflowName, input1); for (var i = 0; i < result.Count; i++) { Assert.Equal(result[i].IsSuccess, outputs[i]); if (result[i].IsSuccess == false) { Assert.StartsWith("Exception while parsing expression", result[i].ExceptionMessage); } } } [Theory] [InlineData("GlobalParamsOnly")] [InlineData("LocalParamsOnly2")] [InlineData("GlobalParamsOnlyWithComplexInput")] public async Task ErrorInScopedParam_ShouldAppearAsErrorMessage(string workflowName) { var workflow = GetWorkflowList(); var engine = new RulesEngine(new string[] { }, null); engine.AddWorkflow(workflow); var input = new { }; var result = await engine.ExecuteAllRulesAsync(workflowName, input); Assert.All(result, c => { Assert.False(c.IsSuccess); Assert.StartsWith("Error while compiling rule", c.ExceptionMessage); }); } [Theory] [InlineData("GlobalParamsOnlyWithComplexInput")] [InlineData("LocalParamsOnlyWithComplexInput")] public async Task RuntimeErrorInScopedParam_ShouldAppearAsErrorMessage(string workflowName) { var workflow = GetWorkflowList(); var engine = new RulesEngine(new string[] { }, null); engine.AddWorkflow(workflow); var input = new RuleTestClass(); var result = await engine.ExecuteAllRulesAsync(workflowName, input); Assert.All(result, c => { Assert.False(c.IsSuccess); Assert.StartsWith("Error while executing scoped params for rule", c.ExceptionMessage); }); } [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); var expectedInputs = new List() { "input1" }; expectedInputs.AddRange(workflow.GlobalParams?.Select(c => c.Name) ?? new List()); foreach (var resultTree in result) { CheckInputs(expectedInputs, resultTree); } } private static void CheckInputs(IEnumerable expectedInputs, RuleResultTree resultTree) { Assert.All(expectedInputs, input => Assert.True(resultTree.Inputs.ContainsKey(input))); var localParamNames = resultTree.Rule.LocalParams?.Select(c => c.Name) ?? new List(); Assert.All(localParamNames, input => Assert.True(resultTree.Inputs.ContainsKey(input))); if (resultTree.ChildResults?.Any() == true) { foreach (var childResultTree in resultTree.ChildResults) { CheckInputs(expectedInputs.Concat(localParamNames), childResultTree); } } } private Workflow[] GetWorkflowList() { return new Workflow[] { new Workflow { WorkflowName = "NoLocalAndGlobalParams", Rules = new List { new Rule { RuleName = "TruthTest", Expression = "input1.trueValue" } } }, new Workflow { WorkflowName = "LocalParamsOnly", Rules = new List { new Rule { RuleName = "WithLocalParam", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "input1.trueValue" } }, Expression = "localParam1 == true" }, new Rule { RuleName = "WithoutLocalParam", Expression = "input1.falseValue == false" }, } }, new Workflow { WorkflowName = "LocalParamsOnly2", Rules = new List { new Rule { RuleName = "WithLocalParam", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "input1.trueValue" } }, Expression = "localParam1 == true" } } }, new Workflow { WorkflowName = "GlobalParamsOnly", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = "input1.falseValue == false" } }, Rules = new List { new Rule { RuleName = "TrueTest", Expression = "globalParam1 == true" } } }, new Workflow { WorkflowName = "GlobalAndLocalParams", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = "input1.falseValue == false" } }, Rules = new List { new Rule { RuleName = "WithLocalParam", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "input1.trueValue" } }, Expression = "globalParam1 == true && localParam1 == true" }, } }, new Workflow { WorkflowName = "GlobalParamReferencedInLocalParams", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = "\"testString\"" } }, Rules = new List { new Rule { RuleName = "WithLocalParam", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "globalParam1.ToUpper()" } }, Expression = "globalParam1 == \"testString\" && localParam1 == \"TESTSTRING\"" }, } }, new Workflow { WorkflowName = "GlobalParamReferencedInNextGlobalParams", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = "\"testString\"" }, new ScopedParam { Name = "globalParam2", Expression = "globalParam1.ToUpper()" } }, Rules = new List { new Rule { RuleName = "WithLocalParam", Expression = "globalParam1 == \"testString\" && globalParam2 == \"TESTSTRING\"" }, } }, new Workflow { WorkflowName = "LocalParamReferencedInNextLocalParams", Rules = new List { new Rule { LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "\"testString\"" }, new ScopedParam { Name = "localParam2", Expression = "localParam1.ToUpper()" } }, RuleName = "WithLocalParam", Expression = "localParam1 == \"testString\" && localParam2 == \"TESTSTRING\"" }, } }, new Workflow { WorkflowName = "GlobalParamAndLocalParamsInNestedRules", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = @"""hello""" } }, Rules = new List { new Rule { RuleName = "NestedRuleTest", Operator = "And", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = @"""world""" } }, Rules = new List{ new Rule{ RuleName = "NestedRule1", Expression = "globalParam1 == \"hello\" && localParam1 == \"world\"" }, new Rule { RuleName = "NestedRule2", LocalParams = new List { new ScopedParam { Name = "nestedLocalParam1", Expression = "globalParam1 + \" \" + localParam1" } }, Expression = "nestedLocalParam1 == \"hello world\"" } } } } }, new Workflow { WorkflowName = "LocalParamsOnlyWithComplexInput", Rules = new List { new Rule { RuleName = "WithLocalParam", LocalParams = new List { new ScopedParam { Name = "localParam1", Expression = "input1.Country.ToLower()" } }, Expression = "localParam1 == \"hello\"" } } }, new Workflow { WorkflowName = "GlobalParamsOnlyWithComplexInput", GlobalParams = new List { new ScopedParam { Name = "globalParam1", Expression = "input1.Country.ToLower()" } }, Rules = new List { new Rule { RuleName = "TrueTest", Expression = "globalParam1 == \"hello\"" }, new Rule { RuleName = "TrueTest2", 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 } } } }; } } }