203 lines
6.7 KiB
C#
Executable File
203 lines
6.7 KiB
C#
Executable File
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<bool>();
|
|
|
|
// 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<ObjectDisposedException>(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();
|
|
}
|
|
}
|
|
} |