322 lines
10 KiB
C#
Executable File
322 lines
10 KiB
C#
Executable File
using MarketAlly.AIPlugin.Analysis.Infrastructure;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Analysis.Tests.Infrastructure
|
|
{
|
|
[TestClass]
|
|
public class ErrorHandlingTests
|
|
{
|
|
private ILogger? _logger;
|
|
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
_logger = null; // In real tests, you might use a mock logger
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task ExecuteWithRetryAsync_SuccessfulOperation_ShouldReturnResult()
|
|
{
|
|
// Arrange
|
|
var expectedResult = "success";
|
|
var callCount = 0;
|
|
|
|
// Act
|
|
var result = await ErrorHandling.ExecuteWithRetryAsync(
|
|
() =>
|
|
{
|
|
callCount++;
|
|
return Task.FromResult(expectedResult);
|
|
},
|
|
maxRetries: 3,
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expectedResult, result);
|
|
Assert.AreEqual(1, callCount);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task ExecuteWithRetryAsync_TransientFailureThenSuccess_ShouldRetryAndSucceed()
|
|
{
|
|
// Arrange
|
|
var expectedResult = "success";
|
|
var callCount = 0;
|
|
|
|
// Act
|
|
var result = await ErrorHandling.ExecuteWithRetryAsync(
|
|
() =>
|
|
{
|
|
callCount++;
|
|
if (callCount < 3)
|
|
throw new IOException("Transient failure");
|
|
return Task.FromResult(expectedResult);
|
|
},
|
|
maxRetries: 3,
|
|
delay: TimeSpan.FromMilliseconds(10),
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expectedResult, result);
|
|
Assert.AreEqual(3, callCount);
|
|
}
|
|
|
|
[TestMethod]
|
|
[ExpectedException(typeof(AggregateException))]
|
|
public async Task ExecuteWithRetryAsync_PersistentFailure_ShouldThrowAggregateException()
|
|
{
|
|
// Arrange
|
|
var callCount = 0;
|
|
|
|
// Act & Assert
|
|
await ErrorHandling.ExecuteWithRetryAsync(
|
|
() =>
|
|
{
|
|
callCount++;
|
|
throw new IOException("Persistent failure");
|
|
},
|
|
maxRetries: 2,
|
|
delay: TimeSpan.FromMilliseconds(10),
|
|
logger: _logger
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
[ExpectedException(typeof(ArgumentException))]
|
|
public async Task ExecuteWithRetryAsync_NonRetryableException_ShouldNotRetry()
|
|
{
|
|
// Arrange
|
|
var callCount = 0;
|
|
|
|
// Act & Assert
|
|
await ErrorHandling.ExecuteWithRetryAsync(
|
|
() =>
|
|
{
|
|
callCount++;
|
|
throw new ArgumentException("Non-retryable failure");
|
|
},
|
|
maxRetries: 3,
|
|
delay: TimeSpan.FromMilliseconds(10),
|
|
logger: _logger
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task ExecuteWithRetryAsync_CancellationRequested_ShouldThrowOperationCancelledException()
|
|
{
|
|
// Arrange
|
|
using var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsExceptionAsync<OperationCanceledException>(async () =>
|
|
{
|
|
await ErrorHandling.ExecuteWithRetryAsync(
|
|
() => Task.FromResult("result"),
|
|
maxRetries: 3,
|
|
logger: _logger,
|
|
cancellationToken: cts.Token
|
|
);
|
|
});
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SafeExecuteAsync_SuccessfulOperation_ShouldReturnSuccessResult()
|
|
{
|
|
// Arrange
|
|
var expectedValue = "success";
|
|
|
|
// Act
|
|
var result = await ErrorHandling.SafeExecuteAsync(
|
|
() => Task.FromResult(expectedValue),
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.IsTrue(result.IsSuccess);
|
|
Assert.AreEqual(expectedValue, result.Value);
|
|
Assert.IsNull(result.Exception);
|
|
Assert.IsNull(result.ErrorMessage);
|
|
Assert.IsTrue(result.Duration > TimeSpan.Zero);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task SafeExecuteAsync_FailedOperation_ShouldReturnFailureResult()
|
|
{
|
|
// Arrange
|
|
var expectedException = new InvalidOperationException("Test error");
|
|
|
|
// Act
|
|
var result = await ErrorHandling.SafeExecuteAsync<string>(
|
|
() => throw expectedException,
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.IsFalse(result.IsSuccess);
|
|
Assert.IsNull(result.Value);
|
|
Assert.IsNotNull(result.Exception);
|
|
Assert.AreEqual(expectedException, result.Exception);
|
|
Assert.AreEqual("Test error", result.ErrorMessage);
|
|
Assert.IsTrue(result.Duration > TimeSpan.Zero);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task WithTimeoutAsync_OperationCompletesInTime_ShouldReturnResult()
|
|
{
|
|
// Arrange
|
|
var expectedResult = "success";
|
|
|
|
// Act
|
|
var result = await ErrorHandling.WithTimeoutAsync(
|
|
async token =>
|
|
{
|
|
await Task.Delay(50, token);
|
|
return expectedResult;
|
|
},
|
|
timeout: TimeSpan.FromSeconds(1),
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual(expectedResult, result);
|
|
}
|
|
|
|
[TestMethod]
|
|
[ExpectedException(typeof(TimeoutException))]
|
|
public async Task WithTimeoutAsync_OperationTimesOut_ShouldThrowTimeoutException()
|
|
{
|
|
// Act & Assert
|
|
await ErrorHandling.WithTimeoutAsync(
|
|
async token =>
|
|
{
|
|
await Task.Delay(1000, token);
|
|
return "result";
|
|
},
|
|
timeout: TimeSpan.FromMilliseconds(100),
|
|
logger: _logger
|
|
);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HandlePluginException_ShouldReturnPluginErrorInfo()
|
|
{
|
|
// Arrange
|
|
var exception = new InvalidOperationException("Plugin error");
|
|
var pluginName = "TestPlugin";
|
|
var operationName = "ExecuteAsync";
|
|
|
|
// Act
|
|
var errorInfo = ErrorHandling.HandlePluginException(
|
|
exception,
|
|
pluginName,
|
|
operationName,
|
|
_logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.IsNotNull(errorInfo);
|
|
Assert.AreEqual(pluginName, errorInfo.PluginName);
|
|
Assert.AreEqual(operationName, errorInfo.OperationName);
|
|
Assert.AreEqual(exception, errorInfo.Exception);
|
|
Assert.AreEqual("General", errorInfo.ErrorType);
|
|
Assert.AreEqual(ErrorSeverity.Medium, errorInfo.Severity);
|
|
Assert.IsTrue(errorInfo.Recoverable);
|
|
Assert.IsTrue(errorInfo.Timestamp <= DateTime.UtcNow);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HandlePluginException_IOError_ShouldClassifyCorrectly()
|
|
{
|
|
// Arrange
|
|
var exception = new IOException("File not accessible");
|
|
var pluginName = "TestPlugin";
|
|
var operationName = "ReadFile";
|
|
|
|
// Act
|
|
var errorInfo = ErrorHandling.HandlePluginException(
|
|
exception,
|
|
pluginName,
|
|
operationName,
|
|
_logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual("IO", errorInfo.ErrorType);
|
|
Assert.AreEqual(ErrorSeverity.Medium, errorInfo.Severity);
|
|
Assert.IsTrue(errorInfo.Recoverable);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HandlePluginException_OutOfMemoryError_ShouldClassifyAsCritical()
|
|
{
|
|
// Arrange
|
|
var exception = new OutOfMemoryException("Out of memory");
|
|
var pluginName = "TestPlugin";
|
|
var operationName = "ProcessLargeFile";
|
|
|
|
// Act
|
|
var errorInfo = ErrorHandling.HandlePluginException(
|
|
exception,
|
|
pluginName,
|
|
operationName,
|
|
_logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Memory", errorInfo.ErrorType);
|
|
Assert.AreEqual(ErrorSeverity.Critical, errorInfo.Severity);
|
|
Assert.IsFalse(errorInfo.Recoverable);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void HandlePluginException_UnauthorizedAccessError_ShouldClassifyAsHighSeverity()
|
|
{
|
|
// Arrange
|
|
var exception = new UnauthorizedAccessException("Access denied");
|
|
var pluginName = "TestPlugin";
|
|
var operationName = "AccessSecureResource";
|
|
|
|
// Act
|
|
var errorInfo = ErrorHandling.HandlePluginException(
|
|
exception,
|
|
pluginName,
|
|
operationName,
|
|
_logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual("Security", errorInfo.ErrorType);
|
|
Assert.AreEqual(ErrorSeverity.High, errorInfo.Severity);
|
|
Assert.IsFalse(errorInfo.Recoverable);
|
|
}
|
|
|
|
[TestMethod]
|
|
public async Task ExecuteWithRetryAsync_NonGeneric_ShouldWork()
|
|
{
|
|
// Arrange
|
|
var callCount = 0;
|
|
|
|
// Act
|
|
await ErrorHandling.ExecuteWithRetryAsync(
|
|
() =>
|
|
{
|
|
callCount++;
|
|
return Task.CompletedTask;
|
|
},
|
|
maxRetries: 3,
|
|
logger: _logger
|
|
);
|
|
|
|
// Assert
|
|
Assert.AreEqual(1, callCount);
|
|
}
|
|
}
|
|
} |