860 lines
27 KiB
C#
860 lines
27 KiB
C#
using ChatBot.Models.Configuration;
|
|
using ChatBot.Services;
|
|
using ChatBot.Services.Interfaces;
|
|
using ChatBot.Tests.TestUtilities;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
|
|
namespace ChatBot.Tests.Services;
|
|
|
|
public class ChatServiceTests : UnitTestBase
|
|
{
|
|
private readonly Mock<ILogger<ChatService>> _loggerMock;
|
|
private readonly Mock<IAIService> _aiServiceMock;
|
|
private readonly Mock<ISessionStorage> _sessionStorageMock;
|
|
private readonly Mock<IHistoryCompressionService> _compressionServiceMock;
|
|
private readonly AISettings _aiSettings;
|
|
private readonly ChatService _chatService;
|
|
|
|
public ChatServiceTests()
|
|
{
|
|
_loggerMock = TestDataBuilder.Mocks.CreateLoggerMock<ChatService>();
|
|
_aiServiceMock = TestDataBuilder.Mocks.CreateAIServiceMock();
|
|
_sessionStorageMock = TestDataBuilder.Mocks.CreateSessionStorageMock();
|
|
_compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
|
|
_aiSettings = TestDataBuilder.Configurations.CreateAISettings();
|
|
|
|
var optionsMock = TestDataBuilder.Mocks.CreateOptionsMock(_aiSettings);
|
|
|
|
_chatService = new ChatService(
|
|
_loggerMock.Object,
|
|
_aiServiceMock.Object,
|
|
_sessionStorageMock.Object,
|
|
optionsMock.Object,
|
|
_compressionServiceMock.Object
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetOrCreateSession_ShouldCreateNewSession_WhenSessionDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var chatType = "private";
|
|
var chatTitle = "Test Chat";
|
|
|
|
// Act
|
|
var session = _chatService.GetOrCreateSession(chatId, chatType, chatTitle);
|
|
|
|
// Assert
|
|
session.Should().NotBeNull();
|
|
session.ChatId.Should().Be(chatId);
|
|
session.ChatType.Should().Be(chatType);
|
|
session.ChatTitle.Should().Be(chatTitle);
|
|
|
|
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, chatType, chatTitle), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetOrCreateSession_ShouldSetCompressionService_WhenCompressionIsEnabled()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
_aiSettings.EnableHistoryCompression = true;
|
|
|
|
// Act
|
|
var session = _chatService.GetOrCreateSession(chatId);
|
|
|
|
// Assert
|
|
session.Should().NotBeNull();
|
|
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldProcessMessageSuccessfully_WhenValidInput()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.AtLeastOnce
|
|
);
|
|
_aiServiceMock.Verify(
|
|
x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldReturnEmptyString_WhenAIResponseIsEmptyMarker()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var emptyResponse = "{empty}";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(emptyResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldReturnErrorMessage_WhenAIResponseIsNull()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync((string)null!);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при генерации ответа.");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleException_WhenErrorOccurs()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ThrowsAsync(new Exception("Test exception"));
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при обработке вашего сообщения.");
|
|
// Verify that error was logged
|
|
// In a real test, we would verify the logger calls
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSessionParametersAsync_ShouldUpdateSession_WhenSessionExists()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var newModel = "llama3.2";
|
|
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
|
|
// Act
|
|
await _chatService.UpdateSessionParametersAsync(chatId, newModel);
|
|
|
|
// Assert
|
|
session.Model.Should().Be(newModel);
|
|
_sessionStorageMock.Verify(x => x.SaveSessionAsync(session), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSessionParametersAsync_ShouldNotUpdate_WhenSessionDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var newModel = "llama3.2";
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null);
|
|
|
|
// Act
|
|
await _chatService.UpdateSessionParametersAsync(chatId, newModel);
|
|
|
|
// Assert
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.Never
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClearHistoryAsync_ShouldClearHistory_WhenSessionExists()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5);
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
|
|
// Act
|
|
await _chatService.ClearHistoryAsync(chatId);
|
|
|
|
// Assert
|
|
session.GetMessageCount().Should().Be(0);
|
|
_sessionStorageMock.Verify(x => x.SaveSessionAsync(session), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSession_ShouldReturnSession_WhenSessionExists()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
|
|
// Act
|
|
var result = _chatService.GetSession(chatId);
|
|
|
|
// Assert
|
|
result.Should().Be(session);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetSession_ShouldReturnNull_WhenSessionDoesNotExist()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null);
|
|
|
|
// Act
|
|
var result = _chatService.GetSession(chatId);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveSession_ShouldReturnTrue_WhenSessionExists()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
|
|
_sessionStorageMock.Setup(x => x.Remove(chatId)).Returns(true);
|
|
|
|
// Act
|
|
var result = _chatService.RemoveSession(chatId);
|
|
|
|
// Assert
|
|
result.Should().BeTrue();
|
|
_sessionStorageMock.Verify(x => x.Remove(chatId), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetActiveSessionsCount_ShouldReturnCorrectCount()
|
|
{
|
|
// Arrange
|
|
var expectedCount = 5;
|
|
|
|
_sessionStorageMock.Setup(x => x.GetActiveSessionsCount()).Returns(expectedCount);
|
|
|
|
// Act
|
|
var result = _chatService.GetActiveSessionsCount();
|
|
|
|
// Assert
|
|
result.Should().Be(expectedCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void CleanupOldSessions_ShouldReturnCleanedCount()
|
|
{
|
|
// Arrange
|
|
var hoursOld = 24;
|
|
var expectedCleaned = 3;
|
|
|
|
_sessionStorageMock.Setup(x => x.CleanupOldSessions(hoursOld)).Returns(expectedCleaned);
|
|
|
|
// Act
|
|
var result = _chatService.CleanupOldSessions(hoursOld);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedCleaned);
|
|
_sessionStorageMock.Verify(x => x.CleanupOldSessions(hoursOld), Times.Once);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData(null!)]
|
|
public async Task ProcessMessageAsync_ShouldHandleEmptyOrNullMessage(string? message)
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(
|
|
chatId,
|
|
username,
|
|
message ?? string.Empty
|
|
);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.AtLeastOnce
|
|
);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData(null!)]
|
|
public async Task ProcessMessageAsync_ShouldHandleEmptyOrNullUsername(string? username)
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(
|
|
chatId,
|
|
username ?? string.Empty,
|
|
message
|
|
);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.AtLeastOnce
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleSessionStorageException()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
|
|
_sessionStorageMock
|
|
.Setup(x => x.GetOrCreate(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
|
|
.Throws(new Exception("Database connection failed"));
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при обработке вашего сообщения.");
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Error,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Error processing message")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleAIServiceException()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ThrowsAsync(new HttpRequestException("AI service unavailable"));
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при обработке вашего сообщения.");
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Error,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Error processing message")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleCancellationToken()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var cts = new CancellationTokenSource();
|
|
cts.Cancel(); // Cancel immediately
|
|
|
|
// Setup AI service to throw OperationCanceledException when cancellation is requested
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ThrowsAsync(new OperationCanceledException("Operation was canceled"));
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(
|
|
chatId,
|
|
username,
|
|
message,
|
|
cancellationToken: cts.Token
|
|
);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при обработке вашего сообщения.");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldLogCorrectInformation()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(
|
|
chatId,
|
|
username,
|
|
message,
|
|
"group",
|
|
"Test Group"
|
|
);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!
|
|
.Contains(
|
|
"Processing message from user testuser in chat 12345 (group): Hello, bot!"
|
|
)
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldLogDebugForResponseLength()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Debug,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!.Contains("AI response generated for chat 12345 (length:")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldTrimHistory_WhenCompressionDisabled()
|
|
{
|
|
// Arrange
|
|
var chatId = 99999L;
|
|
_aiSettings.EnableHistoryCompression = false;
|
|
|
|
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
|
session.MaxHistoryLength = 5; // force small history limit
|
|
|
|
_sessionStorageMock
|
|
.Setup(x => x.GetOrCreate(chatId, It.IsAny<string>(), It.IsAny<string>()))
|
|
.Returns(session);
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync("ok");
|
|
|
|
// Act: add many messages to exceed the limit
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
await _chatService.ProcessMessageAsync(chatId, "u", $"m{i}");
|
|
}
|
|
|
|
// Assert: history trimmed to MaxHistoryLength
|
|
session.GetMessageCount().Should().BeLessThanOrEqualTo(5);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldCompressHistory_WhenCompressionEnabledAndThresholdExceeded()
|
|
{
|
|
// Arrange
|
|
var chatId = 88888L;
|
|
_aiSettings.EnableHistoryCompression = true;
|
|
_aiSettings.CompressionThreshold = 4;
|
|
_aiSettings.CompressionTarget = 3;
|
|
|
|
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
|
session.MaxHistoryLength = 50; // avoid trimming impacting compression assertion
|
|
|
|
_sessionStorageMock
|
|
.Setup(x => x.GetOrCreate(chatId, It.IsAny<string>(), It.IsAny<string>()))
|
|
.Returns(session);
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync("ok");
|
|
|
|
// Act: add enough messages to exceed threshold
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
await _chatService.ProcessMessageAsync(chatId, "u", $"m{i}");
|
|
}
|
|
|
|
// Assert: compressed to target (user+assistant messages should be around target)
|
|
session.GetMessageCount().Should().BeLessThanOrEqualTo(_aiSettings.CompressionTarget + 1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldLogEmptyResponseMarker()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var emptyResponse = "{empty}";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(emptyResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty();
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!
|
|
.Contains(
|
|
"AI returned empty response marker for chat 12345, ignoring message"
|
|
)
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSessionParametersAsync_ShouldHandleSessionStorageException()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var newModel = "llama3.2";
|
|
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
_sessionStorageMock
|
|
.Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()))
|
|
.ThrowsAsync(new Exception("Database save failed"));
|
|
|
|
// Act & Assert
|
|
var act = async () => await _chatService.UpdateSessionParametersAsync(chatId, newModel);
|
|
await act.Should().ThrowAsync<Exception>().WithMessage("Database save failed");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClearHistoryAsync_ShouldHandleSessionStorageException()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5);
|
|
|
|
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
|
_sessionStorageMock
|
|
.Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()))
|
|
.ThrowsAsync(new Exception("Database save failed"));
|
|
|
|
// Act & Assert
|
|
var act = async () => await _chatService.ClearHistoryAsync(chatId);
|
|
await act.Should().ThrowAsync<Exception>().WithMessage("Database save failed");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(int.MinValue)]
|
|
public void CleanupOldSessions_ShouldHandleInvalidHoursOld(int hoursOld)
|
|
{
|
|
// Arrange
|
|
var expectedCleaned = 0;
|
|
_sessionStorageMock.Setup(x => x.CleanupOldSessions(hoursOld)).Returns(expectedCleaned);
|
|
|
|
// Act
|
|
var result = _chatService.CleanupOldSessions(hoursOld);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedCleaned);
|
|
_sessionStorageMock.Verify(x => x.CleanupOldSessions(hoursOld), Times.Once);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(long.MaxValue)]
|
|
[InlineData(long.MinValue)]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
public async Task ProcessMessageAsync_ShouldHandleExtremeChatIds(long chatId)
|
|
{
|
|
// Arrange
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleVeryLongMessage()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var veryLongMessage = new string('A', 10000); // Very long message
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, veryLongMessage);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.AtLeastOnce
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleVeryLongUsername()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var veryLongUsername = new string('U', 1000); // Very long username
|
|
var message = "Hello, bot!";
|
|
var expectedResponse = "Hello! How can I help you?";
|
|
|
|
_aiServiceMock
|
|
.Setup(x =>
|
|
x.GenerateChatCompletionWithCompressionAsync(
|
|
It.IsAny<List<ChatBot.Models.Dto.ChatMessage>>(),
|
|
It.IsAny<CancellationToken>()
|
|
)
|
|
)
|
|
.ReturnsAsync(expectedResponse);
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, veryLongUsername, message);
|
|
|
|
// Assert
|
|
result.Should().Be(expectedResponse);
|
|
_sessionStorageMock.Verify(
|
|
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
|
|
Times.AtLeastOnce
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProcessMessageAsync_ShouldHandleCompressionServiceException()
|
|
{
|
|
// Arrange
|
|
var chatId = 12345L;
|
|
var username = "testuser";
|
|
var message = "Hello, bot!";
|
|
_aiSettings.EnableHistoryCompression = true;
|
|
|
|
_compressionServiceMock
|
|
.Setup(x => x.ShouldCompress(It.IsAny<int>(), It.IsAny<int>()))
|
|
.Throws(new Exception("Compression service failed"));
|
|
|
|
// Act
|
|
var result = await _chatService.ProcessMessageAsync(chatId, username, message);
|
|
|
|
// Assert
|
|
result.Should().Be("Извините, произошла ошибка при обработке вашего сообщения.");
|
|
_loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Error,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Error processing message")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
}
|