using Admin.NET.Plugin.AiDOP.DataPlatform.Executors; using Admin.NET.Plugin.AiDOP.Entity.DataPlatform; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform; public class MdpOutboundGateTests { [Fact] public async Task Disabled_DbExecutor_DoesNotSend() { var gate = Gate(false); var executor = new MdpDbPushExecutor(null!, gate); var result = await executor.PushAsync(new MdpSource { SourceType = "DB", SourceCode = "DOPDEMORQ_SQLSERVER" }, new MdpOutbox()); Assert.True(result.Held); Assert.False(result.Success); Assert.Equal(1, gate.RefusedCount); } [Fact] public async Task Disabled_ApiExecutor_DoesNotSend() { var gate = Gate(false); var executor = new MdpApiPushExecutor(null!, gate, NullLoggerFactory.Instance); var result = await executor.PushAsync(new MdpSource { SourceType = "API", SourceCode = "WMS_API", ApiBaseUrl = "http://127.0.0.1" }, new MdpOutbox()); Assert.True(result.Held); Assert.False(result.Success); Assert.Equal(1, gate.RefusedCount); } [Fact] public void Enabled_DoesNotHold() { var gate = Gate(true); Assert.Null(gate.TryHold()); Assert.Equal(0, gate.RefusedCount); } [Fact] public void Callers_DoNotMutateOutbox_WhenHeld() { var admin = Read("DataPlatform/MdpOutboxAdminService.cs"); var pushNow = admin[(admin.IndexOf("push-now", StringComparison.Ordinal))..]; var heldAt = pushNow.IndexOf("result.Held", StringComparison.Ordinal); var updateAt = pushNow.IndexOf("Updateable", StringComparison.Ordinal); Assert.True(heldAt >= 0 && updateAt > heldAt, "手工重投必须先判断 Held,再写 outbox"); var dispatcher = Read("DataPlatform/Executors/MdpTargetPushDispatcher.cs"); var gateAt = dispatcher.IndexOf("!_gate.IsEnabled", StringComparison.Ordinal); var queryAt = dispatcher.IndexOf("Queryable", StringComparison.Ordinal); Assert.True(gateAt >= 0 && gateAt < queryAt, "批量推送关闭时不得先取出消息"); Assert.Contains("result.Held", dispatcher, StringComparison.Ordinal); var pick = Read("DataPlatform/Wms/PickBillNbrSyncService.cs"); var pickGate = pick.IndexOf("_gate.TryHold()", StringComparison.Ordinal); var pickSend = pick.IndexOf("GetScopeAsync", StringComparison.Ordinal); Assert.True(pickGate >= 0 && pickGate < pickSend, "领料单号同步必须先过闸门再连 165"); } private static MdpOutboundGate Gate(bool enabled) => new(new FixedMonitor(enabled), NullLoggerFactory.Instance); private static string Read(string relative) { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir != null) { var candidate = Path.Combine(dir.FullName, "server", "Plugins", "Admin.NET.Plugin.AiDOP", relative); if (File.Exists(candidate)) return File.ReadAllText(candidate); dir = dir.Parent; } throw new FileNotFoundException(relative); } private sealed class FixedMonitor(bool enabled) : IOptionsMonitor { public AidopOutboundOptions CurrentValue { get; } = new() { Enabled = enabled }; public AidopOutboundOptions Get(string? name) => CurrentValue; public IDisposable OnChange(Action listener) => new Noop(); private sealed class Noop : IDisposable { public void Dispose() { } } } }