# RuleEngineTests Documentation Comprehensive unit tests for Strata.Rules based on Microsoft RulesEngine patterns. This test suite demonstrates rule creation, evaluation, and various expression types. ## Test Structure Overview ```mermaid graph TD Tests[RuleEngineTests] Tests --> Basic[Basic Comparison Tests] Tests --> Logical[Logical Operator Tests] Tests --> Multi[Multiple Property Access Tests] Tests --> Groups[Rule Group Tests] Tests --> Discount[Discount Calculation Examples] Tests --> String[String Operations] Tests --> Nested[Nested Properties] Tests --> Isolation[RuleSet Isolation Tests] Tests --> Complex[Complex Real-World Scenarios] Basic --> Equality[Equality ==] Basic --> NotEqual[Not Equal !=] Basic --> GreaterThan[Greater Than >] Basic --> LessThan[Less Than <] Logical --> And[AND &] Logical --> Or[OR |] Logical --> Mixed[Mixed Operators] Groups --> AndGroup[AND Group] Groups --> OrGroup[OR Group] style Tests fill:#e1f5ff style Basic fill:#fff4e6 style Logical fill:#e8f5e9 style Groups fill:#f3e5f5 style Discount fill:#fff9c4 style Complex fill:#ffebee ``` ## Test Categories ### 1. Basic Comparison Tests Tests fundamental comparison operations with single conditions. ```mermaid sequenceDiagram participant Test participant Parameter participant Expression participant RuleSet participant Engine Test->>Parameter: Create Parameter("input") Test->>Parameter: Get Property("country") Test->>Expression: Create Equality (property == "india") Test->>RuleSet: Create SingleRule + GroupAnd Test->>Engine: RunRules(ruleSet, input) Engine-->>Test: Return true/false ``` **Example:** ```csharp // Test: input.country == "india" var inputParam = new Parameter("input"); var expression = inputParam.Property("country") == "india"; var rule = new SingleRule("CheckCountryIsIndia", expression); var ruleSet = new RuleSet(new GroupAnd(new[] { rule }), Guid.NewGuid()); var result = await engine.RunRules(ruleSet, new { country = "india" }); // Result: true ``` ### 2. Logical Operator Tests Demonstrates combining expressions using AND (`&`) and OR (`|`) operators. ```mermaid graph LR A[Country == #quot;india#quot;] -->|&| C[AND Expression] B[LoyaltyFactor > 2] -->|&| C C -->|Result| D[Combined Rule] E[Country == #quot;india#quot;] -->|#124;| G[OR Expression] F[Country == #quot;usa#quot;] -->|#124;| G G -->|Result| H[Alternative Rule] style C fill:#e8f5e9 style G fill:#fff4e6 ``` **AND Example:** ```csharp // Rule: input.country == "india" AND input.loyaltyFactor > 2 var countryExpr = inputParam.Property("country") == "india"; var loyaltyExpr = new GreaterThan(inputParam.Property("loyaltyFactor"), 2m); var expression = countryExpr & loyaltyExpr; ``` **OR Example:** ```csharp // Rule: input.country == "india" OR input.country == "usa" var indiaExpr = inputParam.Property("country") == "india"; var usaExpr = inputParam.Property("country") == "usa"; var expression = indiaExpr | usaExpr; ``` **Mixed Operators Example:** ```csharp // Rule: (country == "india" AND loyaltyFactor >= 2) OR vipCustomer == true var And = countryExpr & loyaltyExpr; var expression = And | vipExpr; ``` ### 3. Multiple Property Access Tests Tests rules that evaluate multiple properties from the same input object. ```mermaid graph TD Input[Input Object] --> P1[country] Input --> P2[totalPurchases] Input --> P3[totalOrders] P1 -->|==| E1[#quot;india#quot;] P2 -->|>=| E2[5000] P3 -->|>| E3[2] E1 -->|&| Combine[Combined Expression] E2 -->|&| Combine E3 -->|&| Combine Combine --> Result[Evaluation Result] style Input fill:#e1f5ff style Combine fill:#e8f5e9 style Result fill:#fff4e6 ``` **Example:** ```csharp // Rule: country == "india" AND totalPurchases >= 5000 AND totalOrders > 2 var expression = (inputParam.Property("country") == "india") & new GreaterThan(inputParam.Property("totalPurchases"), 4999m) & new GreaterThan(inputParam.Property("totalOrders"), 2m); ``` ### 4. Rule Group Tests Demonstrates organizing multiple rules into AND or OR groups. ```mermaid graph TD subgraph AND Group [All Rules Must Pass] R1[Rule 1: Age > 18] R2[Rule 2: Status == Active] R3[Rule 3: Balance > 0] R1 --> AndEval[AND Evaluation] R2 --> AndEval R3 --> AndEval end subgraph OR Group [Any Rule Can Pass] R4[Rule 4: Premium Member] R5[Rule 5: Loyalty > 5 years] R6[Rule 6: Total Spent > $10k] R4 --> OrEval[OR Evaluation] R5 --> OrEval R6 --> OrEval end AndEval --> Final[Final Result] OrEval --> Final style AndEval fill:#e8f5e9 style OrEval fill:#fff4e6 ``` **AND Group Example:** ```csharp // All rules must pass var rule1 = new SingleRule("R1", inputParam.Property("age") > 18m); var rule2 = new SingleRule("R2", inputParam.Property("status") == "Active"); var andGroup = new GroupAnd(new[] { rule1, rule2 }); var ruleSet = new RuleSet(andGroup, Guid.NewGuid()); ``` **OR Group Example:** ```csharp // Any rule can pass var rule1 = new SingleRule("R1", inputParam.Property("premium") == "true"); var rule2 = new SingleRule("R2", new GreaterThan(inputParam.Property("loyalty"), 5m)); var orGroup = new GroupOr(new[] { rule1, rule2 }); var ruleSet = new RuleSet(orGroup, Guid.NewGuid()); ``` ### 5. Discount Calculation Examples Real-world examples based on Microsoft RulesEngine discount calculation scenarios. ```mermaid flowchart TD Start[Customer Input] --> Check1{Country == #quot;india#quot;?} Check1 -->|No| NoDiscount[No Discount] Check1 -->|Yes| Check2{Loyalty Factor?} Check2 -->|== 2| Check3{Purchases >= $5000
AND Orders > 2?} Check2 -->|== 3| Check4{Purchases >= $10000?} Check3 -->|Yes| Discount10[10% Discount] Check3 -->|No| NoDiscount Check4 -->|Yes| Discount20[20% Discount] Check4 -->|No| Check3 style Discount10 fill:#c8e6c9 style Discount20 fill:#81c784 style NoDiscount fill:#ffcdd2 ``` **10% Discount Rule:** ```csharp // Rule: country == "india" AND loyaltyFactor <= 2 // AND totalPurchases >= 5000 AND totalOrders > 2 var expression = (inputParam.Property("country") == "india") & (inputParam.Property("loyaltyFactor") == 2m) & new GreaterThan(inputParam.Property("totalPurchasesToDate"), 4999m) & new GreaterThan(inputParam.Property("totalOrders"), 2m); var rule = new SingleRule("GiveDiscount10", expression); ``` **20% Discount Rule:** ```csharp // Rule: country == "india" AND loyaltyFactor == 3 // AND totalPurchases >= 10000 var expression = (inputParam.Property("country") == "india") & (inputParam.Property("loyaltyFactor") == 3m) & new GreaterThan(inputParam.Property("totalPurchasesToDate"), 9999m); var rule = new SingleRule("GiveDiscount20", expression); ``` **Multiple Discount Rules (OR Group):** ```csharp // Either 10% or 20% discount can apply var discount10 = new SingleRule("GiveDiscount10", /* 10% expression */); var discount20 = new SingleRule("GiveDiscount20", /* 20% expression */); var orGroup = new GroupOr(new IRule[] { discount10, discount20 }); ``` ### 6. String Operations Tests string comparison operations including case-sensitivity. ```mermaid graph LR A[String Property] --> B{Comparison Type} B -->|Case Sensitive| C[Exact Match] B -->|StartsWith| D[Prefix Check] B -->|Contains| E[Substring Search] B -->|EndsWith| F[Suffix Check] C --> Result[Boolean Result] D --> Result E --> Result F --> Result style A fill:#e1f5ff style Result fill:#e8f5e9 ``` **Example:** ```csharp // Case-sensitive string comparison var expression = inputParam.Property("name") == "John Doe"; // Tests verify exact string matching var input = new { name = "John Doe" }; // Returns true var input2 = new { name = "john doe" }; // Returns false ``` ### 7. Nested Properties Demonstrates accessing nested object properties. ```mermaid graph TD Root[Root Object] --> Child1[customer] Root --> Child2[address] Root --> Child3[account] Child1 --> CP1[name] Child1 --> CP2[age] Child2 --> AP1[street] Child2 --> AP2[city] Child2 --> AP3[country] Child3 --> AC1[balance] Child3 --> AC2[type] AP3 -.->|Property Access| Expr[Expression: address.country == #quot;USA#quot;] style Root fill:#e1f5ff style Expr fill:#e8f5e9 ``` **Example:** ```csharp // Accessing nested properties: input.address.country var addressProp = inputParam.Property("address"); var countryProp = addressProp.Property("country"); var expression = countryProp == "USA"; // Test with nested object var input = new { address = new { country = "USA", city = "New York" } }; ``` ### 8. RuleSet Isolation Tests Verifies that multiple RuleSets evaluate independently without interference. ```mermaid sequenceDiagram participant Test participant Engine participant RuleSet1 participant RuleSet2 Test->>Engine: Create Engine Test->>RuleSet1: Create RuleSet (Condition A) Test->>RuleSet2: Create RuleSet (Condition B) Test->>Engine: RunRules(RuleSet1, input) Engine->>RuleSet1: Evaluate RuleSet1-->>Engine: Result 1 Engine-->>Test: Result 1 Test->>Engine: RunRules(RuleSet2, input) Engine->>RuleSet2: Evaluate RuleSet2-->>Engine: Result 2 Engine-->>Test: Result 2 Note over Test,RuleSet2: Each RuleSet evaluates independently ``` **Example:** ```csharp // RuleSet 1: Check if country is India var ruleSet1 = new RuleSet( new GroupAnd(new[] { new SingleRule("R1", inputParam.Property("country") == "india") }), Guid.NewGuid() ); // RuleSet 2: Check if country is USA var ruleSet2 = new RuleSet( new GroupAnd(new[] { new SingleRule("R2", inputParam.Property("country") == "usa") }), Guid.NewGuid() ); // Each evaluates independently var result1 = await engine.RunRules(ruleSet1, input); // Can be true or false var result2 = await engine.RunRules(ruleSet2, input); // Independent result ``` ### 9. Complex Real-World Scenarios Demonstrates sophisticated business rules combining multiple patterns. ```mermaid flowchart TD Start[Eligibility Check] --> Type{Membership Type?} Type -->|Premium| Eligible[Eligible ✓] Type -->|Regular| Age{Account Age
> 365 days?} Age -->|No| NotEligible[Not Eligible ✗] Age -->|Yes| Spent{Total Spent
> $1000?} Spent -->|No| NotEligible Spent -->|Yes| Reviews{Negative Reviews
== 0?} Reviews -->|No| NotEligible Reviews -->|Yes| Eligible style Eligible fill:#c8e6c9 style NotEligible fill:#ffcdd2 style Start fill:#e1f5ff ``` **Complex Business Rule Example:** ```csharp // Eligible if: (Premium member) OR (Regular member with good history) var premiumRule = new SingleRule("PremiumMember", inputParam.Property("membershipType") == "Premium"); var regularWithHistoryExpr = (inputParam.Property("membershipType") == "Regular") & new GreaterThan(inputParam.Property("accountAge"), 365m) & new GreaterThan(inputParam.Property("totalSpent"), 1000m) & (inputParam.Property("negativeReviews") == 0m); var regularRule = new SingleRule("RegularMemberGoodHistory", regularWithHistoryExpr); var eligibilityGroup = new GroupOr(new IRule[] { premiumRule, regularRule }); var ruleSet = new RuleSet(eligibilityGroup, Guid.NewGuid()); ``` ## Rule Evaluation Flow ```mermaid sequenceDiagram participant Input[Input Object] participant Param[Parameter] participant Prop[Property] participant Expr[Expression] participant Rule[SingleRule] participant Group[Group AND/OR] participant Set[RuleSet] participant Engine[RuleSetEngine] participant MSEngine[Microsoft RulesEngine] Input->>Param: Provide data context Param->>Prop: Access property chain Prop->>Expr: Build expression tree Expr->>Rule: Create rule with name Rule->>Group: Add to group Group->>Set: Build RuleSet Set->>Engine: Submit for evaluation Engine->>MSEngine: Convert to RulesEngine format MSEngine->>Engine: Return evaluation result Engine->>Input: Return boolean result ``` ## Key Patterns ### Pattern 1: Simple Property Comparison ```csharp var inputParam = new Parameter("input"); var expression = inputParam.Property("propertyName") == value; var rule = new SingleRule("RuleName", expression); var ruleSet = new RuleSet(new GroupAnd(new[] { rule }), Guid.NewGuid()); ``` ### Pattern 2: Multiple Conditions (AND) ```csharp var expr1 = inputParam.Property("prop1") == value1; var expr2 = inputParam.Property("prop2") > value2; var combined = expr1 & expr2; ``` ### Pattern 3: Alternative Conditions (OR) ```csharp var expr1 = inputParam.Property("prop1") == value1; var expr2 = inputParam.Property("prop2") == value2; var combined = expr1 | expr2; ``` ### Pattern 4: Multiple Rules in Groups ```csharp var rule1 = new SingleRule("R1", expression1); var rule2 = new SingleRule("R2", expression2); // All must pass var andGroup = new GroupAnd(new[] { rule1, rule2 }); // Any can pass var orGroup = new GroupOr(new[] { rule1, rule2 }); ``` ### Pattern 5: Nested Property Access ```csharp var parentProp = inputParam.Property("parent"); var childProp = parentProp.Property("child"); var expression = childProp == value; ``` ## Test Execution All tests are executed asynchronously using NUnit: ```csharp [Test] public async Task TestName() { // Arrange - Set up rules and input var expression = /* create expression */; var ruleSet = /* create ruleset */; var input = /* create input object */; // Act - Execute rule engine var result = await engine.RunRules(ruleSet, input); // Assert - Verify expected outcome Assert.That(result, Is.True); } ``` ## Dependencies - **Strata.Rules**: Core rule engine library - **Strata.Rules.Rule**: Rule definitions (SingleRule, RuleSet, IRule) - **Strata.Rules.Rule.Expression**: Expression types (Parameter, Property, Equal, GreaterThan, etc.) - **Strata.Rules.Rule.Groups**: Grouping logic (And, Or) - **Microsoft RulesEngine**: Underlying evaluation engine - **NUnit**: Testing framework ## Test Statistics - **Total Tests**: 34 - **Test Categories**: 9 - **Success Rate**: 100% - **Execution Time**: ~1 second ## Related Documentation - [ClassDiagram.md](../Strata.Rules/ClassDiagram.md) - Expression class hierarchy - [RuleVisitorTests.md](RuleVisitorTests.md) - Visitor pattern tests