using Admin.NET.Plugin.AiDOP.Rule; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.Rule; public class RuleConfigServiceTests { [Fact] public async Task ResolveOptionsAsync_WhenNoConfiguredLayers_ReturnsDefaults() { var service = new RuleConfigService(); var defaults = new S3DeliveryGenerateRuleOptions { QuotaRequiredTotal = 99m, DefaultLeadDays = 2, EnableExternalPushTracking = true }; var result = await service.ResolveOptionsAsync("S3", "DELIVERY_GENERATE", 1000, 2000, defaults); Assert.True(result.Success); Assert.Equal(99m, result.Options.QuotaRequiredTotal); Assert.Equal(2, result.Options.DefaultLeadDays); Assert.True(result.Options.EnableExternalPushTracking); Assert.Empty(result.AppliedItems); } [Fact] public async Task ResolveS3DeliveryGenerateOptionsAsync_WhenNoConfiguredLayers_ReturnsS3Defaults() { var service = new RuleConfigService(); var result = await service.ResolveS3DeliveryGenerateOptionsAsync(1000, 2000); Assert.True(result.Success); Assert.Equal(100m, result.Options.QuotaRequiredTotal); Assert.Equal(0.0001m, result.Options.QuotaTolerance); Assert.Equal(new[] { "110", "160", "170" }, result.Options.PoBuyerCodes); Assert.Equal(new[] { "标准", "VMI", "委外加工" }, result.Options.DoUsages); Assert.Equal(1, result.Options.DefaultLeadDays); Assert.True(result.Options.EnablePackagingRoundUp); Assert.True(result.Options.ShortageCreatePr); Assert.True(result.Options.EnablePrMerge); Assert.True(result.Options.EnableRequireGoodsToPo); Assert.True(result.Options.EnableExternalPushTracking); } [Fact] public async Task ResolveOptionsAsync_WhenRepositoryReturnsLayers_AppliesConfiguredRules() { var service = new RuleConfigService(new InMemoryRuleConfigRepository(new[] { new RuleConfigLayer { Scope = "global", Priority = 100, Items = new List { new() { RuleCode = "defaultLeadDays", ValueType = "number", RuleValue = "3" }, new() { RuleCode = "enableExternalPushTracking", ValueType = "boolean", RuleValue = "false" } } } })); var result = await service.ResolveS3DeliveryGenerateOptionsAsync(1000, 2000); Assert.True(result.Success); Assert.Equal(3, result.Options.DefaultLeadDays); Assert.False(result.Options.EnableExternalPushTracking); Assert.Equal(2, result.AppliedItems.Count); } [Fact] public async Task ResolveOptionsAsync_WhenCancellationRequested_Throws() { var service = new RuleConfigService(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); await Assert.ThrowsAsync(() => service.ResolveOptionsAsync("S3", "DELIVERY_GENERATE", 1000, null, new S3DeliveryGenerateRuleOptions(), cts.Token)); } [Fact] public async Task CreateSnapshot_WhenNoAppliedItems_UsesDefaultSource() { var service = new RuleConfigService(); var resolvedAt = new DateTime(2026, 5, 16, 12, 0, 0); var result = await service.ResolveS3DeliveryGenerateOptionsAsync(1000, 2000); var snapshot = service.CreateSnapshot("S3", "DELIVERY_GENERATE", 1000, 2000, result, resolvedAt); Assert.Equal("S3", snapshot.ModuleCode); Assert.Equal("DELIVERY_GENERATE", snapshot.ScenarioCode); Assert.Equal("default", snapshot.Source); Assert.Equal(resolvedAt, snapshot.ResolvedAt); Assert.Equal(1000, snapshot.TenantId); Assert.Equal(2000, snapshot.FactoryId); Assert.Same(result.Options, snapshot.Options); Assert.Empty(snapshot.AppliedItems); Assert.Empty(snapshot.Warnings); } [Fact] public async Task CreateSnapshot_WhenAppliedItemsExist_UsesConfiguredSource() { var service = new RuleConfigService(new InMemoryRuleConfigRepository(new[] { new RuleConfigLayer { Scope = "factory", Priority = 300, Items = new List { new() { RuleCode = "defaultLeadDays", ValueType = "number", RuleValue = "5" } } } })); var result = await service.ResolveS3DeliveryGenerateOptionsAsync(1000, 2000); var snapshot = service.CreateSnapshot("S3", "DELIVERY_GENERATE", 1000, 2000, result, DateTime.Now); Assert.Equal("configured", snapshot.Source); Assert.Single(snapshot.AppliedItems); Assert.Equal(5, snapshot.Options.DefaultLeadDays); } [Fact] public async Task ResolveS3DeliveryGenerateSnapshotAsync_ReturnsSnapshot() { var service = new RuleConfigService(new InMemoryRuleConfigRepository(new[] { new RuleConfigLayer { Scope = "global", Priority = 100, Items = new List { new() { RuleCode = "defaultLeadDays", ValueType = "number", RuleValue = "4" } } } })); var resolvedAt = new DateTime(2026, 5, 16, 13, 30, 0); var snapshot = await service.ResolveS3DeliveryGenerateSnapshotAsync(1000, null, resolvedAt); Assert.Equal("S3", snapshot.ModuleCode); Assert.Equal("DELIVERY_GENERATE", snapshot.ScenarioCode); Assert.Equal("configured", snapshot.Source); Assert.Equal(resolvedAt, snapshot.ResolvedAt); Assert.Equal(1000, snapshot.TenantId); Assert.Null(snapshot.FactoryId); Assert.Equal(4, snapshot.Options.DefaultLeadDays); Assert.Single(snapshot.AppliedItems); } private sealed class InMemoryRuleConfigRepository : IRuleConfigRepository { private readonly IReadOnlyList _layers; public InMemoryRuleConfigRepository(IReadOnlyList layers) { _layers = layers; } public Task> GetEffectiveLayersAsync( string moduleCode, string scenarioCode, long tenantId, long? factoryId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); _ = moduleCode; _ = scenarioCode; _ = tenantId; _ = factoryId; return Task.FromResult(_layers); } } }