using ChatBot.Services.Telegram.Services; using ChatBot.Tests.TestUtilities; using Microsoft.Extensions.Logging; using Moq; using Telegram.Bot; using Telegram.Bot.Exceptions; namespace ChatBot.Tests.Services.Telegram; public class TelegramErrorHandlerTests : UnitTestBase { private readonly Mock> _loggerMock; private readonly Mock _botClientMock; private readonly TelegramErrorHandler _errorHandler; public TelegramErrorHandlerTests() { _loggerMock = new Mock>(); _botClientMock = new Mock(); _errorHandler = new TelegramErrorHandler(_loggerMock.Object); } [Fact] public void Constructor_ShouldCreateInstance() { // Act & Assert Assert.NotNull(_errorHandler); } [Fact] public async Task HandlePollingErrorAsync_WithApiRequestException_ShouldLogErrorWithFormattedMessage() { // Arrange var errorCode = 400; var errorMessage = "Bad Request: chat not found"; var apiException = new ApiRequestException(errorMessage, errorCode); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, apiException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains($"Telegram API Error:\n[{errorCode}]\n{errorMessage}") ), apiException, It.IsAny>() ), Times.Once ); } [Theory] [InlineData(400, "Bad Request")] [InlineData(401, "Unauthorized")] [InlineData(403, "Forbidden")] [InlineData(404, "Not Found")] [InlineData(429, "Too Many Requests")] [InlineData(500, "Internal Server Error")] public async Task HandlePollingErrorAsync_WithDifferentApiErrorCodes_ShouldLogCorrectFormat( int errorCode, string errorMessage ) { // Arrange var apiException = new ApiRequestException(errorMessage, errorCode); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, apiException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains($"Telegram API Error:\n[{errorCode}]\n{errorMessage}") ), apiException, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_WithGenericException_ShouldLogExceptionToString() { // Arrange var genericException = new InvalidOperationException("Something went wrong"); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, genericException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("System.InvalidOperationException: Something went wrong") ), genericException, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_WithTimeoutException_ShouldLogTimeoutException() { // Arrange var timeoutException = new TimeoutException("Request timed out"); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, timeoutException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("System.TimeoutException: Request timed out") ), timeoutException, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_WithHttpRequestException_ShouldLogHttpRequestException() { // Arrange var httpException = new HttpRequestException("Network error occurred"); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, httpException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains( "System.Net.Http.HttpRequestException: Network error occurred" ) ), httpException, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_WithCancelledToken_ShouldCompleteSuccessfully() { // Arrange var exception = new InvalidOperationException("Test exception"); using var cancellationTokenSource = new CancellationTokenSource(); await cancellationTokenSource.CancelAsync(); var cancelledToken = cancellationTokenSource.Token; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, exception, cancelledToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.IsAny(), exception, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_ShouldReturnCompletedTask() { // Arrange var exception = new Exception("Test exception"); var cancellationToken = CancellationToken.None; // Act var task = _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, exception, cancellationToken ); // Assert Assert.True(task.IsCompleted); await task; // Ensure no exceptions are thrown } [Fact] public async Task HandlePollingErrorAsync_WithNestedException_ShouldLogOuterException() { // Arrange var innerException = new ArgumentException("Inner exception"); var outerException = new InvalidOperationException("Outer exception", innerException); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, outerException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("System.InvalidOperationException: Outer exception") ), outerException, It.IsAny>() ), Times.Once ); } [Fact] public async Task HandlePollingErrorAsync_WithAggregateException_ShouldLogAggregateException() { // Arrange var exceptions = new Exception[] { new InvalidOperationException("First exception"), new ArgumentException("Second exception"), }; var aggregateException = new AggregateException("Multiple exceptions occurred", exceptions); var cancellationToken = CancellationToken.None; // Act await _errorHandler.HandlePollingErrorAsync( _botClientMock.Object, aggregateException, cancellationToken ); // Assert _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("System.AggregateException: Multiple exceptions occurred") ), aggregateException, It.IsAny>() ), Times.Once ); } }