using System.Collections.Concurrent; using System.Reflection; using adas_core.Application.Services.Caching; namespace adas_core.Test.Services; [TestFixture] public class InMemoryLockProviderTest { private InMemoryLockProvider _provider = null!; [SetUp] public void SetUp() { _provider = new InMemoryLockProvider(); } private ConcurrentDictionary GetLocks() { var field = typeof(InMemoryLockProvider) .GetField("_locks", BindingFlags.NonPublic | BindingFlags.Instance); return (ConcurrentDictionary)field!.GetValue(_provider)!; } #region TC-50 [Test] public async Task AcquireAsync_ReturnsTrue_AndCreatesEntryInLocksDictionary() { var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1)); Assert.That(result, Is.True); var locks = GetLocks(); Assert.That(locks.ContainsKey("key"), Is.True); } #endregion #region TC-51 [Test] public async Task AcquireAsync_ReturnsFalse_WhenSemaphoreOccupiedAndTimeoutExpires() { await _provider.AcquireAsync("key", TimeSpan.FromSeconds(5)); var result = await _provider.AcquireAsync("key", TimeSpan.FromMilliseconds(100)); Assert.That(result, Is.False); await _provider.ReleaseAsync("key"); var locks = GetLocks(); Assert.That(locks["key"].CurrentCount, Is.EqualTo(1)); } #endregion #region TC-52 [Test] public async Task ReleaseAsync_MakesSemaphoreAvailableForNextAcquire() { await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1)); await _provider.ReleaseAsync("key"); var result = await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1)); Assert.That(result, Is.True); var locks = GetLocks(); Assert.That(locks["key"].CurrentCount, Is.EqualTo(0)); await _provider.ReleaseAsync("key"); Assert.That(locks["key"].CurrentCount, Is.EqualTo(1)); } #endregion #region TC-53 [Test] public async Task ReleaseAsync_DoesNotThrow_WhenCalledWithoutPriorAcquire() { await _provider.ReleaseAsync("nonexistent-key"); await _provider.AcquireAsync("key", TimeSpan.FromSeconds(1)); await _provider.ReleaseAsync("key"); await _provider.ReleaseAsync("key"); } #endregion #region TC-54 [Test] public async Task AcquireAsync_IsThreadSafe_OnlyOneHolderAtATime_SingleSemaphorePerKey() { const string key = "same-key"; const int threadCount = 10; var acquiredCount = 0; var currentHolders = 0; var tasks = Enumerable.Range(0, threadCount).Select(_ => Task.Run(async () => { var acquired = await _provider.AcquireAsync(key, TimeSpan.FromSeconds(10)); Assert.That(acquired, Is.True); var current = Interlocked.Increment(ref currentHolders); Assert.That(current, Is.EqualTo(1)); Interlocked.Increment(ref acquiredCount); await Task.Delay(5); Interlocked.Decrement(ref currentHolders); await _provider.ReleaseAsync(key); })).ToArray(); await Task.WhenAll(tasks); Assert.That(acquiredCount, Is.EqualTo(threadCount)); var locks = GetLocks(); Assert.That(locks.ContainsKey(key), Is.True); Assert.That(locks.Keys.Count(k => k == key), Is.EqualTo(1)); } #endregion }