Add more tests
This commit is contained in:
318
ChatBot.Tests/Services/Telegram/TelegramErrorHandlerTests.cs
Normal file
318
ChatBot.Tests/Services/Telegram/TelegramErrorHandlerTests.cs
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
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<ILogger<TelegramErrorHandler>> _loggerMock;
|
||||||
|
private readonly Mock<ITelegramBotClient> _botClientMock;
|
||||||
|
private readonly TelegramErrorHandler _errorHandler;
|
||||||
|
|
||||||
|
public TelegramErrorHandlerTests()
|
||||||
|
{
|
||||||
|
_loggerMock = new Mock<ILogger<TelegramErrorHandler>>();
|
||||||
|
_botClientMock = new Mock<ITelegramBotClient>();
|
||||||
|
_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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains($"Telegram API Error:\n[{errorCode}]\n{errorMessage}")
|
||||||
|
),
|
||||||
|
apiException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains($"Telegram API Error:\n[{errorCode}]\n{errorMessage}")
|
||||||
|
),
|
||||||
|
apiException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains("System.InvalidOperationException: Something went wrong")
|
||||||
|
),
|
||||||
|
genericException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!.Contains("System.TimeoutException: Request timed out")
|
||||||
|
),
|
||||||
|
timeoutException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
"System.Net.Http.HttpRequestException: Network error occurred"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
httpException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.IsAny<It.IsAnyType>(),
|
||||||
|
exception,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandlePollingErrorAsync_ShouldReturnCompletedTask()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var exception = new Exception("Test exception");
|
||||||
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _errorHandler.HandlePollingErrorAsync(
|
||||||
|
_botClientMock.Object,
|
||||||
|
exception,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains("System.InvalidOperationException: Outer exception")
|
||||||
|
),
|
||||||
|
outerException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains("System.AggregateException: Multiple exceptions occurred")
|
||||||
|
),
|
||||||
|
aggregateException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
832
ChatBot.Tests/Services/Telegram/TelegramMessageHandlerTests.cs
Normal file
832
ChatBot.Tests/Services/Telegram/TelegramMessageHandlerTests.cs
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
|
using ChatBot.Services.Telegram.Services;
|
||||||
|
using ChatBot.Tests.TestUtilities;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moq;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ChatBot.Tests.Services.Telegram;
|
||||||
|
|
||||||
|
public class TelegramMessageHandlerTests : UnitTestBase
|
||||||
|
{
|
||||||
|
private readonly Mock<ILogger<TelegramMessageHandler>> _loggerMock;
|
||||||
|
private readonly Mock<ITelegramCommandProcessor> _commandProcessorMock;
|
||||||
|
private readonly Mock<ITelegramMessageSender> _messageSenderMock;
|
||||||
|
private readonly Mock<ITelegramBotClient> _botClientMock;
|
||||||
|
private readonly TelegramMessageHandler _handler;
|
||||||
|
|
||||||
|
public TelegramMessageHandlerTests()
|
||||||
|
{
|
||||||
|
_loggerMock = TestDataBuilder.Mocks.CreateLoggerMock<TelegramMessageHandler>();
|
||||||
|
_commandProcessorMock = new Mock<ITelegramCommandProcessor>();
|
||||||
|
_messageSenderMock = new Mock<ITelegramMessageSender>();
|
||||||
|
_botClientMock = TestDataBuilder.Mocks.CreateTelegramBotClient();
|
||||||
|
|
||||||
|
_handler = new TelegramMessageHandler(
|
||||||
|
_loggerMock.Object,
|
||||||
|
_commandProcessorMock.Object,
|
||||||
|
_messageSenderMock.Object
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Message CreateMessage(
|
||||||
|
string text,
|
||||||
|
long chatId,
|
||||||
|
string username,
|
||||||
|
string chatTitle,
|
||||||
|
int messageId,
|
||||||
|
User? from = null,
|
||||||
|
Message? replyToMessage = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var message = new Message
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
Chat = new Chat
|
||||||
|
{
|
||||||
|
Id = chatId,
|
||||||
|
Type = ChatType.Private,
|
||||||
|
Title = chatTitle,
|
||||||
|
},
|
||||||
|
From = from ?? new User { Id = 67890, Username = username },
|
||||||
|
ReplyToMessage = replyToMessage,
|
||||||
|
};
|
||||||
|
// Note: MessageId is read-only, so we can't set it directly
|
||||||
|
// The actual MessageId will be 0, but this is sufficient for testing
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldInitializeCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = TestDataBuilder.Mocks.CreateLoggerMock<TelegramMessageHandler>().Object;
|
||||||
|
var commandProcessor = new Mock<ITelegramCommandProcessor>().Object;
|
||||||
|
var messageSender = new Mock<ITelegramMessageSender>().Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var handler = new TelegramMessageHandler(logger, commandProcessor, messageSender);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
handler.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldNotThrow_WhenLoggerIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
ILogger<TelegramMessageHandler>? logger = null;
|
||||||
|
var commandProcessor = new Mock<ITelegramCommandProcessor>().Object;
|
||||||
|
var messageSender = new Mock<ITelegramMessageSender>().Object;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var act = () => new TelegramMessageHandler(logger!, commandProcessor, messageSender);
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldNotThrow_WhenCommandProcessorIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = TestDataBuilder.Mocks.CreateLoggerMock<TelegramMessageHandler>().Object;
|
||||||
|
ITelegramCommandProcessor? commandProcessor = null;
|
||||||
|
var messageSender = new Mock<ITelegramMessageSender>().Object;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var act = () => new TelegramMessageHandler(logger, commandProcessor!, messageSender);
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldNotThrow_WhenMessageSenderIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var logger = TestDataBuilder.Mocks.CreateLoggerMock<TelegramMessageHandler>().Object;
|
||||||
|
var commandProcessor = new Mock<ITelegramCommandProcessor>().Object;
|
||||||
|
ITelegramMessageSender? messageSender = null;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var act = () => new TelegramMessageHandler(logger, commandProcessor, messageSender!);
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldReturnEarly_WhenUpdateMessageIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var update = new Update { Message = null };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
It.IsAny<ITelegramBotClient>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>(),
|
||||||
|
It.IsAny<int>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldReturnEarly_WhenMessageTextIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var message = CreateMessage(null!, 12345, "testuser", "Test Chat", 1);
|
||||||
|
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
It.IsAny<ITelegramBotClient>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>(),
|
||||||
|
It.IsAny<int>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldProcessMessage_WhenValidMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
var messageId = 1;
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, messageId);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
var expectedResponse = "Hello! How can I help you?";
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
null,
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(expectedResponse);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
null,
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
expectedResponse,
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>(),
|
||||||
|
3
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldNotSendMessage_WhenResponseIsEmpty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(string.Empty);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
It.IsAny<ITelegramBotClient>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>(),
|
||||||
|
It.IsAny<int>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldNotSendMessage_WhenResponseIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync((string)null!);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
It.IsAny<ITelegramBotClient>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>(),
|
||||||
|
It.IsAny<int>()
|
||||||
|
),
|
||||||
|
Times.Never
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldUseUsername_WhenFromHasUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync("Response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
null,
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldUseFirstName_WhenFromHasNoUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var firstName = "TestUser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var from = new User
|
||||||
|
{
|
||||||
|
Id = 67890,
|
||||||
|
Username = null,
|
||||||
|
FirstName = firstName,
|
||||||
|
};
|
||||||
|
var message = CreateMessage(messageText, chatId, firstName, chatTitle, 1, from);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync("Response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
firstName,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
null,
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldUseUnknown_WhenFromIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, "Unknown", chatTitle, 1, null);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync("Response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
"Unknown",
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
null,
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldHandleReplyMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
var messageId = 1;
|
||||||
|
var replyToMessageId = 2;
|
||||||
|
var replyToUserId = 67890L;
|
||||||
|
var replyToUsername = "originaluser";
|
||||||
|
|
||||||
|
var replyToMessage = CreateMessage(
|
||||||
|
"Original message",
|
||||||
|
chatId,
|
||||||
|
replyToUsername,
|
||||||
|
chatTitle,
|
||||||
|
replyToMessageId
|
||||||
|
);
|
||||||
|
var message = CreateMessage(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
chatTitle,
|
||||||
|
messageId,
|
||||||
|
null,
|
||||||
|
replyToMessage
|
||||||
|
);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
var expectedResponse = "Response to reply";
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(expectedResponse);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
It.Is<ReplyInfo>(r =>
|
||||||
|
r.UserId == replyToUserId && r.Username == replyToUsername
|
||||||
|
),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldPassCancellationToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
var cancellationToken = new CancellationToken();
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
var expectedResponse = "Response";
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(expectedResponse);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, cancellationToken);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_commandProcessorMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
cancellationToken
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
_messageSenderMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageWithRetry(
|
||||||
|
It.IsAny<ITelegramBotClient>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
cancellationToken,
|
||||||
|
It.IsAny<int>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldLogError_WhenExceptionOccurs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var chatType = "Private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, chatTitle, 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
var exception = new Exception("Test exception");
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ThrowsAsync(exception);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Error,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) => v.ToString()!.Contains("Error handling update from chat")
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldLogInformation_WhenMessageReceived()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, "Test Chat", 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync("Response");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Information,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
$"Message from @{username} in chat {chatId}: \"{messageText}\""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldLogInformation_WhenResponseSent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var response = "Hello! How can I help you?";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, "Test Chat", 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(response);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Information,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
$"Response sent to @{username} in chat {chatId}: \"{response}\""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandleUpdateAsync_ShouldLogInformation_WhenNoResponseSent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
var message = CreateMessage(messageText, chatId, username, "Test Chat", 1);
|
||||||
|
var update = new Update { Message = message };
|
||||||
|
|
||||||
|
_commandProcessorMock
|
||||||
|
.Setup(x =>
|
||||||
|
x.ProcessMessageAsync(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<ReplyInfo?>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(string.Empty);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _handler.HandleUpdateAsync(_botClientMock.Object, update, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Information,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
$"No response sent to @{username} in chat {chatId} (AI chose to ignore message)"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
647
ChatBot.Tests/Services/Telegram/TelegramMessageSenderTests.cs
Normal file
647
ChatBot.Tests/Services/Telegram/TelegramMessageSenderTests.cs
Normal file
@@ -0,0 +1,647 @@
|
|||||||
|
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<ILogger<TelegramMessageSender>> _loggerMock;
|
||||||
|
private readonly Mock<ITelegramBotClient> _botClientMock;
|
||||||
|
private readonly Mock<ITelegramMessageSenderWrapper> _messageSenderWrapperMock;
|
||||||
|
private readonly TelegramMessageSender _messageSender;
|
||||||
|
|
||||||
|
public TelegramMessageSenderTests()
|
||||||
|
{
|
||||||
|
_loggerMock = new Mock<ILogger<TelegramMessageSender>>();
|
||||||
|
_botClientMock = new Mock<ITelegramBotClient>();
|
||||||
|
_messageSenderWrapperMock = new Mock<ITelegramMessageSenderWrapper>();
|
||||||
|
_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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken,
|
||||||
|
maxRetries
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ThrowsAsync(rateLimitException)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(ct => ct == cancellationToken)
|
||||||
|
),
|
||||||
|
Times.Exactly(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Warning,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) => v.ToString()!.Contains("Rate limit exceeded (429)")
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ThrowsAsync(rateLimitException);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
_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<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(ct => ct == cancellationToken)
|
||||||
|
),
|
||||||
|
Times.Exactly(maxRetries)
|
||||||
|
);
|
||||||
|
|
||||||
|
_loggerMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.Log(
|
||||||
|
LogLevel.Error,
|
||||||
|
It.IsAny<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
"Failed to send message after 2 attempts due to rate limiting"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
It.IsAny<Exception>(),
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ThrowsAsync(originalException);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
_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<EventId>(),
|
||||||
|
It.Is<It.IsAnyType>(
|
||||||
|
(v, t) =>
|
||||||
|
v.ToString()!
|
||||||
|
.Contains(
|
||||||
|
"Unexpected error sending message to chat 12345 on attempt 1"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
originalException,
|
||||||
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||||
|
),
|
||||||
|
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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ThrowsAsync(apiException);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
_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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancelledToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken,
|
||||||
|
maxRetries
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(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<long>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<CancellationToken>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.ReturnsAsync(new Message());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
_botClientMock.Object,
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
replyToMessageId,
|
||||||
|
cancellationToken,
|
||||||
|
maxRetries
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_messageSenderWrapperMock.Verify(
|
||||||
|
x =>
|
||||||
|
x.SendMessageAsync(
|
||||||
|
It.Is<long>(id => id == chatId),
|
||||||
|
It.Is<string>(t => t == text),
|
||||||
|
It.Is<int>(r => r == replyToMessageId),
|
||||||
|
It.Is<CancellationToken>(ct => ct == cancellationToken)
|
||||||
|
),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using ChatBot.Services.Telegram.Services;
|
using ChatBot.Services.Telegram.Services;
|
||||||
using ChatBot.Tests.TestUtilities;
|
using ChatBot.Tests.TestUtilities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -69,7 +70,8 @@ public class TelegramServicesTests : UnitTestBase
|
|||||||
var loggerMock = new Mock<ILogger<TelegramMessageSender>>();
|
var loggerMock = new Mock<ILogger<TelegramMessageSender>>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var sender = new TelegramMessageSender(loggerMock.Object);
|
var messageSenderWrapperMock = new Mock<ITelegramMessageSenderWrapper>();
|
||||||
|
var sender = new TelegramMessageSender(loggerMock.Object, messageSenderWrapperMock.Object);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(sender);
|
Assert.NotNull(sender);
|
||||||
|
|||||||
343
ChatBot.Tests/Services/TelegramBotClientWrapperTests.cs
Normal file
343
ChatBot.Tests/Services/TelegramBotClientWrapperTests.cs
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
using ChatBot.Services;
|
||||||
|
using ChatBot.Services.Interfaces;
|
||||||
|
using ChatBot.Tests.TestUtilities;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ChatBot.Tests.Services;
|
||||||
|
|
||||||
|
public class TelegramBotClientWrapperTests : UnitTestBase
|
||||||
|
{
|
||||||
|
private readonly Mock<ITelegramBotClient> _botClientMock;
|
||||||
|
private readonly TelegramBotClientWrapper _wrapper;
|
||||||
|
|
||||||
|
public TelegramBotClientWrapperTests()
|
||||||
|
{
|
||||||
|
_botClientMock = TestDataBuilder.Mocks.CreateTelegramBotClient();
|
||||||
|
_wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldInitializeCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var botClient = TestDataBuilder.Mocks.CreateTelegramBotClient().Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var wrapper = new TelegramBotClientWrapper(botClient);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
wrapper.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_ShouldNotThrow_WhenBotClientIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
ITelegramBotClient? botClient = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var act = () => new TelegramBotClientWrapper(botClient!);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Note: The constructor doesn't validate null input
|
||||||
|
act.Should().NotThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldImplementITelegramBotClientWrapper()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
wrapper.Should().BeAssignableTo<ITelegramBotClientWrapper>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveGetMeAsyncMethod()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var method = typeof(TelegramBotClientWrapper).GetMethod("GetMeAsync");
|
||||||
|
method.Should().NotBeNull();
|
||||||
|
method!.ReturnType.Should().Be(typeof(Task<User>));
|
||||||
|
|
||||||
|
var parameters = method.GetParameters();
|
||||||
|
parameters.Should().HaveCount(1);
|
||||||
|
parameters[0].ParameterType.Should().Be(typeof(CancellationToken));
|
||||||
|
parameters[0].HasDefaultValue.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldNotBeDisposable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
// TelegramBotClientWrapper doesn't implement IDisposable
|
||||||
|
wrapper.Should().NotBeAssignableTo<IDisposable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectNamespace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().Namespace.Should().Be("ChatBot.Services");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectAssembly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().Assembly.GetName().Name.Should().Be("ChatBot");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldNotBeSealed()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().IsSealed.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldNotBeAbstract()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().IsAbstract.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldBePublic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().IsPublic.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveSingleConstructor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var constructors = typeof(TelegramBotClientWrapper).GetConstructors();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
constructors.Should().HaveCount(1);
|
||||||
|
|
||||||
|
var constructor = constructors[0];
|
||||||
|
var parameters = constructor.GetParameters();
|
||||||
|
parameters.Should().HaveCount(1);
|
||||||
|
parameters[0].ParameterType.Should().Be(typeof(ITelegramBotClient));
|
||||||
|
parameters[0].Name.Should().Be("botClient");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectBaseType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().BaseType.Should().Be(typeof(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldImplementCorrectInterface()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var interfaces = wrapper.GetType().GetInterfaces();
|
||||||
|
interfaces.Should().Contain(typeof(ITelegramBotClientWrapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectInterfaceMethods()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var interfaceType = typeof(ITelegramBotClientWrapper);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var interfaceMethods = interfaceType.GetMethods();
|
||||||
|
var wrapperMethods = wrapper
|
||||||
|
.GetType()
|
||||||
|
.GetMethods()
|
||||||
|
.Where(m => m.DeclaringType == wrapper.GetType())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
interfaceMethods.Should().HaveCount(1);
|
||||||
|
interfaceMethods[0].Name.Should().Be("GetMeAsync");
|
||||||
|
interfaceMethods[0].ReturnType.Should().Be(typeof(Task<User>));
|
||||||
|
|
||||||
|
var parameters = interfaceMethods[0].GetParameters();
|
||||||
|
parameters.Should().HaveCount(1);
|
||||||
|
parameters[0].ParameterType.Should().Be(typeof(CancellationToken));
|
||||||
|
parameters[0].HasDefaultValue.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectGenericTypeArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().IsGenericType.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectTypeName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().Name.Should().Be("TelegramBotClientWrapper");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectFullName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper.GetType().FullName.Should().Be("ChatBot.Services.TelegramBotClientWrapper");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldBeInstantiable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var botClient = TestDataBuilder.Mocks.CreateTelegramBotClient().Object;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var wrapper = new TelegramBotClientWrapper(botClient);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
wrapper.Should().NotBeNull();
|
||||||
|
wrapper.Should().BeOfType<TelegramBotClientWrapper>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectHashCode()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper1 = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var wrapper2 = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var hash1 = wrapper1.GetHashCode();
|
||||||
|
var hash2 = wrapper2.GetHashCode();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
hash1.Should().NotBe(hash2); // Different instances should have different hash codes
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectToString()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var toString = wrapper.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
toString.Should().NotBeNull();
|
||||||
|
toString.Should().Contain("TelegramBotClientWrapper");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldBeEqualOnlyToItself()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper1 = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var wrapper2 = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
wrapper1.Should().NotBe(wrapper2);
|
||||||
|
wrapper1.Should().Be(wrapper1);
|
||||||
|
wrapper1.Equals(wrapper1).Should().BeTrue();
|
||||||
|
wrapper1.Equals(wrapper2).Should().BeFalse();
|
||||||
|
wrapper1.Equals(null).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectTypeAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var attributes = wrapper.GetType().GetCustomAttributes(false);
|
||||||
|
attributes.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectMethodAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var getMeMethod = wrapper.GetType().GetMethod("GetMeAsync");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
getMeMethod.Should().NotBeNull();
|
||||||
|
var attributes = getMeMethod!.GetCustomAttributes(false);
|
||||||
|
attributes.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectParameterAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var getMeMethod = wrapper.GetType().GetMethod("GetMeAsync");
|
||||||
|
var parameters = getMeMethod!.GetParameters();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
parameters.Should().HaveCount(1);
|
||||||
|
var parameter = parameters[0];
|
||||||
|
var attributes = parameter.GetCustomAttributes(false);
|
||||||
|
attributes.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Wrapper_ShouldHaveCorrectReturnTypeAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var wrapper = new TelegramBotClientWrapper(_botClientMock.Object);
|
||||||
|
var getMeMethod = wrapper.GetType().GetMethod("GetMeAsync");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
getMeMethod.Should().NotBeNull();
|
||||||
|
var returnType = getMeMethod!.ReturnType;
|
||||||
|
returnType.Should().Be(typeof(Task<User>));
|
||||||
|
|
||||||
|
var attributes = returnType.GetCustomAttributes(false);
|
||||||
|
attributes.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
446
ChatBot.Tests/Telegram/Commands/CommandAttributeTests.cs
Normal file
446
ChatBot.Tests/Telegram/Commands/CommandAttributeTests.cs
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ChatBot.Tests.Telegram.Commands;
|
||||||
|
|
||||||
|
public class CommandAttributeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHaveCorrectAttributeUsage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var attributeType = typeof(CommandAttribute);
|
||||||
|
var attributeUsage = attributeType
|
||||||
|
.GetCustomAttributes(typeof(AttributeUsageAttribute), false)
|
||||||
|
.Cast<AttributeUsageAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attributeUsage.Should().NotBeNull();
|
||||||
|
attributeUsage!.ValidOn.Should().Be(AttributeTargets.Class);
|
||||||
|
attributeUsage.AllowMultiple.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldSetCommandNameAndDescription()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
attribute.Priority.Should().Be(0); // Default value
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldAllowSettingPriority()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
var priority = 5;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description) { Priority = priority };
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
attribute.Priority.Should().Be(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/start", "Start the bot")]
|
||||||
|
[InlineData("/help", "Show help")]
|
||||||
|
[InlineData("/status", "Show status")]
|
||||||
|
[InlineData("/clear", "Clear chat history")]
|
||||||
|
[InlineData("/settings", "Show settings")]
|
||||||
|
public void CommandAttribute_ShouldAcceptValidCommandNames(
|
||||||
|
string commandName,
|
||||||
|
string description
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("start")]
|
||||||
|
[InlineData("help")]
|
||||||
|
[InlineData("status")]
|
||||||
|
[InlineData("clear")]
|
||||||
|
[InlineData("settings")]
|
||||||
|
public void CommandAttribute_ShouldAcceptCommandNamesWithoutSlash(string commandName)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("A simple description")]
|
||||||
|
[InlineData(
|
||||||
|
"A very long description that contains multiple words and explains what the command does in detail"
|
||||||
|
)]
|
||||||
|
[InlineData("Описание на русском языке")]
|
||||||
|
[InlineData("Description with special characters: !@#$%^&*()_+-=[]{}|;':\",./<>?")]
|
||||||
|
[InlineData("Description with unicode: 用户 ユーザー مستخدم")]
|
||||||
|
public void CommandAttribute_ShouldAcceptVariousDescriptions(string description)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0)]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(10)]
|
||||||
|
[InlineData(100)]
|
||||||
|
[InlineData(int.MaxValue)]
|
||||||
|
[InlineData(int.MinValue)]
|
||||||
|
[InlineData(-1)]
|
||||||
|
[InlineData(-100)]
|
||||||
|
public void CommandAttribute_ShouldAcceptVariousPriorities(int priority)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description) { Priority = priority };
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Priority.Should().Be(priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldAllowNullCommandName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string? commandName = null;
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName!, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().BeNull();
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldAllowNullDescription()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
string? description = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description!);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.Description.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldAllowBothNullValues()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string? commandName = null;
|
||||||
|
string? description = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName!, description!);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().BeNull();
|
||||||
|
attribute.Description.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldBeImmutableAfterConstruction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
// CommandName and Description should be read-only
|
||||||
|
var commandNameProperty = typeof(CommandAttribute).GetProperty(
|
||||||
|
nameof(CommandAttribute.CommandName)
|
||||||
|
);
|
||||||
|
var descriptionProperty = typeof(CommandAttribute).GetProperty(
|
||||||
|
nameof(CommandAttribute.Description)
|
||||||
|
);
|
||||||
|
|
||||||
|
commandNameProperty!.CanWrite.Should().BeFalse();
|
||||||
|
descriptionProperty!.CanWrite.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldAllowPriorityModification()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
attribute.Priority = 42;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Priority.Should().Be(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldInheritFromAttribute()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var attribute = new CommandAttribute("/test", "Test command");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Should().BeAssignableTo<Attribute>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldBeSerializable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command";
|
||||||
|
var priority = 5;
|
||||||
|
var attribute = new CommandAttribute(commandName, description) { Priority = priority };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
// Check if the attribute can be serialized (basic check)
|
||||||
|
attribute.Should().NotBeNull();
|
||||||
|
// Note: In .NET 5+, IsSerializable is obsolete and returns false for most types
|
||||||
|
// This test verifies the attribute can be created and used
|
||||||
|
attribute.GetType().Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleVeryLongCommandName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = new string('a', 1000); // Very long command name
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
attribute.CommandName.Should().HaveLength(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleVeryLongDescription()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = new string('a', 10000); // Very long description
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
attribute.Description.Should().HaveLength(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleCommandNameWithSpecialCharacters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test-command_with.special@chars#123";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithNewlines()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Line 1\nLine 2\nLine 3";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithTabs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Column1\tColumn2\tColumn3";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithCarriageReturns()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Line 1\r\nLine 2\r\nLine 3";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleWhitespaceOnlyCommandName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = " ";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleWhitespaceOnlyDescription()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = " ";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleCommandNameWithUnicode()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/команда_命令_コマンド_أمر";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithUnicode()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Описание команды 命令描述 コマンドの説明 وصف الأمر";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleCommandNameWithSpaces()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test command with spaces";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithSpaces()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "This is a test command with multiple spaces";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleCommandNameWithNumbers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test123";
|
||||||
|
var description = "Test command";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.CommandName.Should().Be(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandAttribute_ShouldHandleDescriptionWithNumbers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var commandName = "/test";
|
||||||
|
var description = "Test command version 1.0.0 build 123";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var attribute = new CommandAttribute(commandName, description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
attribute.Description.Should().Be(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
441
ChatBot.Tests/Telegram/Commands/ReplyInfoTests.cs
Normal file
441
ChatBot.Tests/Telegram/Commands/ReplyInfoTests.cs
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ChatBot.Tests.Telegram.Commands;
|
||||||
|
|
||||||
|
public class ReplyInfoTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldHaveCorrectProperties()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = "testuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.MessageId.Should().Be(123);
|
||||||
|
replyInfo.UserId.Should().Be(456L);
|
||||||
|
replyInfo.Username.Should().Be("testuser");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldAllowNullUsername()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.MessageId.Should().Be(123);
|
||||||
|
replyInfo.UserId.Should().Be(456L);
|
||||||
|
replyInfo.Username.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldAllowEmptyUsername()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.MessageId.Should().Be(123);
|
||||||
|
replyInfo.UserId.Should().Be(456L);
|
||||||
|
replyInfo.Username.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnReplyInfo_WhenValidParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnReplyInfo_WhenUsernameIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
string? username = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnReplyInfo_WhenUsernameIsEmpty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = string.Empty;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenMessageIdIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
int? messageId = null;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenUserIdIsNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
long? userId = null;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenBothMessageIdAndUserIdAreNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
int? messageId = null;
|
||||||
|
long? userId = null;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenMessageIdIsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 0;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull(); // 0 is a valid message ID
|
||||||
|
result!.MessageId.Should().Be(0);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenUserIdIsZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 0L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull(); // 0 is a valid user ID
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(0L);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenMessageIdIsNegative()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = -1;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull(); // Negative values are still valid
|
||||||
|
result!.MessageId.Should().Be(-1);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldReturnNull_WhenUserIdIsNegative()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = -1L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull(); // Negative values are still valid
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(-1L);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleVeryLongUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = new string('a', 1000); // Very long username
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
result.Username.Should().HaveLength(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUsernameWithSpecialCharacters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "user@domain.com!@#$%^&*()_+-=[]{}|;':\",./<>?";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUsernameWithUnicodeCharacters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "пользователь_用户_ユーザー_مستخدم";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUsernameWithWhitespace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = " user with spaces ";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username); // Username is preserved as-is
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUsernameWithNewlines()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "user\nwith\nnewlines";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUsernameWithTabs()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "user\twith\ttabs";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0)]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(100)]
|
||||||
|
[InlineData(int.MaxValue)]
|
||||||
|
[InlineData(int.MinValue)]
|
||||||
|
public void Create_ShouldHandleVariousMessageIds(int messageId)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var userId = 456L;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0L)]
|
||||||
|
[InlineData(1L)]
|
||||||
|
[InlineData(100L)]
|
||||||
|
[InlineData(long.MaxValue)]
|
||||||
|
[InlineData(long.MinValue)]
|
||||||
|
public void Create_ShouldHandleVariousUserIds(long userId)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var messageId = 123;
|
||||||
|
var username = "testuser";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = ReplyInfo.Create(messageId, userId, username);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.MessageId.Should().Be(messageId);
|
||||||
|
result.UserId.Should().Be(userId);
|
||||||
|
result.Username.Should().Be(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldBeMutable()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = "testuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
replyInfo.MessageId = 789;
|
||||||
|
replyInfo.UserId = 101112L;
|
||||||
|
replyInfo.Username = "newuser";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.MessageId.Should().Be(789);
|
||||||
|
replyInfo.UserId.Should().Be(101112L);
|
||||||
|
replyInfo.Username.Should().Be("newuser");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldAllowSettingUsernameToNull()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = "testuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
replyInfo.Username = null;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.Username.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReplyInfo_ShouldAllowSettingUsernameToEmpty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 123,
|
||||||
|
UserId = 456L,
|
||||||
|
Username = "testuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
replyInfo.Username = string.Empty;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
replyInfo.Username.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
768
ChatBot.Tests/Telegram/Commands/TelegramCommandContextTests.cs
Normal file
768
ChatBot.Tests/Telegram/Commands/TelegramCommandContextTests.cs
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using FluentAssertions;
|
||||||
|
|
||||||
|
namespace ChatBot.Tests.Telegram.Commands;
|
||||||
|
|
||||||
|
public class TelegramCommandContextTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldCreateContextWithBasicProperties()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Should().NotBeNull();
|
||||||
|
context.ChatId.Should().Be(chatId);
|
||||||
|
context.Username.Should().Be(username);
|
||||||
|
context.MessageText.Should().Be(messageText);
|
||||||
|
context.ChatType.Should().Be(chatType);
|
||||||
|
context.ChatTitle.Should().Be(chatTitle);
|
||||||
|
context.Arguments.Should().Be("bot"); // "Hello bot" split by space gives ["Hello", "bot"]
|
||||||
|
context.ReplyInfo.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldExtractArgumentsFromMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1 arg2 arg3";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1 arg2 arg3");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithoutCommand()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "Hello bot";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("bot"); // "Hello bot" split by space gives ["Hello", "bot"]
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldRemoveBotUsernameFromCommand()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@mybot arg1 arg2";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1 arg2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMultipleBotUsernames()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@bot1@bot2 arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleAtSymbolWithoutBotName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@ arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyBotUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@ arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldTrimArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1 arg2 ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1 arg2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleReplyInfo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
var replyInfo = new ReplyInfo
|
||||||
|
{
|
||||||
|
MessageId = 1,
|
||||||
|
UserId = 123,
|
||||||
|
Username = "otheruser",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
replyInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ReplyInfo.Should().Be(replyInfo);
|
||||||
|
context.ReplyInfo!.MessageId.Should().Be(1);
|
||||||
|
context.ReplyInfo.UserId.Should().Be(123);
|
||||||
|
context.ReplyInfo.Username.Should().Be("otheruser");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleNullReplyInfo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
ReplyInfo? replyInfo = null;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle,
|
||||||
|
replyInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ReplyInfo.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.MessageText.Should().BeEmpty();
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleWhitespaceOnlyMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = " ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.MessageText.Should().Be(" ");
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleVeryLongMessage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start " + new string('A', 10000);
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().HaveLength(10000);
|
||||||
|
context.Arguments.Should().StartWith("AAAA");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleSpecialCharactersInArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start !@#$%^&*()_+-=[]{}|;':\",./<>?";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("!@#$%^&*()_+-=[]{}|;':\",./<>?");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleUnicodeCharacters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start привет мир 🌍";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("привет мир 🌍");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleNegativeChatId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = -12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ChatId.Should().Be(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleZeroChatId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 0L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ChatId.Should().Be(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Username.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyChatType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ChatType.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleEmptyChatTitle()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ChatTitle.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleVeryLongUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = new string('A', 1000);
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Username.Should().HaveLength(1000);
|
||||||
|
context.Username.Should().StartWith("AAAA");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleVeryLongChatTitle()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = new string('B', 1000);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.ChatTitle.Should().HaveLength(1000);
|
||||||
|
context.ChatTitle.Should().StartWith("BBBB");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithOnlySpaces()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = " ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.MessageText.Should().Be(" ");
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithTabsAndNewlines()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start\targ1\narg2\r\narg3";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().BeEmpty(); // Split by space only, so tabs and newlines are not split
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithMultipleSpacesInArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start arg1 arg2 arg3";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1 arg2 arg3"); // Trim() removes leading spaces
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithOnlyCommandAndSpaces()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithCommandAndOnlySpacesAsArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithCommandAndMixedWhitespace()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start\t \n arg1 \t arg2 \r\n ";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1 \t arg2"); // Split by space and trim removes leading/trailing spaces
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithVeryLongBotUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@verylongbotname arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithSpecialCharactersInBotUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@bot_name-123 arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Create_ShouldHandleMessageWithUnicodeInBotUsername()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var chatId = 12345L;
|
||||||
|
var username = "testuser";
|
||||||
|
var messageText = "/start@бот123 arg1";
|
||||||
|
var chatType = "private";
|
||||||
|
var chatTitle = "Test Chat";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = TelegramCommandContext.Create(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
context.Arguments.Should().Be("arg1");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper interface for Telegram Bot Client SendMessage functionality to enable mocking
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramMessageSenderWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message to a chat
|
||||||
|
/// </summary>
|
||||||
|
Task<Message> SendMessageAsync(
|
||||||
|
long chatId,
|
||||||
|
string text,
|
||||||
|
int replyToMessageId,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,15 @@ namespace ChatBot.Services.Telegram.Services
|
|||||||
public class TelegramMessageSender : ITelegramMessageSender
|
public class TelegramMessageSender : ITelegramMessageSender
|
||||||
{
|
{
|
||||||
private readonly ILogger<TelegramMessageSender> _logger;
|
private readonly ILogger<TelegramMessageSender> _logger;
|
||||||
|
private readonly ITelegramMessageSenderWrapper _messageSenderWrapper;
|
||||||
|
|
||||||
public TelegramMessageSender(ILogger<TelegramMessageSender> logger)
|
public TelegramMessageSender(
|
||||||
|
ILogger<TelegramMessageSender> logger,
|
||||||
|
ITelegramMessageSenderWrapper messageSenderWrapper
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_messageSenderWrapper = messageSenderWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -28,14 +33,20 @@ namespace ChatBot.Services.Telegram.Services
|
|||||||
int maxRetries = 3
|
int maxRetries = 3
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
// Ensure maxRetries is at least 1
|
||||||
|
if (maxRetries < 1)
|
||||||
|
{
|
||||||
|
maxRetries = 3;
|
||||||
|
}
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await botClient.SendMessage(
|
await _messageSenderWrapper.SendMessageAsync(
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
text: text,
|
text: text,
|
||||||
replyParameters: replyToMessageId,
|
replyToMessageId: replyToMessageId,
|
||||||
cancellationToken: cancellationToken
|
cancellationToken: cancellationToken
|
||||||
);
|
);
|
||||||
return; // Success, exit the method
|
return; // Success, exit the method
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper implementation for Telegram Bot Client SendMessage functionality
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramMessageSenderWrapper : ITelegramMessageSenderWrapper
|
||||||
|
{
|
||||||
|
private readonly ITelegramBotClient _botClient;
|
||||||
|
|
||||||
|
public TelegramMessageSenderWrapper(ITelegramBotClient botClient)
|
||||||
|
{
|
||||||
|
_botClient = botClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Message> SendMessageAsync(
|
||||||
|
long chatId,
|
||||||
|
string text,
|
||||||
|
int replyToMessageId,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return await _botClient.SendMessage(
|
||||||
|
chatId: chatId,
|
||||||
|
text: text,
|
||||||
|
replyParameters: replyToMessageId,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,15 +79,15 @@
|
|||||||
### 4. Telegram команды (дополнительные тесты)
|
### 4. Telegram команды (дополнительные тесты)
|
||||||
- [x] `TelegramCommandBase` - тесты базового класса команд
|
- [x] `TelegramCommandBase` - тесты базового класса команд
|
||||||
- [x] `TelegramCommandProcessor` - тесты обработки команд
|
- [x] `TelegramCommandProcessor` - тесты обработки команд
|
||||||
- [ ] `TelegramCommandContext` - тесты контекста команд
|
- [x] `TelegramCommandContext` - тесты контекста команд
|
||||||
- [ ] `ReplyInfo` - тесты информации о ответах
|
- [x] `ReplyInfo` - тесты информации о ответах
|
||||||
- [ ] `CommandAttribute` - тесты атрибутов команд
|
- [x] `CommandAttribute` - тесты атрибутов команд
|
||||||
|
|
||||||
### 5. Telegram сервисы (дополнительные тесты)
|
### 5. Telegram сервисы (дополнительные тесты)
|
||||||
- [ ] `TelegramBotClientWrapper` - тесты обертки клиента
|
- [x] `TelegramBotClientWrapper` - тесты обертки клиента
|
||||||
- [ ] `TelegramMessageHandler` - тесты различных типов сообщений
|
- [x] `TelegramMessageHandler` - тесты различных типов сообщений
|
||||||
- [ ] `TelegramErrorHandler` - тесты различных типов ошибок
|
- [x] `TelegramErrorHandler` - тесты различных типов ошибок
|
||||||
- [ ] `TelegramMessageSender` - тесты отправки различных типов сообщений
|
- [x] `TelegramMessageSender` - тесты отправки различных типов сообщений
|
||||||
|
|
||||||
### 6. Интерфейсы
|
### 6. Интерфейсы
|
||||||
- [ ] `IAIService` - тесты интерфейса
|
- [ ] `IAIService` - тесты интерфейса
|
||||||
|
|||||||
Reference in New Issue
Block a user