using ChatBot.Services.Telegram.Interfaces; using ChatBot.Services.Telegram.Services; using ChatBot.Tests.TestUtilities; using Microsoft.Extensions.Logging; using Moq; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Types; namespace ChatBot.Tests.Services.Telegram; public class TelegramMessageSenderTests : UnitTestBase { private readonly Mock> _loggerMock; private readonly Mock _botClientMock; private readonly Mock _messageSenderWrapperMock; private readonly TelegramMessageSender _messageSender; public TelegramMessageSenderTests() { _loggerMock = new Mock>(); _botClientMock = new Mock(); _messageSenderWrapperMock = new Mock(); _messageSender = new TelegramMessageSender( _loggerMock.Object, _messageSenderWrapperMock.Object ); } [Fact] public void Constructor_ShouldCreateInstance() { // Act & Assert Assert.NotNull(_messageSender); } [Fact] public async Task SendMessageWithRetry_ShouldSendMessageSuccessfully() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithCustomMaxRetries_ShouldUseCustomValue() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var maxRetries = 5; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken, maxRetries ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithRateLimitError_ShouldRetryAndSucceed() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; var rateLimitException = new ApiRequestException("Rate limit exceeded", 429); _messageSenderWrapperMock .SetupSequence(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ThrowsAsync(rateLimitException) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Exactly(2) ); _loggerMock.Verify( x => x.Log( LogLevel.Warning, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Rate limit exceeded (429)") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithRateLimitErrorMaxRetries_ShouldThrowException() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var maxRetries = 2; var cancellationToken = CancellationToken.None; var rateLimitException = new ApiRequestException("Rate limit exceeded", 429); _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ThrowsAsync(rateLimitException); // Act & Assert var exception = await Assert.ThrowsAsync(() => _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken, maxRetries ) ); Assert.Contains( "Failed to send message after 2 attempts due to rate limiting", exception.Message ); _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Exactly(maxRetries) ); _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains( "Failed to send message after 2 attempts due to rate limiting" ) ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithGenericException_ShouldThrowInvalidOperationException() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; var originalException = new HttpRequestException("Network error"); _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ThrowsAsync(originalException); // Act & Assert var exception = await Assert.ThrowsAsync(() => _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ) ); Assert.Contains("Failed to send message to chat 12345 after 1 attempts", exception.Message); Assert.Equal(originalException, exception.InnerException); _loggerMock.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains( "Unexpected error sending message to chat 12345 on attempt 1" ) ), originalException, It.IsAny>() ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithApiRequestExceptionNon429_ShouldThrowInvalidOperationException() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; var apiException = new ApiRequestException("Bad Request", 400); _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ThrowsAsync(apiException); // Act & Assert var exception = await Assert.ThrowsAsync(() => _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ) ); Assert.Contains("Failed to send message to chat 12345 after 1 attempts", exception.Message); Assert.Equal(apiException, exception.InnerException); } [Fact] public async Task SendMessageWithRetry_WithCancelledToken_ShouldHandleCancellation() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; using var cancellationTokenSource = new CancellationTokenSource(); await cancellationTokenSource.CancelAsync(); var cancelledToken = cancellationTokenSource.Token; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancelledToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancelledToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithEmptyText_ShouldSendEmptyMessage() { // Arrange var chatId = 12345L; var text = ""; var replyToMessageId = 0; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithLongText_ShouldSendLongMessage() { // Arrange var chatId = 12345L; var text = new string('A', 4096); // Very long message var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithSpecialCharacters_ShouldSendMessageWithSpecialChars() { // Arrange var chatId = 12345L; var text = "Test message with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?`~"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithUnicodeText_ShouldSendUnicodeMessage() { // Arrange var chatId = 12345L; var text = "Test message with unicode: ๐Ÿš€ Hello ไธ–็•Œ ๐ŸŒ ะŸั€ะธะฒะตั‚"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Theory] [InlineData(1)] [InlineData(2)] [InlineData(3)] [InlineData(5)] [InlineData(10)] public async Task SendMessageWithRetry_WithDifferentMaxRetries_ShouldRespectMaxRetries( int maxRetries ) { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken, maxRetries ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } [Fact] public async Task SendMessageWithRetry_WithNegativeMaxRetries_ShouldUseDefaultValue() { // Arrange var chatId = 12345L; var text = "Test message"; var replyToMessageId = 67890; var maxRetries = -1; // Invalid value var cancellationToken = CancellationToken.None; _messageSenderWrapperMock .Setup(x => x.SendMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ) ) .ReturnsAsync(new Message()); // Act await _messageSender.SendMessageWithRetry( _botClientMock.Object, chatId, text, replyToMessageId, cancellationToken, maxRetries ); // Assert _messageSenderWrapperMock.Verify( x => x.SendMessageAsync( It.Is(id => id == chatId), It.Is(t => t == text), It.Is(r => r == replyToMessageId), It.Is(ct => ct == cancellationToken) ), Times.Once ); } }