work-flow/tests/Workflow.Tests/Condition/ConditionEvaluatorTests.cs

567 lines
15 KiB
C#

using System.Collections.Generic;
using FluentAssertions;
using Workflow.Domain.Expressions;
using Xunit;
namespace Workflow.Tests.Condition;
public class ConditionEvaluatorTests
{
private readonly ConditionEvaluator _sut = new();
#region Simple Comparisons
[Fact]
public void Evaluate_Equals_ReturnsTrue_WhenValueMatches()
{
// Arrange
const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["status"] = "approved"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_Equals_ReturnsFalse_WhenValueDiffers()
{
// Arrange
const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["status"] = "rejected"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_NotEquals_ReturnsTrue_WhenValueDiffers()
{
// Arrange
const string condition = """{ "field": "status", "op": "!=", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["status"] = "rejected"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_GreaterThan_ReturnsTrue_WhenValueIsGreater()
{
// Arrange
const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
var variables = new Dictionary<string, object>
{
["amount"] = 7500
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_GreaterThan_ReturnsFalse_WhenValueIsLessOrEqual()
{
// Arrange
const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
var variables = new Dictionary<string, object>
{
["amount"] = 5000
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_LessThan_ReturnsTrue_WhenValueIsLess()
{
// Arrange
const string condition = """{ "field": "days", "op": "<", "value": 5 }""";
var variables = new Dictionary<string, object>
{
["days"] = 3
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_GreaterThanOrEqual_ReturnsTrue_WhenValueIsEqual()
{
// Arrange
const string condition = """{ "field": "amount", "op": ">=", "value": 5000 }""";
var variables = new Dictionary<string, object>
{
["amount"] = 5000
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_LessThanOrEqual_ReturnsTrue_WhenValueIsEqual()
{
// Arrange
const string condition = """{ "field": "days", "op": "<=", "value": 3 }""";
var variables = new Dictionary<string, object>
{
["days"] = 3
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_Contains_ReturnsTrue_WhenFieldContainsValue()
{
// Arrange
const string condition = """{ "field": "description", "op": "contains", "value": "urgent" }""";
var variables = new Dictionary<string, object>
{
["description"] = "This is an urgent request"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_Contains_ReturnsFalse_WhenFieldDoesNotContainValue()
{
// Arrange
const string condition = """{ "field": "description", "op": "contains", "value": "urgent" }""";
var variables = new Dictionary<string, object>
{
["description"] = "This is a routine request"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_In_ReturnsTrue_WhenValueInArray()
{
// Arrange
const string condition = """{ "field": "status", "op": "in", "value": ["pending", "review", "approved"] }""";
var variables = new Dictionary<string, object>
{
["status"] = "review"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_In_ReturnsFalse_WhenValueNotInArray()
{
// Arrange
const string condition = """{ "field": "status", "op": "in", "value": ["pending", "review", "approved"] }""";
var variables = new Dictionary<string, object>
{
["status"] = "rejected"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
#endregion
#region Combined Conditions
[Fact]
public void Evaluate_And_ReturnsTrue_WhenAllConditionsTrue()
{
// Arrange
const string condition = """
{ "and": [
{ "field": "days", "op": "<=", "value": 3 },
{ "field": "type", "op": "==", "value": "annual" }
]}
""";
var variables = new Dictionary<string, object>
{
["days"] = 2,
["type"] = "annual"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_And_ReturnsFalse_WhenAnyConditionFalse()
{
// Arrange
const string condition = """
{ "and": [
{ "field": "days", "op": "<=", "value": 3 },
{ "field": "type", "op": "==", "value": "annual" }
]}
""";
var variables = new Dictionary<string, object>
{
["days"] = 5,
["type"] = "annual"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_Or_ReturnsTrue_WhenAnyConditionTrue()
{
// Arrange
const string condition = """
{ "or": [
{ "field": "amount", "op": ">", "value": 10000 },
{ "field": "level", "op": "==", "value": "executive" }
]}
""";
var variables = new Dictionary<string, object>
{
["amount"] = 5000,
["level"] = "executive"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_Or_ReturnsFalse_WhenAllConditionsFalse()
{
// Arrange
const string condition = """
{ "or": [
{ "field": "amount", "op": ">", "value": 10000 },
{ "field": "level", "op": "==", "value": "executive" }
]}
""";
var variables = new Dictionary<string, object>
{
["amount"] = 5000,
["level"] = "manager"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
#endregion
#region Built-in Variables
[Fact]
public void Evaluate_LastResult_ReturnsApprovedResult()
{
// Arrange
const string condition = """{ "field": "$lastResult", "op": "==", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["$lastResult"] = "approved"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_LastResult_WithRejectedResult()
{
// Arrange
const string condition = """{ "field": "$lastResult", "op": "==", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["$lastResult"] = "rejected"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
#endregion
#region Nested Conditions
[Fact]
public void Evaluate_NestedAndOr_CombinesCorrectly()
{
// Arrange
// (type == "sick" OR type == "personal") AND (days <= 1)
const string condition = """
{ "and": [
{ "or": [
{ "field": "type", "op": "==", "value": "sick" },
{ "field": "type", "op": "==", "value": "personal" }
]},
{ "field": "days", "op": "<=", "value": 1 }
]}
""";
var variables = new Dictionary<string, object>
{
["type"] = "sick",
["days"] = 1
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
#endregion
#region Edge Cases
[Fact]
public void Evaluate_MissingField_ReturnsFalse()
{
// Arrange
const string condition = """{ "field": "nonexistent", "op": "==", "value": "something" }""";
var variables = new Dictionary<string, object>
{
["other"] = "value"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_NullVariable_ReturnsFalse()
{
// Arrange
const string condition = """{ "field": "status", "op": "==", "value": "approved" }""";
var variables = new Dictionary<string, object>
{
["status"] = null!
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
[Fact]
public void Evaluate_EmptyCondition_ReturnsTrue()
{
// Arrange
const string condition = "";
var variables = new Dictionary<string, object>();
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_TypeMismatch_ReturnsFalse()
{
// Arrange
const string condition = """{ "field": "amount", "op": ">", "value": 5000 }""";
var variables = new Dictionary<string, object>
{
["amount"] = "not-a-number"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeFalse();
}
#endregion
#region Numeric Comparisons
[Fact]
public void Evaluate_NumericGreaterThan_WithStringVariables_ParsesCorrectly()
{
// Arrange
const string condition = """{ "field": "total", "op": ">", "value": 100 }""";
var variables = new Dictionary<string, object>
{
["total"] = "250"
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
[Fact]
public void Evaluate_NumericLessThan_WithDecimalValues()
{
// Arrange
const string condition = """{ "field": "price", "op": "<", "value": 99.99 }""";
var variables = new Dictionary<string, object>
{
["price"] = 49.50
};
// Act
var result = _sut.Evaluate(condition, variables);
// Assert
result.Should().BeTrue();
}
#endregion
#region Simple Expression with Quotes
[Fact]
public void Evaluate_SimpleExpression_DoubleQuotedValue_ReturnsTrue()
{
var variables = new Dictionary<string, object> { ["name"] = "hello world" };
_sut.Evaluate("""name == "hello world" """, variables).Should().BeTrue();
}
[Fact]
public void Evaluate_SimpleExpression_SingleQuotedValue_ReturnsTrue()
{
var variables = new Dictionary<string, object> { ["name"] = "hello world" };
_sut.Evaluate("""name == 'hello world'""", variables).Should().BeTrue();
}
[Fact]
public void Evaluate_SimpleExpression_DoubleQuotedValue_ReturnsFalse()
{
var variables = new Dictionary<string, object> { ["name"] = "other" };
_sut.Evaluate("""name == "hello world" """, variables).Should().BeFalse();
}
[Fact]
public void Evaluate_SimpleExpression_UnquotedValue_StillWorks()
{
var variables = new Dictionary<string, object> { ["status"] = "approved" };
_sut.Evaluate("status == approved", variables).Should().BeTrue();
}
[Fact]
public void Evaluate_SimpleExpression_ContainsWithQuotedValue_ReturnsTrue()
{
var variables = new Dictionary<string, object> { ["desc"] = "urgent request" };
_sut.Evaluate("""desc contains "urgent" """, variables).Should().BeTrue();
}
#endregion
#region New Operators via JSON
[Fact]
public void Evaluate_Between_ReturnsTrue_WhenInRange()
{
const string condition = """{ "field": "amount", "op": "between", "value": [100, 500] }""";
var variables = new Dictionary<string, object> { ["amount"] = 250 };
_sut.Evaluate(condition, variables).Should().BeTrue();
}
[Fact]
public void Evaluate_Between_ReturnsFalse_WhenOutOfRange()
{
const string condition = """{ "field": "amount", "op": "between", "value": [100, 500] }""";
var variables = new Dictionary<string, object> { ["amount"] = 999 };
_sut.Evaluate(condition, variables).Should().BeFalse();
}
[Fact]
public void Evaluate_NotIn_ReturnsTrue_WhenNotInArray()
{
const string condition = """{ "field": "status", "op": "notIn", "value": ["pending", "review"] }""";
var variables = new Dictionary<string, object> { ["status"] = "approved" };
_sut.Evaluate(condition, variables).Should().BeTrue();
}
[Fact]
public void Evaluate_EqualsIgnoreCase_ReturnsTrue()
{
const string condition = """{ "field": "name", "op": "equalsIgnoreCase", "value": "HELLO" }""";
var variables = new Dictionary<string, object> { ["name"] = "hello" };
_sut.Evaluate(condition, variables).Should().BeTrue();
}
#endregion
}