namespace ScadaLink.DeploymentManager.Tests; /// /// WP-3: Tests for per-instance operation lock. /// public class OperationLockManagerTests { private readonly OperationLockManager _lockManager = new(); [Fact] public async Task AcquireAsync_ReturnsDisposable() { using var lockHandle = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); Assert.NotNull(lockHandle); } [Fact] public async Task AcquireAsync_SameInstance_BlocksSecondCaller() { using var firstLock = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); // Second acquire should time out await Assert.ThrowsAsync(() => _lockManager.AcquireAsync("inst1", TimeSpan.FromMilliseconds(50))); } [Fact] public async Task AcquireAsync_DifferentInstances_BothSucceed() { using var lock1 = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); using var lock2 = await _lockManager.AcquireAsync("inst2", TimeSpan.FromSeconds(5)); Assert.NotNull(lock1); Assert.NotNull(lock2); } [Fact] public async Task AcquireAsync_AfterRelease_CanReacquire() { var firstLock = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); firstLock.Dispose(); // Should succeed now using var secondLock = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); Assert.NotNull(secondLock); } [Fact] public async Task IsLocked_ReturnsTrueWhileLocked() { Assert.False(_lockManager.IsLocked("inst1")); using var lockHandle = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); Assert.True(_lockManager.IsLocked("inst1")); } [Fact] public async Task IsLocked_ReturnsFalseAfterRelease() { var lockHandle = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); lockHandle.Dispose(); Assert.False(_lockManager.IsLocked("inst1")); } [Fact] public async Task AcquireAsync_DoubleDispose_DoesNotThrow() { var lockHandle = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(5)); lockHandle.Dispose(); lockHandle.Dispose(); // Should not throw } [Fact] public async Task AcquireAsync_CancellationToken_Respected() { using var firstLock = await _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(30)); using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50)); await Assert.ThrowsAnyAsync(() => _lockManager.AcquireAsync("inst1", TimeSpan.FromSeconds(30), cts.Token)); } [Fact] public async Task AcquireAsync_ConcurrentDifferentInstances_AllSucceed() { var tasks = Enumerable.Range(0, 10).Select(async i => { using var lockHandle = await _lockManager.AcquireAsync($"inst{i}", TimeSpan.FromSeconds(5)); await Task.Delay(10); }); await Task.WhenAll(tasks); } }