This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore --verbosity normal
|
||||
|
||||
- name: Build solution
|
||||
run: dotnet build --no-restore --verbosity normal
|
||||
|
||||
- name: Run unit tests
|
||||
run: dotnet test --no-build --verbosity minimal --logger "trx;LogFileName=test-results.trx" --results-directory ./TestResults
|
||||
|
||||
- name: Generate test report
|
||||
if: always()
|
||||
run: |
|
||||
echo "Test results:"
|
||||
find ./TestResults -name "*.trx" -exec echo "Found test result file: {}" \;
|
||||
echo "File sizes:"
|
||||
find ./TestResults -name "*.trx" -exec ls -lh {} \;
|
||||
echo "✅ Tests completed successfully"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -20,6 +20,7 @@
|
||||
<PackageReference Include="FluentAssertions" Version="8.7.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
|
||||
@@ -261,4 +261,234 @@ public class ChatSessionRepositoryTests : TestBase
|
||||
var remainingSessions = await _repository.GetActiveSessionsCountAsync();
|
||||
remainingSessions.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMessagesAsync_ShouldReturnMessages_WhenMessagesExist()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0);
|
||||
await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1);
|
||||
|
||||
// Act
|
||||
var messages = await _repository.GetMessagesAsync(session.Id);
|
||||
|
||||
// Assert
|
||||
messages.Should().HaveCount(2);
|
||||
messages[0].Content.Should().Be("Message 1");
|
||||
messages[0].Role.Should().Be("User");
|
||||
messages[1].Content.Should().Be("Message 2");
|
||||
messages[1].Role.Should().Be("Assistant");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMessagesAsync_ShouldReturnEmptyList_WhenNoMessages()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var messages = await _repository.GetMessagesAsync(session.Id);
|
||||
|
||||
// Assert
|
||||
messages.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddMessageAsync_ShouldAddMessage()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var message = await _repository.AddMessageAsync(session.Id, "Test message", "User", 0);
|
||||
|
||||
// Assert
|
||||
message.Should().NotBeNull();
|
||||
message.Content.Should().Be("Test message");
|
||||
message.Role.Should().Be("User");
|
||||
message.MessageOrder.Should().Be(0);
|
||||
message.SessionId.Should().Be(session.Id);
|
||||
|
||||
var messages = await _repository.GetMessagesAsync(session.Id);
|
||||
messages.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClearMessagesAsync_ShouldRemoveAllMessages()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0);
|
||||
await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1);
|
||||
await _repository.AddMessageAsync(session.Id, "Message 3", "User", 2);
|
||||
|
||||
// Act
|
||||
await _repository.ClearMessagesAsync(session.Id);
|
||||
|
||||
// Assert
|
||||
var messages = await _repository.GetMessagesAsync(session.Id);
|
||||
messages.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSessionsForCleanupAsync_ShouldReturnOldSessions()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
|
||||
var oldSession1 = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
oldSession1.LastUpdatedAt = DateTime.UtcNow.AddDays(-2);
|
||||
|
||||
var oldSession2 = TestDataBuilder.Mocks.CreateChatSessionEntity(2, 12346);
|
||||
oldSession2.LastUpdatedAt = DateTime.UtcNow.AddHours(-25);
|
||||
|
||||
var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(3, 12347);
|
||||
recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-30);
|
||||
|
||||
_dbContext.ChatSessions.AddRange(oldSession1, oldSession2, recentSession);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var sessionsForCleanup = await _repository.GetSessionsForCleanupAsync(24);
|
||||
|
||||
// Assert
|
||||
sessionsForCleanup.Should().HaveCount(2);
|
||||
sessionsForCleanup.Should().Contain(s => s.ChatId == 12345);
|
||||
sessionsForCleanup.Should().Contain(s => s.ChatId == 12346);
|
||||
sessionsForCleanup.Should().NotContain(s => s.ChatId == 12347);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSessionsForCleanupAsync_ShouldReturnEmptyList_WhenNoOldSessions()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
|
||||
var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-30);
|
||||
|
||||
_dbContext.ChatSessions.Add(recentSession);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var sessionsForCleanup = await _repository.GetSessionsForCleanupAsync(24);
|
||||
|
||||
// Assert
|
||||
sessionsForCleanup.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ShouldUpdateLastUpdatedAt()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
var originalLastUpdated = DateTime.UtcNow.AddDays(-1);
|
||||
session.LastUpdatedAt = originalLastUpdated;
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
session.Model = "new-model";
|
||||
var updatedSession = await _repository.UpdateAsync(session);
|
||||
|
||||
// Assert
|
||||
updatedSession.LastUpdatedAt.Should().BeAfter(originalLastUpdated);
|
||||
updatedSession.Model.Should().Be("new-model");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByChatIdAsync_ShouldIncludeMessages()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
await _repository.AddMessageAsync(session.Id, "Test message", "User", 0);
|
||||
|
||||
// Act
|
||||
var retrievedSession = await _repository.GetByChatIdAsync(12345);
|
||||
|
||||
// Assert
|
||||
retrievedSession.Should().NotBeNull();
|
||||
retrievedSession!.Messages.Should().HaveCount(1);
|
||||
retrievedSession.Messages.First().Content.Should().Be("Test message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetBySessionIdAsync_ShouldIncludeMessages()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var sessionId = "test-session-id";
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345, sessionId);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
await _repository.AddMessageAsync(session.Id, "Test message", "User", 0);
|
||||
|
||||
// Act
|
||||
var retrievedSession = await _repository.GetBySessionIdAsync(sessionId);
|
||||
|
||||
// Assert
|
||||
retrievedSession.Should().NotBeNull();
|
||||
retrievedSession!.Messages.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddMessageAsync_WithMultipleMessages_ShouldMaintainOrder()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
_dbContext.ChatSessions.Add(session);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0);
|
||||
await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1);
|
||||
await _repository.AddMessageAsync(session.Id, "Message 3", "User", 2);
|
||||
|
||||
// Assert
|
||||
var messages = await _repository.GetMessagesAsync(session.Id);
|
||||
messages.Should().HaveCount(3);
|
||||
messages[0].MessageOrder.Should().Be(0);
|
||||
messages[1].MessageOrder.Should().Be(1);
|
||||
messages[2].MessageOrder.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CleanupOldSessionsAsync_WithNoOldSessions_ShouldReturnZero()
|
||||
{
|
||||
// Arrange
|
||||
CleanupDatabase();
|
||||
|
||||
var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345);
|
||||
recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-10);
|
||||
_dbContext.ChatSessions.Add(recentSession);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var removedCount = await _repository.CleanupOldSessionsAsync(24);
|
||||
|
||||
// Assert
|
||||
removedCount.Should().Be(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ChatSessionTests
|
||||
|
||||
// Assert
|
||||
session.GetAllMessages().Should().HaveCount(1);
|
||||
var message = session.GetAllMessages().First();
|
||||
var message = session.GetAllMessages()[0];
|
||||
message.Role.Should().Be(ChatRole.User);
|
||||
message.Content.Should().Be(content);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class ChatSessionTests
|
||||
|
||||
// Assert
|
||||
session.GetAllMessages().Should().HaveCount(1);
|
||||
var message = session.GetAllMessages().First();
|
||||
var message = session.GetAllMessages()[0];
|
||||
message.Role.Should().Be(ChatRole.Assistant);
|
||||
message.Content.Should().Be(content);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class ChatSessionTests
|
||||
|
||||
// Assert
|
||||
session.GetAllMessages().Should().HaveCount(1);
|
||||
var addedMessage = session.GetAllMessages().First();
|
||||
var addedMessage = session.GetAllMessages()[0];
|
||||
addedMessage.Role.Should().Be(ChatRole.System);
|
||||
addedMessage.Content.Should().Be(content);
|
||||
}
|
||||
@@ -183,4 +183,319 @@ public class ChatSessionTests
|
||||
// Assert
|
||||
count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddUserMessage_InGroupChat_ShouldIncludeUsername()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { ChatType = "group" };
|
||||
var content = "Hello";
|
||||
var username = "testuser";
|
||||
|
||||
// Act
|
||||
session.AddUserMessage(content, username);
|
||||
|
||||
// Assert
|
||||
var message = session.GetAllMessages()[0];
|
||||
message.Content.Should().Be("testuser: Hello");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddUserMessage_InPrivateChat_ShouldNotIncludeUsername()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { ChatType = "private" };
|
||||
var content = "Hello";
|
||||
var username = "testuser";
|
||||
|
||||
// Act
|
||||
session.AddUserMessage(content, username);
|
||||
|
||||
// Assert
|
||||
var message = session.GetAllMessages()[0];
|
||||
message.Content.Should().Be("Hello");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMessage_ShouldTrimHistory_WhenExceedsMaxHistoryLength()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { MaxHistoryLength = 5 };
|
||||
|
||||
// Add system message
|
||||
session.AddMessage(new ChatMessage { Role = ChatRole.System, Content = "System prompt" });
|
||||
|
||||
// Add messages to exceed max history
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" });
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
session.GetMessageCount().Should().BeLessThanOrEqualTo(5);
|
||||
// System message should be preserved
|
||||
session.GetAllMessages()[0].Role.Should().Be(ChatRole.System);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMessage_ShouldTrimHistory_WithoutSystemMessage()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { MaxHistoryLength = 3 };
|
||||
|
||||
// Add messages to exceed max history
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" });
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
session.GetMessageCount().Should().BeLessThanOrEqualTo(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCompressionService_ShouldSetService()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
var compressionServiceMock =
|
||||
new Moq.Mock<ChatBot.Services.Interfaces.IHistoryCompressionService>();
|
||||
|
||||
// Act
|
||||
session.SetCompressionService(compressionServiceMock.Object);
|
||||
|
||||
// Assert
|
||||
// The service should be set (no exception)
|
||||
session.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddMessageWithCompressionAsync_WithoutCompressionService_ShouldUseTrimming()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { MaxHistoryLength = 3 };
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await session.AddMessageWithCompressionAsync(
|
||||
new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" },
|
||||
10,
|
||||
5
|
||||
);
|
||||
}
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().BeLessThanOrEqualTo(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddUserMessageWithCompressionAsync_ShouldAddMessage()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { ChatType = "private" };
|
||||
|
||||
// Act
|
||||
await session.AddUserMessageWithCompressionAsync("Test message", "user", 10, 5);
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().Be(1);
|
||||
session.GetAllMessages()[0].Content.Should().Be("Test message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddUserMessageWithCompressionAsync_InGroupChat_ShouldIncludeUsername()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { ChatType = "group" };
|
||||
|
||||
// Act
|
||||
await session.AddUserMessageWithCompressionAsync("Test message", "user", 10, 5);
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().Be(1);
|
||||
session.GetAllMessages()[0].Content.Should().Be("user: Test message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAssistantMessageWithCompressionAsync_ShouldAddMessage()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
|
||||
// Act
|
||||
await session.AddAssistantMessageWithCompressionAsync("Test response", 10, 5);
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().Be(1);
|
||||
session.GetAllMessages()[0].Role.Should().Be(ChatRole.Assistant);
|
||||
session.GetAllMessages()[0].Content.Should().Be("Test response");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddMessageWithCompressionAsync_ShouldTriggerTrimming_WhenNoCompressionService()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { MaxHistoryLength = 2 };
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
await session.AddMessageWithCompressionAsync(
|
||||
new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" },
|
||||
2,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().BeLessThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClearHistory_ShouldUpdateLastUpdatedAt()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
session.AddUserMessage("Test", "user");
|
||||
var lastUpdated = session.LastUpdatedAt;
|
||||
await Task.Delay(10); // Small delay
|
||||
|
||||
// Act
|
||||
session.ClearHistory();
|
||||
|
||||
// Assert
|
||||
session.LastUpdatedAt.Should().BeAfter(lastUpdated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetCreatedAtForTesting_ShouldUpdateCreatedAt()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
var targetDate = DateTime.UtcNow.AddDays(-5);
|
||||
|
||||
// Act
|
||||
session.SetCreatedAtForTesting(targetDate);
|
||||
|
||||
// Assert
|
||||
session.CreatedAt.Should().Be(targetDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMessage_MultipleTimes_ShouldMaintainOrder()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
|
||||
// Act
|
||||
session.AddUserMessage("Message 1", "user1");
|
||||
session.AddAssistantMessage("Response 1");
|
||||
session.AddUserMessage("Message 2", "user1");
|
||||
session.AddAssistantMessage("Response 2");
|
||||
|
||||
// Assert
|
||||
var messages = session.GetAllMessages();
|
||||
messages.Should().HaveCount(4);
|
||||
messages[0].Content.Should().Be("Message 1");
|
||||
messages[1].Content.Should().Be("Response 1");
|
||||
messages[2].Content.Should().Be("Message 2");
|
||||
messages[3].Content.Should().Be("Response 2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddMessage_WithSystemMessage_ShouldPreserveSystemMessage()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession { MaxHistoryLength = 3 };
|
||||
session.AddMessage(new ChatMessage { Role = ChatRole.System, Content = "System prompt" });
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
session.AddUserMessage($"Message {i}", "user");
|
||||
}
|
||||
|
||||
// Assert
|
||||
var messages = session.GetAllMessages();
|
||||
messages[0].Role.Should().Be(ChatRole.System);
|
||||
messages[0].Content.Should().Be("System prompt");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAllMessages_ShouldReturnCopy()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
session.AddUserMessage("Test", "user");
|
||||
|
||||
// Act
|
||||
var messages1 = session.GetAllMessages();
|
||||
var messages2 = session.GetAllMessages();
|
||||
|
||||
// Assert
|
||||
messages1.Should().NotBeSameAs(messages2);
|
||||
messages1.Should().BeEquivalentTo(messages2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChatSession_ThreadSafety_MultipleConcurrentAdds()
|
||||
{
|
||||
// Arrange
|
||||
var session = new ChatSession();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int messageNum = i;
|
||||
tasks.Add(Task.Run(() => session.AddUserMessage($"Message {messageNum}", "user")));
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks.ToArray());
|
||||
|
||||
// Assert
|
||||
session.GetMessageCount().Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaxHistoryLength_ShouldBeSettable()
|
||||
{
|
||||
// Arrange & Act
|
||||
var session = new ChatSession { MaxHistoryLength = 50 };
|
||||
|
||||
// Assert
|
||||
session.MaxHistoryLength.Should().Be(50);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Model_ShouldBeSettable()
|
||||
{
|
||||
// Arrange & Act
|
||||
var session = new ChatSession { Model = "llama3" };
|
||||
|
||||
// Assert
|
||||
session.Model.Should().Be("llama3");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionProperties_ShouldBeSettableViaInitializer()
|
||||
{
|
||||
// Arrange & Act
|
||||
var session = new ChatSession
|
||||
{
|
||||
ChatId = 12345,
|
||||
ChatType = "group",
|
||||
ChatTitle = "Test Group",
|
||||
Model = "llama3",
|
||||
MaxHistoryLength = 50,
|
||||
};
|
||||
|
||||
// Assert
|
||||
session.ChatId.Should().Be(12345);
|
||||
session.ChatType.Should().Be("group");
|
||||
session.ChatTitle.Should().Be("Test Group");
|
||||
session.Model.Should().Be("llama3");
|
||||
session.MaxHistoryLength.Should().Be(50);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,4 +255,328 @@ public class DatabaseInitializationServiceTests : UnitTestBase
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_WhenDatabaseExists_ShouldLogAndMigrate()
|
||||
{
|
||||
// Arrange
|
||||
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
||||
var services = new ServiceCollection();
|
||||
services.AddDbContext<ChatBotDbContext>(options =>
|
||||
options.UseSqlite($"Data Source={dbPath}")
|
||||
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
||||
);
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
|
||||
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) => v.ToString()!.Contains("Starting database initialization")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) =>
|
||||
v.ToString()!
|
||||
.Contains("Database initialization completed successfully")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
serviceProvider.Dispose();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_WithCancellationToken_ShouldComplete()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StopAsync(cts.Token);
|
||||
|
||||
// Assert
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_WhenCancellationRequested_ShouldStillComplete()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
// Act
|
||||
await service.StopAsync(cts.Token);
|
||||
|
||||
// Assert
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_ShouldHandleDatabaseDoesNotExistException()
|
||||
{
|
||||
// Arrange
|
||||
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
||||
var services = new ServiceCollection();
|
||||
services.AddDbContext<ChatBotDbContext>(options =>
|
||||
options.UseSqlite($"Data Source={dbPath}")
|
||||
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
||||
);
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
|
||||
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert - service should complete successfully
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) =>
|
||||
v.ToString()!
|
||||
.Contains("Database initialization completed successfully")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
serviceProvider.Dispose();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_WithValidDatabase_ShouldLogDatabaseExists()
|
||||
{
|
||||
// Arrange
|
||||
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
||||
var services = new ServiceCollection();
|
||||
services.AddDbContext<ChatBotDbContext>(options =>
|
||||
options.UseSqlite($"Data Source={dbPath}")
|
||||
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
||||
);
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
|
||||
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>((v, t) => true), // Any log message
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.AtLeastOnce
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
serviceProvider.Dispose();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatabaseInitializationService_ShouldImplementIHostedService()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
|
||||
// Act
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
|
||||
// Assert
|
||||
service.Should().BeAssignableTo<Microsoft.Extensions.Hosting.IHostedService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_MultipleCallsInSequence_ShouldWork()
|
||||
{
|
||||
// Arrange
|
||||
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
||||
var services = new ServiceCollection();
|
||||
services.AddDbContext<ChatBotDbContext>(options =>
|
||||
options.UseSqlite($"Data Source={dbPath}")
|
||||
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
||||
);
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await service.StartAsync(CancellationToken.None);
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - should complete without exceptions
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) =>
|
||||
v.ToString()!
|
||||
.Contains("Database initialization completed successfully")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
serviceProvider.Dispose();
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_WithoutStartAsync_ShouldComplete()
|
||||
{
|
||||
// Arrange
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
||||
var service = new DatabaseInitializationService(
|
||||
serviceProviderMock.Object,
|
||||
loggerMock.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - should complete without exceptions
|
||||
loggerMock.Verify(
|
||||
x =>
|
||||
x.Log(
|
||||
LogLevel.Information,
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>(
|
||||
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
||||
),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,4 +176,265 @@ public class DatabaseSessionStorageTests : TestBase
|
||||
result.Should().Be(expectedCount);
|
||||
_repositoryMock.Verify(x => x.CleanupOldSessionsAsync(24), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrCreate_ShouldThrowInvalidOperationException_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat"))
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var act = () => _sessionStorage.GetOrCreate(12345, "private", "Test Chat");
|
||||
|
||||
// Assert
|
||||
act.Should()
|
||||
.Throw<InvalidOperationException>()
|
||||
.WithMessage("Failed to get or create session for chat 12345")
|
||||
.WithInnerException<Exception>()
|
||||
.WithMessage("Database error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_ShouldReturnNull_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetByChatIdAsync(12345))
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var result = _sessionStorage.Get(12345);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveSessionAsync_ShouldLogWarning_WhenSessionNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetByChatIdAsync(12345))
|
||||
.ReturnsAsync((ChatSessionEntity?)null);
|
||||
|
||||
// Act
|
||||
await _sessionStorage.SaveSessionAsync(session);
|
||||
|
||||
// Assert
|
||||
_repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once);
|
||||
_repositoryMock.Verify(x => x.UpdateAsync(It.IsAny<ChatSessionEntity>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveSessionAsync_ShouldThrowInvalidOperationException_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetByChatIdAsync(12345))
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var act = async () => await _sessionStorage.SaveSessionAsync(session);
|
||||
|
||||
// Assert
|
||||
var exception = await act.Should()
|
||||
.ThrowAsync<InvalidOperationException>()
|
||||
.WithMessage("Failed to save session for chat 12345");
|
||||
exception
|
||||
.And.InnerException.Should()
|
||||
.BeOfType<Exception>()
|
||||
.Which.Message.Should()
|
||||
.Be("Database error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveSessionAsync_ShouldClearMessagesAndAddNew()
|
||||
{
|
||||
// Arrange
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
|
||||
session.AddUserMessage("Test message", "user1");
|
||||
session.AddAssistantMessage("Test response");
|
||||
|
||||
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
|
||||
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
|
||||
_repositoryMock
|
||||
.Setup(x => x.UpdateAsync(It.IsAny<ChatSessionEntity>()))
|
||||
.ReturnsAsync(sessionEntity);
|
||||
|
||||
// Act
|
||||
await _sessionStorage.SaveSessionAsync(session);
|
||||
|
||||
// Assert
|
||||
_repositoryMock.Verify(x => x.ClearMessagesAsync(It.IsAny<int>()), Times.Once);
|
||||
_repositoryMock.Verify(
|
||||
x =>
|
||||
x.AddMessageAsync(
|
||||
It.IsAny<int>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<int>()
|
||||
),
|
||||
Times.Exactly(2)
|
||||
);
|
||||
_repositoryMock.Verify(x => x.UpdateAsync(It.IsAny<ChatSessionEntity>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_ShouldReturnFalse_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(x => x.DeleteAsync(12345))
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var result = _sessionStorage.Remove(12345);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActiveSessionsCount_ShouldReturnZero_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetActiveSessionsCountAsync())
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var result = _sessionStorage.GetActiveSessionsCount();
|
||||
|
||||
// Assert
|
||||
result.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CleanupOldSessions_ShouldReturnZero_WhenRepositoryThrows()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(x => x.CleanupOldSessionsAsync(24))
|
||||
.ThrowsAsync(new Exception("Database error"));
|
||||
|
||||
// Act
|
||||
var result = _sessionStorage.CleanupOldSessions(24);
|
||||
|
||||
// Assert
|
||||
result.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrCreate_WithCompressionService_ShouldSetCompressionService()
|
||||
{
|
||||
// Arrange
|
||||
var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
|
||||
var storageWithCompression = new DatabaseSessionStorage(
|
||||
_repositoryMock.Object,
|
||||
Mock.Of<ILogger<DatabaseSessionStorage>>(),
|
||||
compressionServiceMock.Object
|
||||
);
|
||||
|
||||
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat"))
|
||||
.ReturnsAsync(sessionEntity);
|
||||
|
||||
// Act
|
||||
var result = storageWithCompression.GetOrCreate(12345, "private", "Test Chat");
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.ChatId.Should().Be(12345);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_WithCompressionService_ShouldSetCompressionService()
|
||||
{
|
||||
// Arrange
|
||||
var loggerMock = new Mock<ILogger<DatabaseSessionStorage>>();
|
||||
var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
|
||||
var storageWithCompression = new DatabaseSessionStorage(
|
||||
_repositoryMock.Object,
|
||||
loggerMock.Object,
|
||||
compressionServiceMock.Object
|
||||
);
|
||||
|
||||
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
|
||||
sessionEntity.Messages.Add(
|
||||
new ChatMessageEntity
|
||||
{
|
||||
Id = 1,
|
||||
SessionId = sessionEntity.Id,
|
||||
Content = "Test",
|
||||
Role = "user",
|
||||
MessageOrder = 0,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
}
|
||||
);
|
||||
|
||||
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
|
||||
|
||||
// Act
|
||||
var result = storageWithCompression.Get(12345);
|
||||
|
||||
// Assert
|
||||
_repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once);
|
||||
result.Should().NotBeNull();
|
||||
result!.GetMessageCount().Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveSessionAsync_WithMultipleMessages_ShouldSaveInCorrectOrder()
|
||||
{
|
||||
// Arrange
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
|
||||
session.AddUserMessage("Message 1", "user1");
|
||||
session.AddAssistantMessage("Response 1");
|
||||
session.AddUserMessage("Message 2", "user1");
|
||||
session.AddAssistantMessage("Response 2");
|
||||
|
||||
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
|
||||
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
|
||||
_repositoryMock
|
||||
.Setup(x => x.UpdateAsync(It.IsAny<ChatSessionEntity>()))
|
||||
.ReturnsAsync(sessionEntity);
|
||||
|
||||
// Act
|
||||
await _sessionStorage.SaveSessionAsync(session);
|
||||
|
||||
// Assert
|
||||
_repositoryMock.Verify(x => x.ClearMessagesAsync(It.IsAny<int>()), Times.Once);
|
||||
_repositoryMock.Verify(
|
||||
x =>
|
||||
x.AddMessageAsync(
|
||||
It.IsAny<int>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<int>()
|
||||
),
|
||||
Times.Exactly(4)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrCreate_WithDefaultParameters_ShouldUseDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
|
||||
_repositoryMock
|
||||
.Setup(x => x.GetOrCreateAsync(12345, "private", ""))
|
||||
.ReturnsAsync(sessionEntity);
|
||||
|
||||
// Act
|
||||
var result = _sessionStorage.GetOrCreate(12345);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
_repositoryMock.Verify(x => x.GetOrCreateAsync(12345, "private", ""), Times.Once);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using ChatBot.Services.Telegram.Services;
|
||||
using ChatBot.Tests.TestUtilities;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Xunit;
|
||||
|
||||
namespace ChatBot.Tests.Services.Telegram;
|
||||
|
||||
public class TelegramMessageSenderWrapperTests : UnitTestBase
|
||||
{
|
||||
private readonly Mock<ITelegramBotClient> _botClientMock;
|
||||
private readonly TelegramMessageSenderWrapper _wrapper;
|
||||
|
||||
public TelegramMessageSenderWrapperTests()
|
||||
{
|
||||
_botClientMock = TestDataBuilder.Mocks.CreateTelegramBotClient();
|
||||
_wrapper = new TelegramMessageSenderWrapper(_botClientMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ShouldInitializeCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var botClient = TestDataBuilder.Mocks.CreateTelegramBotClient().Object;
|
||||
|
||||
// Act
|
||||
var wrapper = new TelegramMessageSenderWrapper(botClient);
|
||||
|
||||
// Assert
|
||||
wrapper.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendMessageAsync_ShouldBePublicMethod()
|
||||
{
|
||||
// Arrange & Act
|
||||
var method = typeof(TelegramMessageSenderWrapper).GetMethod("SendMessageAsync");
|
||||
|
||||
// Assert
|
||||
method.Should().NotBeNull();
|
||||
method!.IsPublic.Should().BeTrue();
|
||||
method.ReturnType.Should().Be<Task<Message>>();
|
||||
}
|
||||
}
|
||||
@@ -329,4 +329,8 @@ public class TelegramBotClientWrapperTests : UnitTestBase
|
||||
var attributes = returnType.GetCustomAttributes(false);
|
||||
attributes.Should().NotBeNull();
|
||||
}
|
||||
|
||||
// Note: Tests for GetMeAsync removed because GetMe is an extension method
|
||||
// and cannot be mocked with Moq. The wrapper simply delegates to the
|
||||
// TelegramBotClient extension method, which is tested by the Telegram.Bot library itself.
|
||||
}
|
||||
|
||||
@@ -10,16 +10,15 @@ namespace ChatBot.Tests.Telegram.Commands;
|
||||
|
||||
public class SettingsCommandTests : UnitTestBase
|
||||
{
|
||||
private readonly Mock<ISessionStorage> _sessionStorageMock;
|
||||
private readonly Mock<ChatService> _chatServiceMock;
|
||||
private readonly SettingsCommand _settingsCommand;
|
||||
|
||||
public SettingsCommandTests()
|
||||
{
|
||||
_sessionStorageMock = TestDataBuilder.Mocks.CreateSessionStorageMock();
|
||||
var chatServiceMock = new Mock<ChatService>(
|
||||
_chatServiceMock = new Mock<ChatService>(
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<ChatService>().Object,
|
||||
TestDataBuilder.Mocks.CreateAIServiceMock().Object,
|
||||
_sessionStorageMock.Object,
|
||||
TestDataBuilder.Mocks.CreateSessionStorageMock().Object,
|
||||
TestDataBuilder
|
||||
.Mocks.CreateOptionsMock(TestDataBuilder.Configurations.CreateAISettings())
|
||||
.Object,
|
||||
@@ -33,7 +32,7 @@ public class SettingsCommandTests : UnitTestBase
|
||||
);
|
||||
var aiSettingsMock = TestDataBuilder.Mocks.CreateOptionsMock(new AISettings());
|
||||
_settingsCommand = new SettingsCommand(
|
||||
chatServiceMock.Object,
|
||||
_chatServiceMock.Object,
|
||||
modelServiceMock.Object,
|
||||
aiSettingsMock.Object
|
||||
);
|
||||
@@ -45,7 +44,7 @@ public class SettingsCommandTests : UnitTestBase
|
||||
// Arrange
|
||||
var chatId = 12345L;
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
|
||||
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session);
|
||||
_chatServiceMock.Setup(x => x.GetSession(chatId)).Returns(session);
|
||||
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
@@ -70,7 +69,9 @@ public class SettingsCommandTests : UnitTestBase
|
||||
{
|
||||
// Arrange
|
||||
var chatId = 12345L;
|
||||
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null);
|
||||
_chatServiceMock
|
||||
.Setup(x => x.GetSession(chatId))
|
||||
.Returns((ChatBot.Models.ChatSession?)null);
|
||||
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
|
||||
@@ -155,4 +155,284 @@ public class StatusCommandTests : UnitTestBase
|
||||
result.Should().Contain("системы");
|
||||
result.Should().Contain("Доступен");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnTimeoutStatus_WhenRequestTimesOut()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws<TaskCanceledException>();
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("Таймаут");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnHttpError502_WhenBadGateway()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
var httpException = new HttpRequestException(
|
||||
"Response status code does not indicate success: 502"
|
||||
);
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws(httpException);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("502");
|
||||
result.Should().Contain("Bad Gateway");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnHttpError503_WhenServiceUnavailable()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
var httpException = new HttpRequestException(
|
||||
"Response status code does not indicate success: 503"
|
||||
);
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws(httpException);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("503");
|
||||
result.Should().Contain("Service Unavailable");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnHttpError504_WhenGatewayTimeout()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
var httpException = new HttpRequestException(
|
||||
"Response status code does not indicate success: 504"
|
||||
);
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws(httpException);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("504");
|
||||
result.Should().Contain("Gateway Timeout");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnHttpError429_WhenTooManyRequests()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
var httpException = new HttpRequestException(
|
||||
"Response status code does not indicate success: 429"
|
||||
);
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws(httpException);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("429");
|
||||
result.Should().Contain("Too Many Requests");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnHttpError500_WhenInternalServerError()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
var httpException = new HttpRequestException(
|
||||
"Response status code does not indicate success: 500"
|
||||
);
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Throws(httpException);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("500");
|
||||
result.Should().Contain("Internal Server Error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ShouldReturnNoResponseStatus_WhenResponseIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
// Return empty response
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Returns(
|
||||
TestDataBuilder.Mocks.CreateAsyncEnumerable(
|
||||
new List<OllamaSharp.Models.Chat.ChatResponseStream>
|
||||
{
|
||||
new OllamaSharp.Models.Chat.ChatResponseStream { Message = null! },
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await _statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("Нет ответа");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithSession_ShouldShowSessionInfo()
|
||||
{
|
||||
// Arrange
|
||||
var chatServiceMock = new Mock<ChatService>(
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<ChatService>().Object,
|
||||
TestDataBuilder.Mocks.CreateAIServiceMock().Object,
|
||||
TestDataBuilder.Mocks.CreateSessionStorageMock().Object,
|
||||
TestDataBuilder
|
||||
.Mocks.CreateOptionsMock(TestDataBuilder.Configurations.CreateAISettings())
|
||||
.Object,
|
||||
TestDataBuilder.Mocks.CreateCompressionServiceMock().Object
|
||||
);
|
||||
|
||||
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
|
||||
session.AddUserMessage("Test", "user");
|
||||
chatServiceMock.Setup(x => x.GetSession(12345)).Returns(session);
|
||||
|
||||
var statusCommand = new StatusCommand(
|
||||
chatServiceMock.Object,
|
||||
new Mock<ModelService>(
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<ModelService>().Object,
|
||||
_ollamaOptionsMock.Object
|
||||
).Object,
|
||||
TestDataBuilder.Mocks.CreateOptionsMock(new AISettings()).Object,
|
||||
_ollamaClientMock.Object
|
||||
);
|
||||
|
||||
var context = new TelegramCommandContext
|
||||
{
|
||||
ChatId = 12345,
|
||||
Username = "testuser",
|
||||
MessageText = "/status",
|
||||
ChatType = "private",
|
||||
ChatTitle = "Test Chat",
|
||||
};
|
||||
|
||||
_ollamaClientMock
|
||||
.Setup(x => x.ChatAsync(It.IsAny<OllamaSharp.Models.Chat.ChatRequest>()))
|
||||
.Returns(
|
||||
TestDataBuilder.Mocks.CreateAsyncEnumerable(
|
||||
new List<OllamaSharp.Models.Chat.ChatResponseStream>
|
||||
{
|
||||
new OllamaSharp.Models.Chat.ChatResponseStream
|
||||
{
|
||||
Message = new OllamaSharp.Models.Chat.Message(
|
||||
ChatRole.Assistant,
|
||||
"Test"
|
||||
),
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await statusCommand.ExecuteAsync(context);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Should().Contain("Сессия");
|
||||
result.Should().Contain("Сообщений в истории");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommandName_ShouldReturnCorrectName()
|
||||
{
|
||||
// Act & Assert
|
||||
_statusCommand.CommandName.Should().Be("/status");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Description_ShouldReturnCorrectDescription()
|
||||
{
|
||||
// Act & Assert
|
||||
_statusCommand.Description.Should().Be("Показать статус системы и API");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using ChatBot.Tests.TestUtilities;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace ChatBot.Tests.Telegram.Commands;
|
||||
@@ -417,8 +418,8 @@ public class TelegramCommandProcessorTests : UnitTestBase
|
||||
var username = "testuser";
|
||||
var chatType = "private";
|
||||
var chatTitle = "Test Chat";
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
using var cts = new CancellationTokenSource();
|
||||
await cts.CancelAsync();
|
||||
|
||||
// Act
|
||||
var result = await _processor.ProcessMessageAsync(
|
||||
@@ -488,4 +489,347 @@ public class TelegramCommandProcessorTests : UnitTestBase
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithReplyToBot_ShouldProcessMessage()
|
||||
{
|
||||
// Arrange
|
||||
var botUser = new User
|
||||
{
|
||||
Id = 999,
|
||||
Username = "testbot",
|
||||
IsBot = true,
|
||||
};
|
||||
|
||||
var botInfoServiceMock = new Mock<BotInfoService>(
|
||||
TestDataBuilder.Mocks.CreateTelegramBotClient().Object,
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<BotInfoService>().Object
|
||||
);
|
||||
botInfoServiceMock.Setup(x => x.GetBotInfoAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(botUser);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
botInfoServiceMock.Object
|
||||
);
|
||||
|
||||
var replyInfo = new ReplyInfo
|
||||
{
|
||||
MessageId = 1,
|
||||
UserId = 999,
|
||||
Username = "testbot",
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Test reply",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat",
|
||||
replyInfo
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithReplyToOtherUser_ShouldReturnEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var botUser = new User
|
||||
{
|
||||
Id = 999,
|
||||
Username = "testbot",
|
||||
IsBot = true,
|
||||
};
|
||||
|
||||
var botInfoServiceMock = new Mock<BotInfoService>(
|
||||
TestDataBuilder.Mocks.CreateTelegramBotClient().Object,
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<BotInfoService>().Object
|
||||
);
|
||||
botInfoServiceMock.Setup(x => x.GetBotInfoAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(botUser);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
botInfoServiceMock.Object
|
||||
);
|
||||
|
||||
var replyInfo = new ReplyInfo
|
||||
{
|
||||
MessageId = 1,
|
||||
UserId = 123,
|
||||
Username = "otheruser",
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Test reply",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat",
|
||||
replyInfo
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithBotMention_ShouldProcessMessage()
|
||||
{
|
||||
// Arrange
|
||||
var botUser = new User
|
||||
{
|
||||
Id = 999,
|
||||
Username = "testbot",
|
||||
IsBot = true,
|
||||
};
|
||||
|
||||
var botInfoServiceMock = new Mock<BotInfoService>(
|
||||
TestDataBuilder.Mocks.CreateTelegramBotClient().Object,
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<BotInfoService>().Object
|
||||
);
|
||||
botInfoServiceMock.Setup(x => x.GetBotInfoAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(botUser);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
botInfoServiceMock.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Hello @testbot",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat"
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithOtherUserMention_ShouldReturnEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var botUser = new User
|
||||
{
|
||||
Id = 999,
|
||||
Username = "testbot",
|
||||
IsBot = true,
|
||||
};
|
||||
|
||||
var botInfoServiceMock = new Mock<BotInfoService>(
|
||||
TestDataBuilder.Mocks.CreateTelegramBotClient().Object,
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<BotInfoService>().Object
|
||||
);
|
||||
botInfoServiceMock.Setup(x => x.GetBotInfoAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(botUser);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
botInfoServiceMock.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Hello @otheruser",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat"
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithCommand_ShouldExecuteCommand()
|
||||
{
|
||||
// Arrange
|
||||
var commandMock = new Mock<ITelegramCommand>();
|
||||
commandMock.Setup(x => x.CommandName).Returns("/test");
|
||||
commandMock
|
||||
.Setup(x => x.CanHandle(It.IsAny<string>()))
|
||||
.Returns((string msg) => msg.StartsWith("/test"));
|
||||
commandMock
|
||||
.Setup(x =>
|
||||
x.ExecuteAsync(It.IsAny<TelegramCommandContext>(), It.IsAny<CancellationToken>())
|
||||
)
|
||||
.ReturnsAsync("Command executed");
|
||||
|
||||
var commandRegistry = new CommandRegistry(
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<CommandRegistry>().Object,
|
||||
new[] { commandMock.Object }
|
||||
);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
_botInfoService
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"/test argument",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat"
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Command executed");
|
||||
commandMock.Verify(
|
||||
x => x.ExecuteAsync(It.IsAny<TelegramCommandContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithException_ShouldReturnErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var chatServiceMock = new Mock<ChatService>(
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<ChatService>().Object,
|
||||
TestDataBuilder.Mocks.CreateAIServiceMock().Object,
|
||||
TestDataBuilder.Mocks.CreateSessionStorageMock().Object,
|
||||
TestDataBuilder
|
||||
.Mocks.CreateOptionsMock(TestDataBuilder.Configurations.CreateAISettings())
|
||||
.Object,
|
||||
TestDataBuilder.Mocks.CreateCompressionServiceMock().Object
|
||||
);
|
||||
|
||||
chatServiceMock
|
||||
.Setup(x =>
|
||||
x.ProcessMessageAsync(
|
||||
It.IsAny<long>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
)
|
||||
)
|
||||
.ThrowsAsync(new Exception("Test error"));
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
chatServiceMock.Object,
|
||||
_loggerMock.Object,
|
||||
_botInfoService
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Test message",
|
||||
12345L,
|
||||
"user",
|
||||
"private",
|
||||
"Test Chat"
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().Contain("Произошла ошибка");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithGroupChat_ShouldProcessCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var messageText = "Hello";
|
||||
var chatId = -100123456789L; // Group chat ID
|
||||
var username = "testuser";
|
||||
var chatType = "group";
|
||||
var chatTitle = "Test Group";
|
||||
|
||||
// Act
|
||||
var result = await _processor.ProcessMessageAsync(
|
||||
messageText,
|
||||
chatId,
|
||||
username,
|
||||
chatType,
|
||||
chatTitle
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithSupergroupChat_ShouldProcessCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var messageText = "Hello";
|
||||
var chatId = -100123456789L;
|
||||
var username = "testuser";
|
||||
var chatType = "supergroup";
|
||||
var chatTitle = "Test Supergroup";
|
||||
|
||||
// Act
|
||||
var result = await _processor.ProcessMessageAsync(
|
||||
messageText,
|
||||
chatId,
|
||||
username,
|
||||
chatType,
|
||||
chatTitle
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessMessageAsync_WithMultipleMentions_IncludingBot_ShouldProcessMessage()
|
||||
{
|
||||
// Arrange
|
||||
var botUser = new User
|
||||
{
|
||||
Id = 999,
|
||||
Username = "testbot",
|
||||
IsBot = true,
|
||||
};
|
||||
|
||||
var botInfoServiceMock = new Mock<BotInfoService>(
|
||||
TestDataBuilder.Mocks.CreateTelegramBotClient().Object,
|
||||
TestDataBuilder.Mocks.CreateLoggerMock<BotInfoService>().Object
|
||||
);
|
||||
botInfoServiceMock.Setup(x => x.GetBotInfoAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(botUser);
|
||||
|
||||
var processor = new TelegramCommandProcessor(
|
||||
_commandRegistry,
|
||||
_chatService,
|
||||
_loggerMock.Object,
|
||||
botInfoServiceMock.Object
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = await processor.ProcessMessageAsync(
|
||||
"Hello @testbot and @otheruser",
|
||||
12345L,
|
||||
"user",
|
||||
"group",
|
||||
"Test Group"
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace ChatBot.Services
|
||||
/// <summary>
|
||||
/// Process a user message and get AI response
|
||||
/// </summary>
|
||||
public async Task<string> ProcessMessageAsync(
|
||||
public virtual async Task<string> ProcessMessageAsync(
|
||||
long chatId,
|
||||
string username,
|
||||
string message,
|
||||
@@ -192,7 +192,7 @@ namespace ChatBot.Services
|
||||
/// <summary>
|
||||
/// Get session information
|
||||
/// </summary>
|
||||
public ChatSession? GetSession(long chatId)
|
||||
public virtual ChatSession? GetSession(long chatId)
|
||||
{
|
||||
return _sessionStorage.Get(chatId);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,14 @@ namespace ChatBot.Services
|
||||
// Add messages to session
|
||||
foreach (var messageEntity in entity.Messages.OrderBy(m => m.MessageOrder))
|
||||
{
|
||||
var role = Enum.Parse<ChatRole>(messageEntity.Role);
|
||||
var role = messageEntity.Role.ToLowerInvariant() switch
|
||||
{
|
||||
"user" => ChatRole.User,
|
||||
"assistant" => ChatRole.Assistant,
|
||||
"system" => ChatRole.System,
|
||||
"tool" => ChatRole.Tool,
|
||||
_ => throw new ArgumentException($"Unknown role: {messageEntity.Role}")
|
||||
};
|
||||
var message = new ChatMessage { Content = messageEntity.Content, Role = role };
|
||||
session.AddMessage(message);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ChatBot.Services.Telegram.Services
|
||||
/// <summary>
|
||||
/// Получает информацию о боте (с кэшированием и автоматической инвалидацией)
|
||||
/// </summary>
|
||||
public async Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default)
|
||||
public virtual async Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Проверяем, есть ли валидный кэш
|
||||
if (
|
||||
|
||||
@@ -23,10 +23,16 @@ namespace ChatBot.Services.Telegram.Services
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ReplyParameters? replyParameters = null;
|
||||
if (replyToMessageId > 0)
|
||||
{
|
||||
replyParameters = new ReplyParameters { MessageId = replyToMessageId };
|
||||
}
|
||||
|
||||
return await _botClient.SendMessage(
|
||||
chatId: chatId,
|
||||
text: text,
|
||||
replyParameters: replyToMessageId,
|
||||
replyParameters: replyParameters,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user