using Admin.NET.Plugin.AiDOP.Service.S8.Rules; using System.Text.Json; using Xunit; namespace Admin.NET.Test.S8; /// /// R2-TIMEOUT-EVALUATOR 纯逻辑回归。覆盖 params_json 解析 + dedup_key 生成稳定性。 /// 不接 DB / 不接 DI;evaluator 的 SQL 与 SqlSugarRepository 路径走 run-once 端到端验证。 /// public class S8TimeoutEvaluatorTests { [Fact] public void TimeoutParams_Parses_FullSpec() { var json = """ { "dueAtField": "due_at", "statusField": "status", "completedStates": ["CLOSED", "DONE", "COMPLETED"], "objectCodeField": "related_object_code", "objectIdField": "source_object_id", "graceMinutes": 5, "exceptionTypeCode": "DELIVERY_DELAY" } """; var p = S8TimeoutParams.Parse(json); Assert.Equal("due_at", p.DueAtField); Assert.Equal("status", p.StatusField); Assert.Equal(new[] { "CLOSED", "DONE", "COMPLETED" }, p.CompletedStates); Assert.Equal("related_object_code", p.ObjectCodeField); Assert.Equal("source_object_id", p.ObjectIdField); Assert.Equal(5, p.GraceMinutes); Assert.Equal("DELIVERY_DELAY", p.ExceptionTypeCode); } [Fact] public void TimeoutParams_Tolerates_MissingOptionalFields() { var json = """{ "dueAtField": "due_at", "statusField": "status", "exceptionTypeCode": "PENDING_SHIPMENT" }"""; var p = S8TimeoutParams.Parse(json); Assert.Equal("due_at", p.DueAtField); Assert.Equal("status", p.StatusField); Assert.Empty(p.CompletedStates); Assert.Null(p.ObjectCodeField); Assert.Null(p.ObjectIdField); Assert.Equal(0, p.GraceMinutes); Assert.Equal("PENDING_SHIPMENT", p.ExceptionTypeCode); } [Fact] public void TimeoutParams_Skips_BlankCompletedStates() { var json = """{ "completedStates": ["CLOSED", " ", "", "DONE"] }"""; var p = S8TimeoutParams.Parse(json); Assert.Equal(new[] { "CLOSED", "DONE" }, p.CompletedStates); } [Fact] public void TimeoutParams_Throws_OnInvalidJson() { // evaluator 在 EvaluateAsync 内 try/catch 该异常并降级为空命中;本断言锁定异常类型契约。 // ThrowsAny 而非 Throws:System.Text.Json 实际抛 JsonReaderException(JsonException 子类), // xunit v3 的 Assert.Throws 是 strict 类型匹配,必须用 ThrowsAny 接受子类。 Assert.ThrowsAny(() => S8TimeoutParams.Parse("not-a-json")); } [Fact] public void BuildDedupKey_IsStable_SameInputsSameOutput() { var k1 = S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "G01_TEST_TIMEOUT", "ORDER", "ORDER-TIMEOUT-01"); var k2 = S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "G01_TEST_TIMEOUT", "ORDER", "ORDER-TIMEOUT-01"); Assert.Equal(k1, k2); Assert.Equal("T1:F1:RG01_TEST_TIMEOUT:ORDER:ORDER-TIMEOUT-01", k1); } [Fact] public void BuildDedupKey_Differentiates_OnTenantFactoryRuleObject() { var baseKey = S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "R", "ORDER", "ID"); Assert.NotEqual(baseKey, S8TimeoutRuleEvaluator.BuildDedupKey(2, 1, "R", "ORDER", "ID")); Assert.NotEqual(baseKey, S8TimeoutRuleEvaluator.BuildDedupKey(1, 2, "R", "ORDER", "ID")); Assert.NotEqual(baseKey, S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "R2", "ORDER", "ID")); Assert.NotEqual(baseKey, S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "R", "TASK", "ID")); Assert.NotEqual(baseKey, S8TimeoutRuleEvaluator.BuildDedupKey(1, 1, "R", "ORDER", "ID2")); } [Fact] public void BuildDedupKey_Length_FitsVarchar128() { // 实体定义 dedup_key VARCHAR(128)。本断言锁定常规标识符不溢出列长度。 var k = S8TimeoutRuleEvaluator.BuildDedupKey(9999999, 9999999, "VERY_LONG_RULE_CODE_FOR_PADDING_64____", "EXTREMELY_LONG_OBJECT_TYPE______", "EXTREMELY_LONG_OBJECT_ID________"); Assert.True(k.Length <= 128, $"dedup_key length {k.Length} exceeds 128: {k}"); } }