// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.Extensions.Logging; using RulesEngine.Exceptions; using RulesEngine.Models; using Moq; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Dynamic; using System.IO; using System.Linq; using Xunit; using Newtonsoft.Json.Converters; using RulesEngine.HelperFunctions; using System.Diagnostics.CodeAnalysis; namespace RulesEngine.UnitTest { [Trait("Category", "Unit")] [ExcludeFromCodeCoverage] public class RulesEngineTest { [Theory] [InlineData("rules1.json")] public void RulesEngine_New_ReturnsNotNull(string ruleFileName) { var re = GetRulesEngine(ruleFileName); Assert.NotNull(re); } [Theory] [InlineData("rules2.json")] public void RulesEngine_InjectedRules_ReturnsListOfRuleResultTree(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); var result = re.ExecuteRule("inputWorkflowReference",input1, input2, input3); Assert.NotNull(result); Assert.IsType>(result); } [Theory] [InlineData("rules2.json")] public void ExecuteRule_ReturnsListOfRuleResultTree(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); List result = re.ExecuteRule("inputWorkflow", input1, input2, input3); Assert.NotNull(result); Assert.IsType>(result); Assert.Contains(result,c => c.IsSuccess); } [Theory] [InlineData("rules2.json")] public void ExecuteRule_SingleObject_ReturnsListOfRuleResultTree(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); List result = re.ExecuteRule("inputWorkflow", input1); Assert.NotNull(result); Assert.IsType>(result); Assert.DoesNotContain(result,c => c.IsSuccess); } [Theory] [InlineData("rules3.json")] public void ExecuteRule_ExceptionScenario_RulesInvalid(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); List result = re.ExecuteRule("inputWorkflow", input1, input2, input3); Assert.NotNull(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 input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); List result = re.ExecuteRule("inputWorkflow",input1, input2, input3); Assert.NotNull(result); Assert.NotNull(result.First().GetMessages()); Assert.NotNull(result.First().GetMessages().WarningMessages); } [Fact] public void RulesEngine_New_IncorrectJSON_ThrowsException() { Assert.Throws(() => { var workflow = new WorkflowRules(); var re = CreateRulesEngine(workflow); }); Assert.Throws(() => { var workflow = new WorkflowRules() { WorkflowName = "test" }; var re = CreateRulesEngine(workflow); }); } [Theory] [InlineData("rules1.json")] public void ExecuteRule_InvalidWorkFlow_ThrowsException(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input = GetInput1(); Assert.Throws(() => { re.ExecuteRule("inputWorkflow1", input); }); } [Theory] [InlineData("rules1.json")] public void RemoveWorkflow_RemovesWorkflow(string ruleFileName) { var re = GetRulesEngine(ruleFileName); re.RemoveWorkflow("inputWorkflow"); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); Assert.Throws(() => re.ExecuteRule("inputWorkflow",input1, input2, input3 )); } [Theory] [InlineData("rules1.json")] public void ClearWorkflow_RemovesAllWorkflow(string ruleFileName) { var re = GetRulesEngine(ruleFileName); re.ClearWorkflows(); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); Assert.Throws(() => re.ExecuteRule("inputWorkflow", input1, input2, input3)); Assert.Throws(() => re.ExecuteRule("inputWorkflowReference", input1, input2, input3)); } [Theory] [InlineData("rules1.json")] [InlineData("rules2.json")] public void ExecuteRule_InputWithVariableProps_ReturnsResult(string ruleFileName) { var re = GetRulesEngine(ruleFileName); dynamic input1 = GetInput1(); dynamic input2 = GetInput2(); dynamic input3 = GetInput3(); List result = re.ExecuteRule("inputWorkflow", input1, input2, input3); Assert.NotNull(result); Assert.IsType>(result); Assert.Contains(result,c => c.IsSuccess); input3.hello = "world"; result = re.ExecuteRule("inputWorkflow", input1, input2, input3); Assert.NotNull(result); Assert.IsType>(result); Assert.Contains(result,c => c.IsSuccess); } [Theory] [InlineData("rules4.json")] public void RulesEngine_Execute_Rule_For_Nested_Rule_Params_Returns_Success(string ruleFileName) { dynamic[] inputs = GetInputs4(); var ruleParams = new List(); for (int i = 0; i < inputs.Length; i++) { var input = inputs[i]; var obj = Utils.GetTypedObject(input); ruleParams.Add(new RuleParameter($"input{i + 1}", obj)); } var files = Directory.GetFiles(Directory.GetCurrentDirectory(), ruleFileName, SearchOption.AllDirectories); if (files == null || files.Length == 0) throw new Exception("Rules not found."); var fileData = File.ReadAllText(files[0]); var bre = new RulesEngine(JsonConvert.DeserializeObject(fileData), null); var result = bre.ExecuteRule("inputWorkflow", ruleParams?.ToArray()); ; var ruleResult = result?.FirstOrDefault(r => string.Equals(r.Rule.RuleName, "GiveDiscount10", StringComparison.OrdinalIgnoreCase)); Assert.True(ruleResult.IsSuccess); } [Theory] [InlineData("rules2.json")] public void ExecuteRule_ReturnsProperErrorOnMissingRuleParameter(string ruleFileName) { var re = GetRulesEngine(ruleFileName); var input1 = new RuleParameter("customName",GetInput1()); var input2 = new RuleParameter("input2",GetInput2()); var input3 = new RuleParameter("input3",GetInput3()); List result = re.ExecuteRule("inputWorkflow", input1,input2, input3); Assert.NotNull(result); Assert.IsType>(result); Assert.Contains(result.First().ChildResults, c => c.ExceptionMessage.Contains("Unknown identifier 'input1'")); } [Theory] [InlineData("rules5.json","hello",true)] [InlineData("rules5.json",null,false)] public void ExecuteRule_WithInjectedUtils_ReturnsListOfRuleResultTree(string ruleFileName,string propValue,bool expectedResult) { var re = GetRulesEngine(ruleFileName); dynamic input1 = new ExpandoObject(); if(propValue != null) input1.Property1 = propValue; if(propValue == null) input1.Property1 = null; var utils = new TestInstanceUtils(); List result = re.ExecuteRule("inputWorkflow", new RuleParameter("input1",input1),new RuleParameter("utils",utils)); Assert.NotNull(result); Assert.IsType>(result); Assert.All(result,c => Assert.Equal(expectedResult,c.IsSuccess)); } private RulesEngine CreateRulesEngine(WorkflowRules workflow) { var json = JsonConvert.SerializeObject(workflow); return new RulesEngine(new string[] { json }, null); } private RulesEngine GetRulesEngine(string filename) { var filePath = Path.Combine(Directory.GetCurrentDirectory() as string, "TestData", filename); var data = File.ReadAllText(filePath); var injectWorkflow = new WorkflowRules { WorkflowName = "inputWorkflowReference", WorkflowRulesToInject = new List { "inputWorkflow" } }; var injectWorkflowStr = JsonConvert.SerializeObject(injectWorkflow); var mockLogger = new Mock(); return new RulesEngine(new string[] { data, injectWorkflowStr }, mockLogger.Object); } private dynamic GetInput1() { var converter = new ExpandoObjectConverter(); var basicInfo = "{\"name\": \"Dishant\",\"email\": \"abc@xyz.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 10000}"; return JsonConvert.DeserializeObject(basicInfo, converter); } private dynamic GetInput2() { var converter = new ExpandoObjectConverter(); var orderInfo = "{\"totalOrders\": 5,\"recurringItems\": 2}"; return JsonConvert.DeserializeObject(orderInfo, converter); } private dynamic GetInput3() { var converter = new ExpandoObjectConverter(); var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}"; return JsonConvert.DeserializeObject(telemetryInfo, converter); } /// /// Gets the inputs. /// /// /// The inputs. /// private static dynamic[] GetInputs4() { var basicInfo = "{\"name\": \"Dishant\",\"email\": \"abc@xyz.com\",\"creditHistory\": \"good\",\"country\": \"canada\",\"loyalityFactor\": 3,\"totalPurchasesToDate\": 70000}"; var orderInfo = "{\"totalOrders\": 50,\"recurringItems\": 2}"; var telemetryInfo = "{\"noOfVisitsPerMonth\": 10,\"percentageOfBuyingToVisit\": 15}"; var laborCategoriesInput = "[{\"country\": \"india\", \"loyalityFactor\": 2, \"totalPurchasesToDate\": 20000}]"; var currentLaborCategoryInput = "{\"CurrentLaborCategoryProp\":\"TestVal2\"}"; var converter = new ExpandoObjectConverter(); dynamic input1 = JsonConvert.DeserializeObject>(laborCategoriesInput); dynamic input2 = JsonConvert.DeserializeObject(currentLaborCategoryInput, converter); dynamic input3 = JsonConvert.DeserializeObject(telemetryInfo, converter); dynamic input4 = JsonConvert.DeserializeObject(basicInfo, converter); dynamic input5 = JsonConvert.DeserializeObject(orderInfo, converter); var inputs = new dynamic[] { input1, input2, input3, input4, input5 }; return inputs; } [ExcludeFromCodeCoverage] private class TestInstanceUtils{ public bool CheckExists(string str){ if(str != null && str.Length > 0) return true; return false; } } } }