wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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);
        }
    }
}