using System;
|
using System.IO;
|
using System.Threading;
|
using Aging.Infrastructure.Core;
|
using Aging.Infrastructure.Contracts;
|
using Xunit;
|
|
namespace Aging.MvpTests
|
{
|
public class LayeredResourceManagerTests : IDisposable
|
{
|
private readonly string _dbPath;
|
private readonly LayeredResourceManager _rm;
|
|
public LayeredResourceManagerTests()
|
{
|
_dbPath = Path.Combine(Path.GetTempPath(),
|
"arb_" + Guid.NewGuid().ToString("N") + ".db");
|
_rm = new LayeredResourceManager(new DbLeaseStore(_dbPath, "local-main"));
|
}
|
|
public void Dispose() { try { File.Delete(_dbPath); File.Delete(_dbPath + "-wal"); File.Delete(_dbPath + "-shm"); } catch { } }
|
|
[Fact]
|
public void ReserveAsync_Then_ValidateOwner_True()
|
{
|
var owner = new ResourceOwnerToken(OwnerType.Maintenance, "W01");
|
var key = new ResourceKey("CAN-002", "CAN");
|
var res = _rm.ReserveAsync(owner, key, CancellationToken.None)
|
.GetAwaiter().GetResult();
|
Assert.Equal(ReservationState.Active, res.State);
|
Assert.True(_rm.ValidateOwner(owner, key));
|
}
|
|
[Fact]
|
public void ReserveAsync_Conflict_ExceptionCarriesHolder()
|
{
|
var a = new ResourceOwnerToken(OwnerType.Maintenance, "holder-A");
|
var b = new ResourceOwnerToken(OwnerType.Production, "requester-B");
|
var key = new ResourceKey("CAN-002", "CAN");
|
_rm.ReserveAsync(a, key, CancellationToken.None).GetAwaiter().GetResult();
|
var ex = Assert.Throws<ResourceConflictException>(() =>
|
_rm.ReserveAsync(b, key, CancellationToken.None).GetAwaiter().GetResult());
|
Assert.Contains("holder-A", ex.Message);
|
}
|
|
[Fact]
|
public void ValidateOwner_DoesNotShrinkLease_KeepsFullTtl()
|
{
|
// 回归:ValidateOwner 曾以 Renew(grant,1) 校验,会把租约到期砍到 now+1s,
|
// 导致作用域租约心跳 Renew 因“已过期”失败≥2 → 误报“租约已失效”。
|
var owner = new ResourceOwnerToken(OwnerType.Production, "auto_x");
|
var key = new ResourceKey("CAN-001", "Generic");
|
_rm.ReserveAsync(owner, key, CancellationToken.None).GetAwaiter().GetResult();
|
|
// 模拟 DeviceService 每次操作前的栅栏校验
|
Assert.True(_rm.ValidateOwner(owner, key));
|
|
var holder = _rm.Coordinator.ProbeConflict(key.ToString());
|
Assert.NotNull(holder);
|
long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
Assert.True(holder!.ExpiresUtcMs - nowMs > 60_000,
|
$"ValidateOwner 不应砍短租约,剩余应≈TTL,实际剩余 {(holder.ExpiresUtcMs - nowMs) / 1000}s");
|
|
// 校验后心跳式续期仍应成功(租约未过期)
|
Assert.True(_rm.TryGetGrants(owner.TokenId, out var grants));
|
Assert.True(_rm.Coordinator.Renew(grants![0], 30));
|
}
|
|
[Fact]
|
public void ReleaseReservationAsync_Then_OtherCanReserve()
|
{
|
var a = new ResourceOwnerToken(OwnerType.Maintenance, "A");
|
var b = new ResourceOwnerToken(OwnerType.Production, "B");
|
var key = new ResourceKey("CAN-002", "CAN");
|
_rm.ReserveAsync(a, key, CancellationToken.None).GetAwaiter().GetResult();
|
_rm.ReleaseReservationAsync(a, key, CancellationToken.None).GetAwaiter().GetResult();
|
var ok = _rm.ReserveAsync(b, key, CancellationToken.None).GetAwaiter().GetResult();
|
Assert.Equal(ReservationState.Active, ok.State);
|
}
|
}
|
}
|