| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- using System.Collections.Concurrent;
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering;
- using Xunit;
- using Xunit.Abstractions;
- namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
- /// <summary>
- /// Phase 4A 取号并发测试(同进程;InMemory provider)。
- /// **可现在证明**:同进程高并发下取号结果不重复、批内连续、重试有界。
- /// **不能声称**:多实例 + 真实 MySQL 最终唯一 / DB UNIQUE 实拒(→ Phase 4B)。
- /// </summary>
- public class BusinessNumberConcurrencyTests
- {
- private readonly ITestOutputHelper _out;
- public BusinessNumberConcurrencyTests(ITestOutputHelper output) => _out = output;
- private static InMemoryBusinessNumberProvider NewProvider()
- => new InMemoryBusinessNumberProvider(
- new[]
- {
- new BusinessNumberRule
- {
- RuleCode = "IQC_RCT", Prefix1 = "IR", DateType = "YYYYMMDD",
- IsDateType = true, MaxValue = 9999999, IniValue = 1,
- },
- },
- clock: () => new DateTime(2026, 8, 8));
- [Fact]
- public async Task NextBatch_1000Concurrent_AllUnique_NoDuplicate()
- {
- const int n = 1000;
- var provider = NewProvider();
- var bag = new ConcurrentBag<string>();
- var tasks = Enumerable.Range(0, n).Select(_ => Task.Run(async () =>
- {
- var b = await provider.NextBatchAsync("IQC_RCT", "8010", 1, "tester");
- bag.Add(b[0]);
- }));
- await Task.WhenAll(tasks);
- var all = bag.ToList();
- var unique = all.Distinct().Count();
- var duplicates = all.Count - unique;
- _out.WriteLine($"[取号并发] 总请求={all.Count} 唯一={unique} 重复={duplicates}");
- Assert.Equal(n, all.Count);
- Assert.Equal(n, unique);
- Assert.Equal(0, duplicates);
- }
- [Fact]
- public async Task NextBatch_ReservesContiguousBlock()
- {
- var provider = NewProvider();
- var batch = await provider.NextBatchAsync("IQC_RCT", "8010", 5, "tester");
- Assert.Equal(5, batch.Count);
- Assert.Equal(5, batch.Distinct().Count());
- // 批内连续:末位流水依次 +1
- Assert.EndsWith("0000001", batch[0]);
- Assert.EndsWith("0000005", batch[4]);
- }
- [Fact]
- public async Task NextBatch_DifferentDomain_IndependentCounters()
- {
- var provider = NewProvider();
- var a = (await provider.NextBatchAsync("IQC_RCT", "8010", 1, "t"))[0];
- var b = (await provider.NextBatchAsync("IQC_RCT", "9010", 1, "t"))[0];
- // 不同 domain 各自计数,均从 1 开始 → 号相同但作用域不同(说明按 domain 隔离)
- Assert.EndsWith("0000001", a);
- Assert.EndsWith("0000001", b);
- }
- [Fact]
- public async Task NextWithUniqueRetry_DuplicateThenSuccess_BoundedAttempts()
- {
- var svc = new BusinessNumberService(NewProvider());
- var calls = 0;
- // 前 2 次模拟撞 DB UNIQUE(返回 false),第 3 次成功
- var r = await svc.NextWithUniqueRetryAsync("IQC_RCT", "8010", "t",
- tryConsume: _ => { calls++; return Task.FromResult(calls >= 3); }, maxRetry: 5);
- _out.WriteLine($"[取号重试] success={r.Success} attempts={r.Attempts}");
- Assert.True(r.Success);
- Assert.Equal(3, r.Attempts);
- }
- [Fact]
- public async Task NextWithUniqueRetry_AllDuplicate_FailsBounded_NoInfiniteLoop()
- {
- var svc = new BusinessNumberService(NewProvider());
- var calls = 0;
- var r = await svc.NextWithUniqueRetryAsync("IQC_RCT", "8010", "t",
- tryConsume: _ => { calls++; return Task.FromResult(false); }, maxRetry: 5);
- _out.WriteLine($"[取号重试-全撞] success={r.Success} attempts={r.Attempts} calls={calls}");
- Assert.False(r.Success); // 有界失败,不无限循环
- Assert.Equal(5, r.Attempts);
- Assert.Equal(5, calls);
- }
- }
|