using Admin.NET.Plugin.AiDOP.Rule; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.Rule; public class RuleConfigResolverTests { [Fact] public void Resolve_WhenFactoryTenantAndGlobalProvided_AppliesHigherPriorityValues() { var defaults = new TestOptions { QuotaRequiredTotal = 100m, DefaultLeadDays = 1 }; var layers = new[] { Layer("global", 10, Item("defaultLeadDays", "number", "2")), Layer("tenant", 20, Item("defaultLeadDays", "number", "3")), Layer("factory", 30, Item("defaultLeadDays", "number", "4")) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.True(result.Success); Assert.Equal(100m, result.Options.QuotaRequiredTotal); Assert.Equal(4, result.Options.DefaultLeadDays); } [Fact] public void Resolve_WhenStringArrayProvided_ConvertsToStringList() { var defaults = new TestOptions(); var layers = new[] { Layer("global", 10, Item("poBuyerCodes", "string_array", """["110","160","170"]""")) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.True(result.Success); Assert.Equal(new[] { "110", "160", "170" }, result.Options.PoBuyerCodes); } [Fact] public void Resolve_WhenBooleanProvided_ConvertsToBool() { var defaults = new TestOptions { EnablePrMerge = false }; var layers = new[] { Layer("global", 10, Item("enablePrMerge", "boolean", "true")) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.True(result.Success); Assert.True(result.Options.EnablePrMerge); } [Fact] public void Resolve_WhenNumberProvided_ConvertsToDecimalAndInt() { var defaults = new TestOptions(); var layers = new[] { Layer("global", 10, Item("quotaTolerance", "number", "0.01"), Item("defaultLeadDays", "number", "5")) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.True(result.Success); Assert.Equal(0.01m, result.Options.QuotaTolerance); Assert.Equal(5, result.Options.DefaultLeadDays); } [Fact] public void Resolve_WhenHighRiskSwitchInvalid_FallsBackToConservativeFalse() { var defaults = new TestOptions { EnableExternalPushTracking = true }; var layers = new[] { Layer("global", 10, Item("enableExternalPushTracking", "boolean", "bad", highRisk: true)) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.True(result.Success); Assert.False(result.Options.EnableExternalPushTracking); Assert.Contains(result.Warnings, x => x.Contains("保守值 false")); } [Fact] public void Resolve_WhenSamePriorityHasDuplicateRule_ReturnsError() { var defaults = new TestOptions(); var layers = new[] { Layer("tenant-a", 20, Item("defaultLeadDays", "number", "2")), Layer("tenant-b", 20, Item("defaultLeadDays", "number", "3")) }; var result = RuleConfigResolver.Resolve(defaults, layers); Assert.False(result.Success); Assert.Contains(result.Errors, x => x.Contains("重复规则")); Assert.Equal(defaults.DefaultLeadDays, result.Options.DefaultLeadDays); } private static RuleConfigLayer Layer(string scope, int priority, params RuleConfigItem[] items) { return new RuleConfigLayer { Scope = scope, Priority = priority, Items = items.ToList() }; } private static RuleConfigItem Item(string ruleCode, string valueType, string value, bool highRisk = false) { return new RuleConfigItem { RuleCode = ruleCode, ValueType = valueType, RuleValue = value, IsHighRiskSwitch = highRisk }; } private sealed class TestOptions { public decimal QuotaRequiredTotal { get; set; } = 100m; public decimal QuotaTolerance { get; set; } = 0.0001m; public List PoBuyerCodes { get; set; } = new() { "110" }; public int DefaultLeadDays { get; set; } = 1; public bool EnablePrMerge { get; set; } = true; public bool EnableExternalPushTracking { get; set; } } }