支持审批流程中的分支网关和节点触发条件
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
using YLErp.Helpers;
|
||||
|
||||
namespace YLErp.Helpers.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ConditionEvaluatorTests
|
||||
{
|
||||
private static ConditionContext CreateContext(double notional = 0, string tradeType = "", int? userId = null, int? initGroupId = null, string processCategory = "")
|
||||
{
|
||||
return new ConditionContext
|
||||
{
|
||||
Trade = new DBModels.trade
|
||||
{
|
||||
StockEqvNotional = notional,
|
||||
TradeType = tradeType
|
||||
},
|
||||
UserId = userId,
|
||||
InitGroupId = initGroupId,
|
||||
ProcessCategory = processCategory
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_EmptyOrNullCondition_ReturnsFalse()
|
||||
{
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(null, CreateContext()));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate("", CreateContext()));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(" ", CreateContext()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_InvalidJson_ReturnsFalse()
|
||||
{
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate("not a json", CreateContext()));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate("{\"tokens\": [", CreateContext()));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_TokenSingleCondition_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken
|
||||
{
|
||||
type = "condition",
|
||||
condition = new ConditionItem { field = "notional", op = ">", value = 100 }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_TokenAnd_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">", value = 100 } },
|
||||
new ConditionToken { type = "operator", connector = "and" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "<=", value = 500 } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 50)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_TokenOr_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "<", value = 100 } },
|
||||
new ConditionToken { type = "operator", connector = "or" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">", value = 500 } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 50)));
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 600)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 300)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_TokenWithParentheses_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "lparen" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">", value = 100 } },
|
||||
new ConditionToken { type = "operator", connector = "or" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "<", value = 50 } },
|
||||
new ConditionToken { type = "rparen" },
|
||||
new ConditionToken { type = "operator", connector = "and" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "tradeType", op = "==", value = "香草" } }
|
||||
}
|
||||
});
|
||||
|
||||
// (200>100 or 200<50) and tradeType=="香草" => true
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200, tradeType: "香草")));
|
||||
// (30>100 or 30<50) and tradeType=="雪球" => false
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 30, tradeType: "雪球")));
|
||||
// (80>100 or 80<50) and tradeType=="香草" => false
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 80, tradeType: "香草")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_MixedAndOr_PriorityAndOverOr()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">", value = 100 } },
|
||||
new ConditionToken { type = "operator", connector = "or" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "<", value = 50 } },
|
||||
new ConditionToken { type = "operator", connector = "and" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "tradeType", op = "==", value = "香草" } }
|
||||
}
|
||||
});
|
||||
|
||||
// A or (B and C) — and 优先级高于 or
|
||||
// 200>100 -> true,无需计算右侧
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200, tradeType: "")));
|
||||
// 30>100=false, 30<50=true, 香草==香草=true -> false or (true and true) = true
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 30, tradeType: "香草")));
|
||||
// 80>100=false, 80<50=false, 雪球==香草=false -> false or (false and false) = false
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 80, tradeType: "雪球")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_InitGroup_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "initGroup", op = "==", value = 5 } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, new ConditionContext { InitGroupId = 5 }));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, new ConditionContext { InitGroupId = 3 }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_ProcessCategory_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "processCategory", op = "==", value = "CloseProcess" } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(processCategory: "CloseProcess")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(processCategory: "TradeProcess")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_TradeTypeStringComparison_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "tradeType", op = "!=", value = "雪球" } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(tradeType: "香草")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(tradeType: "雪球")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_IncompleteParentheses_DoesNotThrow()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "lparen" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">", value = 100 } }
|
||||
}
|
||||
});
|
||||
|
||||
// Parser tolerates missing closing parenthesis and returns the value inside
|
||||
var result = ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200));
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_NotEquals_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "!=", value = 100 } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_GreaterOrEqual_Works()
|
||||
{
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = ">=", value = 100 } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 99)));
|
||||
}
|
||||
|
||||
// ===== 字母 op 标识符(前端改用 gt/lt/gte/lte/eq/neq 规避 > < 编码问题)=====
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_AlphaOp_GreaterThan_Works()
|
||||
{
|
||||
var condition = BuildTokens(new ConditionItem { field = "notional", op = "gt", value = 100 });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 50)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_AlphaOp_LessThanOrEqual_Works()
|
||||
{
|
||||
var condition = BuildTokens(new ConditionItem { field = "notional", op = "lte", value = 100 });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 50)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 101)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_AlphaOp_EqualAndNotEqual_Works()
|
||||
{
|
||||
var eqCond = BuildTokens(new ConditionItem { field = "tradeType", op = "eq", value = "香草" });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(eqCond, CreateContext(tradeType: "香草")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(eqCond, CreateContext(tradeType: "雪球")));
|
||||
|
||||
var neqCond = BuildTokens(new ConditionItem { field = "tradeType", op = "neq", value = "雪球" });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(neqCond, CreateContext(tradeType: "香草")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(neqCond, CreateContext(tradeType: "雪球")));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_AlphaOp_CaseInsensitive_Works()
|
||||
{
|
||||
// 大写字母 op 也应识别(归一化为小写)
|
||||
var condition = BuildTokens(new ConditionItem { field = "notional", op = "GTE", value = 100 });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 100)));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 99)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_MixedAlphaAndSymbolOp_Works()
|
||||
{
|
||||
// 字母 op 与符号 op 混用:notional gt 100 and tradeType == 香草
|
||||
var condition = JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "notional", op = "gt", value = 100 } },
|
||||
new ConditionToken { type = "operator", connector = "and" },
|
||||
new ConditionToken { type = "condition", condition = new ConditionItem { field = "tradeType", op = "==", value = "香草" } }
|
||||
}
|
||||
});
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200, tradeType: "香草")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 50, tradeType: "香草")));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, CreateContext(notional: 200, tradeType: "雪球")));
|
||||
}
|
||||
|
||||
// ===== 需求①触发条件字段:initialNotional(期初)/ currentNotional(本次)=====
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_InitialNotional_UsesOriginalStockEqvNotional()
|
||||
{
|
||||
// initialNotional 取 trade.OriginalStockEqvNotional(与 StockEqvNotional 是不同字段)
|
||||
var condition = BuildTokens(new ConditionItem { field = "initialNotional", op = "gte", value = 1000000 });
|
||||
var ctx = new ConditionContext
|
||||
{
|
||||
Trade = new DBModels.trade
|
||||
{
|
||||
StockEqvNotional = 500, // 当前份额,不应被 initialNotional 使用
|
||||
OriginalStockEqvNotional = 2000000 // 期初名义本金
|
||||
}
|
||||
};
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, ctx));
|
||||
|
||||
var ctxBelow = new ConditionContext
|
||||
{
|
||||
Trade = new DBModels.trade { OriginalStockEqvNotional = 500000 }
|
||||
};
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, ctxBelow));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_CurrentNotional_UsesContextValue()
|
||||
{
|
||||
// currentNotional 取 ConditionContext.CurrentNotional(了结场景由 trade_cash 取绝对值传入)
|
||||
var condition = BuildTokens(new ConditionItem { field = "currentNotional", op = "gt", value = 1000000 });
|
||||
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = 5000000 }));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = 500000 }));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = null }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_CurrentNotional_AbsoluteValueSemantics()
|
||||
{
|
||||
// 需求:平仓500万,本次交易名义本金按绝对值判断。
|
||||
// 调用方应传 Math.Abs 后的正值(BuildTriggerContext 已处理),这里验证传入正值即可。
|
||||
var condition = BuildTokens(new ConditionItem { field = "currentNotional", op = "gte", value = 5000000 });
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = 5000000 }));
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = 8000000 }));
|
||||
Assert.IsFalse(ConditionEvaluator.Evaluate(condition, new ConditionContext { CurrentNotional = 4999999 }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Evaluate_InitialNotional_AbsoluteValueStored()
|
||||
{
|
||||
// OriginalStockEqvNotional 的 setter 已做 Math.Abs,负值存入会变正
|
||||
var condition = BuildTokens(new ConditionItem { field = "initialNotional", op = "gt", value = 100 });
|
||||
var ctx = new ConditionContext
|
||||
{
|
||||
Trade = new DBModels.trade { OriginalStockEqvNotional = -500 } // setter 归一化为 500
|
||||
};
|
||||
Assert.IsTrue(ConditionEvaluator.Evaluate(condition, ctx));
|
||||
}
|
||||
|
||||
/// <summary>辅助:单条件 tokens 序列化为 JSON。</summary>
|
||||
private static string BuildTokens(ConditionItem item)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new System.Collections.Generic.List<ConditionToken>
|
||||
{
|
||||
new ConditionToken { type = "condition", condition = item }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace YLErp.Helpers.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// 需求①:交易提交进入审批流程时的「起点触发条件跳过」测试。
|
||||
/// <para>场景:交易一进来,若节点1、2配置的触发条件均不满足,应直接从节点3开始审核;
|
||||
/// 若全部节点都不满足,则直接审批通过(无需任何审核)。</para>
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class TriggerNodeSkipTests
|
||||
{
|
||||
/// <summary>构造一个审批节点:order + 可选的触发条件JSON。</summary>
|
||||
private static approvalprocess Node(int order, string triggerCondition = null)
|
||||
{
|
||||
return new approvalprocess
|
||||
{
|
||||
order = order,
|
||||
node = 0,
|
||||
triggerCondition = triggerCondition
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>构造"期初名义本金 > 阈值"的触发条件JSON。</summary>
|
||||
private static string InitialNotionalGt(double threshold)
|
||||
{
|
||||
return JsonConvert.SerializeObject(new ConditionExpressionConfig
|
||||
{
|
||||
tokens = new List<ConditionToken>
|
||||
{
|
||||
new ConditionToken
|
||||
{
|
||||
type = "condition",
|
||||
condition = new ConditionItem { field = "initialNotional", op = "gt", value = threshold }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static ConditionContext Ctx(double initialNotional)
|
||||
{
|
||||
return new ConditionContext
|
||||
{
|
||||
Trade = new trade { OriginalStockEqvNotional = initialNotional }
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StartNode_NoTrigger_ReturnsStartDirectly()
|
||||
{
|
||||
// 节点1无触发条件 → 直接返回节点1
|
||||
var nodes = new List<approvalprocess> { Node(1), Node(2), Node(3) };
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(500));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(1, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StartNode_TriggerSatisfied_ReturnsStart()
|
||||
{
|
||||
// 节点1触发条件">100",交易期初200满足 → 返回节点1
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(100)),
|
||||
Node(2),
|
||||
Node(3)
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(200));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(1, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StartNode_TriggerNotSatisfied_SkipsToNextSatisfied()
|
||||
{
|
||||
// 节点1触发">100",节点2触发">500",交易期初200
|
||||
// → 节点1不满足(200>100满足? 满足)... 重新设计:节点1触发">1000",200不满足;节点2无触发 → 返回节点2
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(1000)), // 200 不满足 >1000
|
||||
Node(2), // 无触发条件
|
||||
Node(3)
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(200));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(2, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AllStartNodesNotSatisfied_ReturnsNull_DirectlyApproved()
|
||||
{
|
||||
// 节点1、2、3都有触发条件,交易都不满足 → 返回null(表示直接审批通过)
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(1000)), // 200 不满足
|
||||
Node(2, InitialNotionalGt(5000)), // 200 不满足
|
||||
Node(3, InitialNotionalGt(10000)) // 200 不满足
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(200));
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SkipMultipleNodes_LandsOnThird()
|
||||
{
|
||||
// 节点1、2都不满足,节点3无触发 → 返回节点3
|
||||
// 模拟"A默认审核、B>100W再审核、C>=500W再审核"中,小额交易跳过B、C直达... 实际应停在满足的节点
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(1000000)), // 50W 不满足
|
||||
Node(2, InitialNotionalGt(2000000)), // 50W 不满足
|
||||
Node(3) // 无触发条件(兜底审核)
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(500000));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(3, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SatisfiedAtSecondNode_StopsThere()
|
||||
{
|
||||
// 节点1触发">1000"不满足,节点2触发">100"满足 → 返回节点2(不会继续到节点3)
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(1000)), // 200 不满足
|
||||
Node(2, InitialNotionalGt(100)), // 200 满足
|
||||
Node(3, InitialNotionalGt(50)) // 不会走到这
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[0], Ctx(200));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(2, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void StartFromMiddleNode_Works()
|
||||
{
|
||||
// 起点不是节点1(如分支调整后从节点2开始),从节点2起判断
|
||||
var nodes = new List<approvalprocess>
|
||||
{
|
||||
Node(1, InitialNotionalGt(1000)),
|
||||
Node(2, InitialNotionalGt(1000)), // 200 不满足
|
||||
Node(3) // 无触发
|
||||
};
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, nodes[1], Ctx(200));
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(3, result.order);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void NullStart_ReturnsNull()
|
||||
{
|
||||
var nodes = new List<approvalprocess> { Node(1) };
|
||||
var result = ConditionEvaluator.FindFirstTriggeredNode(nodes, null, Ctx(500));
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user