| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
- using Microsoft.Extensions.Logging.Abstractions;
- using SqlSugar;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.MaterialWarehouse;
- /// <summary>
- /// 库存物化有界等待锁的进程内行为。不连真实库。
- /// 跨连接互斥留在 S5 集成测试,仅当 AIDOP_IT=1 时执行。
- /// </summary>
- public class InventoryInboundLockGuardAcquireTests
- {
- [Fact]
- public void ProductionDefaults_AreTenMinutesAndTwoSeconds()
- {
- Assert.Equal(TimeSpan.FromMinutes(10), InventoryInboundLockGuard.DefaultWaitTimeout);
- Assert.Equal(TimeSpan.FromSeconds(2), InventoryInboundLockGuard.DefaultPollInterval);
- }
- [Fact]
- public async Task AcquireAsync_PreCancelledToken_StopsBeforeWaiting()
- {
- using var db = ClosedClient();
- using var cts = new CancellationTokenSource();
- cts.Cancel();
- var started = DateTime.UtcNow;
- await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
- InventoryInboundLockGuard.AcquireAsync(
- db, NullLogger.Instance, "aidop:t:inv-in:cancel",
- TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(50), cts.Token));
- Assert.True(DateTime.UtcNow - started < TimeSpan.FromSeconds(2));
- }
- [Fact]
- public async Task AcquireAsync_UnreachableDatabase_TimesOutWithFixedMessage()
- {
- using var db = ClosedClient();
- var ex = await Assert.ThrowsAsync<TimeoutException>(() =>
- InventoryInboundLockGuard.AcquireAsync(
- db, NullLogger.Instance, "aidop:t:inv-in:timeout",
- TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(50)));
- Assert.Equal("inventory materialization lock busy", ex.Message);
- }
- [Fact]
- public void TenantMaterializeEntry_WaitsForLock_BackgroundPathsStayNonBlocking()
- {
- var src = File.ReadAllText(Path.Combine(RepoRoot(),
- "server", "Plugins", "Admin.NET.Plugin.AiDOP", "MaterialWarehouse", "InventoryMdpSyncService.cs"));
- var method = Slice(src, "public async Task<int> TransformTransStdFromStgAsync(", "private async Task<InventorySyncResult> RunAsync");
- Assert.Contains("InventoryInboundLockGuard.AcquireAsync(", method, StringComparison.Ordinal);
- Assert.DoesNotContain("TryAcquireAsync", method, StringComparison.Ordinal);
- Assert.True(Count(src, "InventoryInboundLockGuard.TryAcquireAsync(") >= 3);
- }
- private static SqlSugarClient ClosedClient() => new(new ConnectionConfig
- {
- DbType = DbType.MySql,
- ConnectionString = "Server=127.0.0.1;Port=1;User ID=x;Password=x;Database=x;Connection Timeout=1;Pooling=false;",
- IsAutoCloseConnection = true,
- });
- private static string RepoRoot()
- {
- var dir = new DirectoryInfo(AppContext.BaseDirectory);
- while (dir != null && !File.Exists(Path.Combine(dir.FullName, "AGENTS.md")))
- dir = dir.Parent;
- return dir?.FullName ?? throw new InvalidOperationException("repo root not found");
- }
- private static string Slice(string src, string start, string end)
- {
- var i = src.IndexOf(start, StringComparison.Ordinal);
- var j = src.IndexOf(end, i, StringComparison.Ordinal);
- Assert.True(i >= 0 && j > i);
- return src[i..j];
- }
- private static int Count(string src, string token)
- {
- var n = 0;
- var i = 0;
- while ((i = src.IndexOf(token, i, StringComparison.Ordinal)) >= 0)
- {
- n++;
- i += token.Length;
- }
- return n;
- }
- }
|