using MarketAlly.AIPlugin.Analysis.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Analysis.Tests.Infrastructure { [TestClass] public class AnalysisContextTests { private AnalysisConfiguration _configuration = null!; private ILogger? _logger; [TestInitialize] public void Setup() { _configuration = new AnalysisConfiguration { MaxConcurrentAnalyses = 2, DefaultTimeout = TimeSpan.FromMinutes(5) }; _logger = null; // In real tests, you might use a mock logger } [TestMethod] public void Constructor_WithValidConfiguration_ShouldInitialize() { // Act using var context = new AnalysisContext(_configuration, _logger); // Assert Assert.IsNotNull(context.Configuration); Assert.AreEqual(_configuration, context.Configuration); Assert.IsNotNull(context.CancellationToken); Assert.IsFalse(context.CancellationToken.IsCancellationRequested); Assert.IsNotNull(context.ConcurrencySemaphore); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void Constructor_WithNullConfiguration_ShouldThrowArgumentNullException() { // Act & Assert using var context = new AnalysisContext(null!, _logger); } [TestMethod] public void Cancel_ShouldSetCancellationTokenToRequested() { // Arrange using var context = new AnalysisContext(_configuration, _logger); // Act context.Cancel(); // Assert Assert.IsTrue(context.CancellationToken.IsCancellationRequested); } [TestMethod] public async Task AcquireConcurrencySlot_ShouldSucceed() { // Arrange using var context = new AnalysisContext(_configuration, _logger); // Act & Assert - Should not throw await context.AcquireConcurrencySlotAsync(); // Cleanup context.ReleaseConcurrencySlot(); } [TestMethod] public async Task ReleaseConcurrencySlot_ShouldSucceed() { // Arrange using var context = new AnalysisContext(_configuration, _logger); // First acquire a slot await context.AcquireConcurrencySlotAsync(); // Act & Assert - Should not throw context.ReleaseConcurrencySlot(); } [TestMethod] public async Task ConcurrencySlot_ShouldLimitConcurrentAccess() { // Arrange - Use a configuration with max concurrency of 1 for clearer testing var restrictiveConfig = new AnalysisConfiguration { MaxConcurrentAnalyses = 1, DefaultTimeout = TimeSpan.FromMinutes(5) }; using var context = new AnalysisContext(restrictiveConfig, _logger); var task1Started = false; var task2Started = false; var task1CanContinue = new TaskCompletionSource(); // Act var task1 = Task.Run(async () => { await context.AcquireConcurrencySlotAsync(); task1Started = true; await task1CanContinue.Task; context.ReleaseConcurrencySlot(); }); var task2 = Task.Run(async () => { // Small delay to ensure task1 starts first await Task.Delay(100); await context.AcquireConcurrencySlotAsync(); task2Started = true; context.ReleaseConcurrencySlot(); }); // Wait for task1 to start and task2 to be blocked await Task.Delay(300); // Assert Assert.IsTrue(task1Started); Assert.IsFalse(task2Started); // Should be blocked by semaphore // Release task1 task1CanContinue.SetResult(true); await Task.WhenAll(task1, task2); Assert.IsTrue(task2Started); } [TestMethod] public void AnalysisContext_WithNullLogger_ShouldInitializeProperly() { // Arrange & Act using var context = new AnalysisContext(_configuration, _logger); // Assert - Logger can be null, that's valid Assert.AreEqual(_logger, context.Logger); // _logger is null in setup Assert.IsNotNull(context.Configuration); Assert.IsNotNull(context.CancellationToken); Assert.IsNotNull(context.ConcurrencySemaphore); } [TestMethod] public void AnalysisContext_BasicFunctionality_ShouldWork() { // Arrange & Act using var context = new AnalysisContext(_configuration, _logger); // Assert - Test core functionality without child contexts Assert.IsNotNull(context.Configuration); Assert.IsNotNull(context.CancellationToken); Assert.IsNotNull(context.ConcurrencySemaphore); Assert.IsFalse(context.CancellationToken.IsCancellationRequested); // Test cancellation works context.Cancel(); Assert.IsTrue(context.CancellationToken.IsCancellationRequested); } [TestMethod] public async Task AcquireConcurrencySlotAsync_AfterDispose_ShouldThrowObjectDisposedException() { // Arrange var context = new AnalysisContext(_configuration, _logger); context.Dispose(); // Act & Assert await Assert.ThrowsExceptionAsync(async () => { await context.AcquireConcurrencySlotAsync(); }); } [TestMethod] [ExpectedException(typeof(ObjectDisposedException))] public void Cancel_AfterDispose_ShouldThrowObjectDisposedException() { // Arrange var context = new AnalysisContext(_configuration, _logger); context.Dispose(); // Act & Assert context.Cancel(); } [TestMethod] public void Dispose_ShouldNotThrow() { // Arrange var context = new AnalysisContext(_configuration, _logger); // Act & Assert - Should not throw context.Dispose(); // Multiple dispose calls should not throw context.Dispose(); } } }