Add promt fix tests
All checks were successful
SonarQube / Build and analyze (push) Successful in 2m54s

This commit is contained in:
Leonid Pershin
2025-10-21 12:07:56 +03:00
parent ef71568579
commit 1996fec14f
18 changed files with 398 additions and 333 deletions

View File

@@ -0,0 +1,12 @@
---
trigger: always_on
---
MCP предоставляет ассистенту доступ к данным SonarQube. Используй инструменты для:
Поиска проблем: search_sonar_issues_in_projects, search_dependency_risks
Проверки статуса: get_project_quality_gate_status, get_system_status, get_system_health
Анализа кода: analyze_code_snippet, get_raw_source
Работы с задачами: change_sonar_issue_status
Получения метрик: get_component_measures, search_metrics
Не гадай — запрашивай данные. Уточняй ключи проектов и issue. Действуй точно, опираясь на информацию из SonarQube.
Текущий проект ChatBot

View File

@@ -54,7 +54,7 @@ public class ChatServiceIntegrationTests : TestBase
var expectedResponse = "I'm doing well, thank you!"; var expectedResponse = "I'm doing well, thank you!";
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.GetOrCreate(chatId, "private", "")).Returns(session); _sessionStorageMock.Setup(x => x.GetOrCreateAsync(chatId, "private", "")).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
@@ -89,8 +89,8 @@ public class ChatServiceIntegrationTests : TestBase
var expectedResponse = "Hi there!"; var expectedResponse = "Hi there!";
_sessionStorageMock _sessionStorageMock
.Setup(x => x.GetOrCreate(chatId, "private", "")) .Setup(x => x.GetOrCreateAsync(chatId, "private", ""))
.Returns(TestDataBuilder.ChatSessions.CreateBasicSession(chatId)); .ReturnsAsync(TestDataBuilder.ChatSessions.CreateBasicSession(chatId));
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
@@ -106,7 +106,7 @@ public class ChatServiceIntegrationTests : TestBase
// Assert // Assert
result.Should().Be(expectedResponse); result.Should().Be(expectedResponse);
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once); _sessionStorageMock.Verify(x => x.GetOrCreateAsync(chatId, "private", ""), Times.Once);
_sessionStorageMock.Verify( _sessionStorageMock.Verify(
x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()), x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()),
Times.Exactly(2) Times.Exactly(2)
@@ -123,7 +123,7 @@ public class ChatServiceIntegrationTests : TestBase
var expectedResponse = "I didn't receive a message. Could you please try again?"; var expectedResponse = "I didn't receive a message. Could you please try again?";
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.GetOrCreate(chatId, "private", "")).Returns(session); _sessionStorageMock.Setup(x => x.GetOrCreateAsync(chatId, "private", "")).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
@@ -151,7 +151,7 @@ public class ChatServiceIntegrationTests : TestBase
var message = "Hello"; var message = "Hello";
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.GetOrCreate(chatId, "private", "")).Returns(session); _sessionStorageMock.Setup(x => x.GetOrCreateAsync(chatId, "private", "")).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
@@ -179,7 +179,7 @@ public class ChatServiceIntegrationTests : TestBase
var expectedResponse = "Hi there!"; var expectedResponse = "Hi there!";
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 10); // 10 messages var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 10); // 10 messages
_sessionStorageMock.Setup(x => x.GetOrCreate(chatId, "private", "")).Returns(session); _sessionStorageMock.Setup(x => x.GetOrCreateAsync(chatId, "private", "")).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
@@ -211,7 +211,7 @@ public class ChatServiceIntegrationTests : TestBase
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5); var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
// Act // Act
await _chatService.ClearHistoryAsync(chatId); await _chatService.ClearHistoryAsync(chatId);
@@ -226,7 +226,7 @@ public class ChatServiceIntegrationTests : TestBase
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync((ChatBot.Models.ChatSession?)null);
// Act // Act
await _chatService.ClearHistoryAsync(chatId); await _chatService.ClearHistoryAsync(chatId);

View File

@@ -37,7 +37,7 @@ public class ChatServiceTests : UnitTestBase
} }
[Fact] [Fact]
public void GetOrCreateSession_ShouldCreateNewSession_WhenSessionDoesNotExist() public async Task GetOrCreateSession_ShouldCreateNewSession_WhenSessionDoesNotExist()
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
@@ -45,7 +45,7 @@ public class ChatServiceTests : UnitTestBase
var chatTitle = "Test Chat"; var chatTitle = "Test Chat";
// Act // Act
var session = _chatService.GetOrCreateSession(chatId, chatType, chatTitle); var session = await _chatService.GetOrCreateSessionAsync(chatId, chatType, chatTitle);
// Assert // Assert
session.Should().NotBeNull(); session.Should().NotBeNull();
@@ -53,22 +53,22 @@ public class ChatServiceTests : UnitTestBase
session.ChatType.Should().Be(chatType); session.ChatType.Should().Be(chatType);
session.ChatTitle.Should().Be(chatTitle); session.ChatTitle.Should().Be(chatTitle);
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, chatType, chatTitle), Times.Once); _sessionStorageMock.Verify(x => x.GetOrCreateAsync(chatId, chatType, chatTitle), Times.Once);
} }
[Fact] [Fact]
public void GetOrCreateSession_ShouldSetCompressionService_WhenCompressionIsEnabled() public async Task GetOrCreateSession_ShouldSetCompressionService_WhenCompressionIsEnabled()
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
_aiSettings.EnableHistoryCompression = true; _aiSettings.EnableHistoryCompression = true;
// Act // Act
var session = _chatService.GetOrCreateSession(chatId); var session = await _chatService.GetOrCreateSessionAsync(chatId);
// Assert // Assert
session.Should().NotBeNull(); session.Should().NotBeNull();
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once); _sessionStorageMock.Verify(x => x.GetOrCreateAsync(chatId, "private", ""), Times.Once);
} }
[Fact] [Fact]
@@ -191,7 +191,7 @@ public class ChatServiceTests : UnitTestBase
var newModel = "llama3.2"; var newModel = "llama3.2";
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
// Act // Act
await _chatService.UpdateSessionParametersAsync(chatId, newModel); await _chatService.UpdateSessionParametersAsync(chatId, newModel);
@@ -208,7 +208,7 @@ public class ChatServiceTests : UnitTestBase
var chatId = 12345L; var chatId = 12345L;
var newModel = "llama3.2"; var newModel = "llama3.2";
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync((ChatBot.Models.ChatSession?)null);
// Act // Act
await _chatService.UpdateSessionParametersAsync(chatId, newModel); await _chatService.UpdateSessionParametersAsync(chatId, newModel);
@@ -227,7 +227,7 @@ public class ChatServiceTests : UnitTestBase
var chatId = 12345L; var chatId = 12345L;
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5); var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
// Act // Act
await _chatService.ClearHistoryAsync(chatId); await _chatService.ClearHistoryAsync(chatId);
@@ -238,82 +238,82 @@ public class ChatServiceTests : UnitTestBase
} }
[Fact] [Fact]
public void GetSession_ShouldReturnSession_WhenSessionExists() public async Task GetSession_ShouldReturnSession_WhenSessionExists()
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
// Act // Act
var result = _chatService.GetSession(chatId); var result = await _chatService.GetSessionAsync(chatId);
// Assert // Assert
result.Should().Be(session); result.Should().Be(session);
} }
[Fact] [Fact]
public void GetSession_ShouldReturnNull_WhenSessionDoesNotExist() public async Task GetSession_ShouldReturnNull_WhenSessionDoesNotExist()
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns((ChatBot.Models.ChatSession?)null); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync((ChatBot.Models.ChatSession?)null);
// Act // Act
var result = _chatService.GetSession(chatId); var result = await _chatService.GetSessionAsync(chatId);
// Assert // Assert
result.Should().BeNull(); result.Should().BeNull();
} }
[Fact] [Fact]
public void RemoveSession_ShouldReturnTrue_WhenSessionExists() public async Task RemoveSession_ShouldReturnTrue_WhenSessionExists()
{ {
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
_sessionStorageMock.Setup(x => x.Remove(chatId)).Returns(true); _sessionStorageMock.Setup(x => x.RemoveAsync(chatId)).ReturnsAsync(true);
// Act // Act
var result = _chatService.RemoveSession(chatId); var result = await _chatService.RemoveSessionAsync(chatId);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
_sessionStorageMock.Verify(x => x.Remove(chatId), Times.Once); _sessionStorageMock.Verify(x => x.RemoveAsync(chatId), Times.Once);
} }
[Fact] [Fact]
public void GetActiveSessionsCount_ShouldReturnCorrectCount() public async Task GetActiveSessionsCount_ShouldReturnCorrectCount()
{ {
// Arrange // Arrange
var expectedCount = 5; var expectedCount = 5;
_sessionStorageMock.Setup(x => x.GetActiveSessionsCount()).Returns(expectedCount); _sessionStorageMock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount);
// Act // Act
var result = _chatService.GetActiveSessionsCount(); var result = await _chatService.GetActiveSessionsCountAsync();
// Assert // Assert
result.Should().Be(expectedCount); result.Should().Be(expectedCount);
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldReturnCleanedCount() public async Task CleanupOldSessions_ShouldReturnCleanedCount()
{ {
// Arrange // Arrange
var hoursOld = 24; var hoursOld = 24;
var expectedCleaned = 3; var expectedCleaned = 3;
_sessionStorageMock.Setup(x => x.CleanupOldSessions(hoursOld)).Returns(expectedCleaned); _sessionStorageMock.Setup(x => x.CleanupOldSessionsAsync(hoursOld)).ReturnsAsync(expectedCleaned);
// Act // Act
var result = _chatService.CleanupOldSessions(hoursOld); var result = await _chatService.CleanupOldSessionsAsync(hoursOld);
// Assert // Assert
result.Should().Be(expectedCleaned); result.Should().Be(expectedCleaned);
_sessionStorageMock.Verify(x => x.CleanupOldSessions(hoursOld), Times.Once); _sessionStorageMock.Verify(x => x.CleanupOldSessionsAsync(hoursOld), Times.Once);
} }
[Theory] [Theory]
@@ -395,8 +395,8 @@ public class ChatServiceTests : UnitTestBase
var message = "Hello, bot!"; var message = "Hello, bot!";
_sessionStorageMock _sessionStorageMock
.Setup(x => x.GetOrCreate(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.Throws(new Exception("Database connection failed")); .ThrowsAsync(new Exception("Database connection failed"));
// Act // Act
var result = await _chatService.ProcessMessageAsync(chatId, username, message); var result = await _chatService.ProcessMessageAsync(chatId, username, message);
@@ -585,9 +585,9 @@ public class ChatServiceTests : UnitTestBase
session.MaxHistoryLength = 5; // force small history limit session.MaxHistoryLength = 5; // force small history limit
_sessionStorageMock _sessionStorageMock
.Setup(x => x.GetOrCreate(chatId, It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.GetOrCreateAsync(chatId, It.IsAny<string>(), It.IsAny<string>()))
.Returns(session); .ReturnsAsync(session);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
x.GenerateChatCompletionWithCompressionAsync( x.GenerateChatCompletionWithCompressionAsync(
@@ -620,9 +620,9 @@ public class ChatServiceTests : UnitTestBase
session.MaxHistoryLength = 50; // avoid trimming impacting compression assertion session.MaxHistoryLength = 50; // avoid trimming impacting compression assertion
_sessionStorageMock _sessionStorageMock
.Setup(x => x.GetOrCreate(chatId, It.IsAny<string>(), It.IsAny<string>())) .Setup(x => x.GetOrCreateAsync(chatId, It.IsAny<string>(), It.IsAny<string>()))
.Returns(session); .ReturnsAsync(session);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
_aiServiceMock _aiServiceMock
.Setup(x => .Setup(x =>
x.GenerateChatCompletionWithCompressionAsync( x.GenerateChatCompletionWithCompressionAsync(
@@ -692,7 +692,7 @@ public class ChatServiceTests : UnitTestBase
var newModel = "llama3.2"; var newModel = "llama3.2";
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
_sessionStorageMock _sessionStorageMock
.Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>())) .Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()))
.ThrowsAsync(new Exception("Database save failed")); .ThrowsAsync(new Exception("Database save failed"));
@@ -709,7 +709,7 @@ public class ChatServiceTests : UnitTestBase
var chatId = 12345L; var chatId = 12345L;
var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5); var session = TestDataBuilder.ChatSessions.CreateSessionWithMessages(chatId, 5);
_sessionStorageMock.Setup(x => x.Get(chatId)).Returns(session); _sessionStorageMock.Setup(x => x.GetAsync(chatId)).ReturnsAsync(session);
_sessionStorageMock _sessionStorageMock
.Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>())) .Setup(x => x.SaveSessionAsync(It.IsAny<ChatBot.Models.ChatSession>()))
.ThrowsAsync(new Exception("Database save failed")); .ThrowsAsync(new Exception("Database save failed"));
@@ -723,18 +723,18 @@ public class ChatServiceTests : UnitTestBase
[InlineData(0)] [InlineData(0)]
[InlineData(-1)] [InlineData(-1)]
[InlineData(int.MinValue)] [InlineData(int.MinValue)]
public void CleanupOldSessions_ShouldHandleInvalidHoursOld(int hoursOld) public async Task CleanupOldSessions_ShouldHandleInvalidHoursOld(int hoursOld)
{ {
// Arrange // Arrange
var expectedCleaned = 0; var expectedCleaned = 0;
_sessionStorageMock.Setup(x => x.CleanupOldSessions(hoursOld)).Returns(expectedCleaned); _sessionStorageMock.Setup(x => x.CleanupOldSessionsAsync(hoursOld)).ReturnsAsync(expectedCleaned);
// Act // Act
var result = _chatService.CleanupOldSessions(hoursOld); var result = await _chatService.CleanupOldSessionsAsync(hoursOld);
// Assert // Assert
result.Should().Be(expectedCleaned); result.Should().Be(expectedCleaned);
_sessionStorageMock.Verify(x => x.CleanupOldSessions(hoursOld), Times.Once); _sessionStorageMock.Verify(x => x.CleanupOldSessionsAsync(hoursOld), Times.Once);
} }
[Theory] [Theory]
@@ -763,7 +763,7 @@ public class ChatServiceTests : UnitTestBase
// Assert // Assert
result.Should().Be(expectedResponse); result.Should().Be(expectedResponse);
_sessionStorageMock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once); _sessionStorageMock.Verify(x => x.GetOrCreateAsync(chatId, "private", ""), Times.Once);
} }
[Fact] [Fact]

View File

@@ -5,6 +5,7 @@ using ChatBot.Services;
using ChatBot.Tests.TestUtilities; using ChatBot.Tests.TestUtilities;
using FluentAssertions; using FluentAssertions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Moq; using Moq;
@@ -27,6 +28,7 @@ public class DatabaseSessionStorageTests : TestBase
// Add in-memory database // Add in-memory database
services.AddDbContext<ChatBotDbContext>(options => services.AddDbContext<ChatBotDbContext>(options =>
options.UseInMemoryDatabase("TestDatabase") options.UseInMemoryDatabase("TestDatabase")
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
); );
// Add mocked repository // Add mocked repository
@@ -52,7 +54,7 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void GetOrCreate_ShouldReturnExistingSession_WhenSessionExists() public async Task GetOrCreateAsync_ShouldReturnExistingSession_WhenSessionExists()
{ {
// Arrange // Arrange
var existingSession = TestDataBuilder.Mocks.CreateChatSessionEntity(); var existingSession = TestDataBuilder.Mocks.CreateChatSessionEntity();
@@ -61,7 +63,7 @@ public class DatabaseSessionStorageTests : TestBase
.ReturnsAsync(existingSession); .ReturnsAsync(existingSession);
// Act // Act
var result = _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); var result = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -70,14 +72,14 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Get_ShouldReturnSession_WhenSessionExists() public async Task GetAsync_ShouldReturnSession_WhenSessionExists()
{ {
// Arrange // Arrange
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity(); var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity); _repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
// Act // Act
var result = _sessionStorage.Get(12345); var result = await _sessionStorage.GetAsync(12345);
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -86,7 +88,7 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Get_ShouldReturnNull_WhenSessionDoesNotExist() public async Task GetAsync_ShouldReturnNull_WhenSessionDoesNotExist()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
@@ -94,7 +96,7 @@ public class DatabaseSessionStorageTests : TestBase
.ReturnsAsync((ChatSessionEntity?)null); .ReturnsAsync((ChatSessionEntity?)null);
// Act // Act
var result = _sessionStorage.Get(12345); var result = await _sessionStorage.GetAsync(12345);
// Assert // Assert
result.Should().BeNull(); result.Should().BeNull();
@@ -120,13 +122,13 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Remove_ShouldReturnTrue_WhenSessionExists() public async Task RemoveAsync_ShouldReturnTrue_WhenSessionExists()
{ {
// Arrange // Arrange
_repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(true); _repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(true);
// Act // Act
var result = _sessionStorage.Remove(12345); var result = await _sessionStorage.RemoveAsync(12345);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
@@ -134,13 +136,13 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Remove_ShouldReturnFalse_WhenSessionDoesNotExist() public async Task RemoveAsync_ShouldReturnFalse_WhenSessionDoesNotExist()
{ {
// Arrange // Arrange
_repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(false); _repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(false);
// Act // Act
var result = _sessionStorage.Remove(12345); var result = await _sessionStorage.RemoveAsync(12345);
// Assert // Assert
result.Should().BeFalse(); result.Should().BeFalse();
@@ -148,14 +150,14 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void GetActiveSessionsCount_ShouldReturnCorrectCount() public async Task GetActiveSessionsCountAsync_ShouldReturnCorrectCount()
{ {
// Arrange // Arrange
var expectedCount = 5; var expectedCount = 5;
_repositoryMock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount); _repositoryMock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount);
// Act // Act
var result = _sessionStorage.GetActiveSessionsCount(); var result = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert // Assert
result.Should().Be(expectedCount); result.Should().Be(expectedCount);
@@ -163,14 +165,14 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldReturnCorrectCount() public async Task CleanupOldSessionsAsync_ShouldReturnCorrectCount()
{ {
// Arrange // Arrange
var expectedCount = 3; var expectedCount = 3;
_repositoryMock.Setup(x => x.CleanupOldSessionsAsync(24)).ReturnsAsync(expectedCount); _repositoryMock.Setup(x => x.CleanupOldSessionsAsync(24)).ReturnsAsync(expectedCount);
// Act // Act
var result = _sessionStorage.CleanupOldSessions(24); var result = await _sessionStorage.CleanupOldSessionsAsync(24);
// Assert // Assert
result.Should().Be(expectedCount); result.Should().Be(expectedCount);
@@ -178,34 +180,32 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void GetOrCreate_ShouldThrowInvalidOperationException_WhenRepositoryThrows() public async Task GetOrCreateAsync_ShouldThrowInvalidOperationException_WhenRepositoryThrows()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
.Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat")) .Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var act = () => _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); Func<Task> act = async () => await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert // Assert
act.Should() await act.Should()
.Throw<InvalidOperationException>() .ThrowAsync<InvalidOperationException>()
.WithMessage("Failed to get or create session for chat 12345") .WithMessage("Failed to get or create session for chat 12345");
.WithInnerException<Exception>()
.WithMessage("Database error");
} }
[Fact] [Fact]
public void Get_ShouldReturnNull_WhenRepositoryThrows() public async Task GetAsync_ShouldReturnNull_WhenRepositoryThrows()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
.Setup(x => x.GetByChatIdAsync(12345)) .Setup(x => x.GetByChatIdAsync(It.IsAny<long>()))
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var result = _sessionStorage.Get(12345); var result = await _sessionStorage.GetAsync(12345);
// Assert // Assert
result.Should().BeNull(); result.Should().BeNull();
@@ -238,7 +238,7 @@ public class DatabaseSessionStorageTests : TestBase
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var act = async () => await _sessionStorage.SaveSessionAsync(session); Func<Task> act = async () => await _sessionStorage.SaveSessionAsync(session);
// Assert // Assert
var exception = await act.Should() var exception = await act.Should()
@@ -284,22 +284,22 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Remove_ShouldReturnFalse_WhenRepositoryThrows() public async Task RemoveAsync_ShouldReturnFalse_WhenRepositoryThrows()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
.Setup(x => x.DeleteAsync(12345)) .Setup(x => x.DeleteAsync(It.IsAny<long>()))
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var result = _sessionStorage.Remove(12345); var result = await _sessionStorage.RemoveAsync(12345);
// Assert // Assert
result.Should().BeFalse(); result.Should().BeFalse();
} }
[Fact] [Fact]
public void GetActiveSessionsCount_ShouldReturnZero_WhenRepositoryThrows() public async Task GetActiveSessionsCountAsync_ShouldReturnZero_WhenRepositoryThrows()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
@@ -307,14 +307,14 @@ public class DatabaseSessionStorageTests : TestBase
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var result = _sessionStorage.GetActiveSessionsCount(); var result = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert // Assert
result.Should().Be(0); result.Should().Be(0);
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldReturnZero_WhenRepositoryThrows() public async Task CleanupOldSessionsAsync_ShouldReturnZero_WhenRepositoryThrows()
{ {
// Arrange // Arrange
_repositoryMock _repositoryMock
@@ -322,20 +322,21 @@ public class DatabaseSessionStorageTests : TestBase
.ThrowsAsync(new Exception("Database error")); .ThrowsAsync(new Exception("Database error"));
// Act // Act
var result = _sessionStorage.CleanupOldSessions(24); var result = await _sessionStorage.CleanupOldSessionsAsync(24);
// Assert // Assert
result.Should().Be(0); result.Should().Be(0);
} }
[Fact] [Fact]
public void GetOrCreate_WithCompressionService_ShouldSetCompressionService() public async Task GetOrCreateAsync_WithCompressionService_ShouldSetCompressionService()
{ {
// Arrange // Arrange
var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock(); var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
var storageWithCompression = new DatabaseSessionStorage( var storageWithCompression = new DatabaseSessionStorage(
_repositoryMock.Object, _repositoryMock.Object,
Mock.Of<ILogger<DatabaseSessionStorage>>(), Mock.Of<ILogger<DatabaseSessionStorage>>(),
_dbContext,
compressionServiceMock.Object compressionServiceMock.Object
); );
@@ -345,7 +346,7 @@ public class DatabaseSessionStorageTests : TestBase
.ReturnsAsync(sessionEntity); .ReturnsAsync(sessionEntity);
// Act // Act
var result = storageWithCompression.GetOrCreate(12345, "private", "Test Chat"); var result = await storageWithCompression.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -353,7 +354,7 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void Get_WithCompressionService_ShouldSetCompressionService() public async Task GetAsync_WithCompressionService_ShouldSetCompressionService()
{ {
// Arrange // Arrange
var loggerMock = new Mock<ILogger<DatabaseSessionStorage>>(); var loggerMock = new Mock<ILogger<DatabaseSessionStorage>>();
@@ -361,6 +362,7 @@ public class DatabaseSessionStorageTests : TestBase
var storageWithCompression = new DatabaseSessionStorage( var storageWithCompression = new DatabaseSessionStorage(
_repositoryMock.Object, _repositoryMock.Object,
loggerMock.Object, loggerMock.Object,
_dbContext,
compressionServiceMock.Object compressionServiceMock.Object
); );
@@ -380,7 +382,7 @@ public class DatabaseSessionStorageTests : TestBase
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity); _repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
// Act // Act
var result = storageWithCompression.Get(12345); var result = await storageWithCompression.GetAsync(12345);
// Assert // Assert
_repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once); _repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once);
@@ -422,7 +424,7 @@ public class DatabaseSessionStorageTests : TestBase
} }
[Fact] [Fact]
public void GetOrCreate_WithDefaultParameters_ShouldUseDefaults() public async Task GetOrCreateAsync_WithDefaultParameters_ShouldUseDefaults()
{ {
// Arrange // Arrange
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity(); var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
@@ -431,7 +433,7 @@ public class DatabaseSessionStorageTests : TestBase
.ReturnsAsync(sessionEntity); .ReturnsAsync(sessionEntity);
// Act // Act
var result = _sessionStorage.GetOrCreate(12345); var result = await _sessionStorage.GetOrCreateAsync(12345);
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();

View File

@@ -17,13 +17,13 @@ public class InMemorySessionStorageTests
} }
[Fact] [Fact]
public void GetOrCreate_ShouldReturnExistingSession_WhenSessionExists() public async Task GetOrCreateAsync_ShouldReturnExistingSession_WhenSessionExists()
{ {
// Arrange // Arrange
_sessionStorage.GetOrCreate(12345, "private", "Test Chat"); await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Act // Act
var result = _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); var result = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -32,10 +32,10 @@ public class InMemorySessionStorageTests
} }
[Fact] [Fact]
public void GetOrCreate_ShouldCreateNewSession_WhenSessionDoesNotExist() public async Task GetOrCreateAsync_ShouldCreateNewSession_WhenSessionDoesNotExist()
{ {
// Act // Act
var result = _sessionStorage.GetOrCreate(12345, "group", "Test Group"); var result = await _sessionStorage.GetOrCreateAsync(12345, "group", "Test Group");
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -45,10 +45,10 @@ public class InMemorySessionStorageTests
} }
[Fact] [Fact]
public void GetOrCreate_ShouldUseDefaultValues_WhenParametersNotProvided() public async Task GetOrCreateAsync_ShouldUseDefaultValues_WhenParametersNotProvided()
{ {
// Act // Act
var result = _sessionStorage.GetOrCreate(12345); var result = await _sessionStorage.GetOrCreateAsync(12345);
// Assert // Assert
result.Should().NotBeNull(); result.Should().NotBeNull();
@@ -58,23 +58,23 @@ public class InMemorySessionStorageTests
} }
[Fact] [Fact]
public void Get_ShouldReturnSession_WhenSessionExists() public async Task GetAsync_ShouldReturnSession_WhenSessionExists()
{ {
// Arrange // Arrange
var session = _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); var session = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Act // Act
var result = _sessionStorage.Get(12345); var result = await _sessionStorage.GetAsync(12345);
// Assert // Assert
result.Should().BeSameAs(session); result.Should().BeSameAs(session);
} }
[Fact] [Fact]
public void Get_ShouldReturnNull_WhenSessionDoesNotExist() public async Task GetAsync_ShouldReturnNull_WhenSessionDoesNotExist()
{ {
// Act // Act
var result = _sessionStorage.Get(99999); var result = await _sessionStorage.GetAsync(99999);
// Assert // Assert
result.Should().BeNull(); result.Should().BeNull();
@@ -84,7 +84,7 @@ public class InMemorySessionStorageTests
public async Task SaveSessionAsync_ShouldUpdateExistingSession() public async Task SaveSessionAsync_ShouldUpdateExistingSession()
{ {
// Arrange // Arrange
var session = _sessionStorage.GetOrCreate(12345, "private", "Original Title"); var session = await _sessionStorage.GetOrCreateAsync(12345, "private", "Original Title");
session.ChatTitle = "Updated Title"; session.ChatTitle = "Updated Title";
session.LastUpdatedAt = DateTime.UtcNow; session.LastUpdatedAt = DateTime.UtcNow;
@@ -92,7 +92,7 @@ public class InMemorySessionStorageTests
await _sessionStorage.SaveSessionAsync(session); await _sessionStorage.SaveSessionAsync(session);
// Assert // Assert
var savedSession = _sessionStorage.Get(12345); var savedSession = await _sessionStorage.GetAsync(12345);
savedSession.Should().NotBeNull(); savedSession.Should().NotBeNull();
savedSession!.ChatTitle.Should().Be("Updated Title"); savedSession!.ChatTitle.Should().Be("Updated Title");
} }
@@ -101,121 +101,123 @@ public class InMemorySessionStorageTests
public async Task SaveSessionAsync_ShouldAddNewSession() public async Task SaveSessionAsync_ShouldAddNewSession()
{ {
// Arrange // Arrange
var session = _sessionStorage.GetOrCreate(12345, "private", "Original Title"); var session = await _sessionStorage.GetOrCreateAsync(12345, "private", "Original Title");
session.ChatTitle = "New Session"; session.ChatTitle = "New Session";
// Act // Act
await _sessionStorage.SaveSessionAsync(session); await _sessionStorage.SaveSessionAsync(session);
// Assert // Assert
var savedSession = _sessionStorage.Get(12345); var savedSession = await _sessionStorage.GetAsync(12345);
savedSession.Should().NotBeNull(); savedSession.Should().NotBeNull();
savedSession!.ChatTitle.Should().Be("New Session"); savedSession!.ChatTitle.Should().Be("New Session");
} }
[Fact] [Fact]
public void Remove_ShouldReturnTrue_WhenSessionExists() public async Task RemoveAsync_ShouldReturnTrue_WhenSessionExists()
{ {
// Arrange // Arrange
_sessionStorage.GetOrCreate(12345, "private", "Test Chat"); await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Act // Act
var result = _sessionStorage.Remove(12345); var result = await _sessionStorage.RemoveAsync(12345);
// Assert // Assert
result.Should().BeTrue(); result.Should().BeTrue();
_sessionStorage.Get(12345).Should().BeNull(); (await _sessionStorage.GetAsync(12345)).Should().BeNull();
} }
[Fact] [Fact]
public void Remove_ShouldReturnFalse_WhenSessionDoesNotExist() public async Task RemoveAsync_ShouldReturnFalse_WhenSessionDoesNotExist()
{ {
// Act // Act
var result = _sessionStorage.Remove(99999); var result = await _sessionStorage.RemoveAsync(99999);
// Assert // Assert
result.Should().BeFalse(); result.Should().BeFalse();
} }
[Fact] [Fact]
public void GetActiveSessionsCount_ShouldReturnCorrectCount() public async Task GetActiveSessionsCountAsync_ShouldReturnCorrectCount()
{ {
// Arrange // Arrange
_sessionStorage.GetOrCreate(12345, "private", "Chat 1"); await _sessionStorage.GetOrCreateAsync(12345, "private", "Chat 1");
_sessionStorage.GetOrCreate(67890, "group", "Chat 2"); await _sessionStorage.GetOrCreateAsync(67890, "group", "Chat 2");
_sessionStorage.GetOrCreate(11111, "private", "Chat 3"); await _sessionStorage.GetOrCreateAsync(11111, "private", "Chat 3");
// Act // Act
var count = _sessionStorage.GetActiveSessionsCount(); var count = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert // Assert
count.Should().Be(3); count.Should().Be(3);
} }
[Fact] [Fact]
public void GetActiveSessionsCount_ShouldReturnZero_WhenNoSessions() public async Task GetActiveSessionsCountAsync_ShouldReturnZero_WhenNoSessions()
{ {
// Act // Act
var count = _sessionStorage.GetActiveSessionsCount(); var count = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert // Assert
count.Should().Be(0); count.Should().Be(0);
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldDeleteOldSessions() public async Task CleanupOldSessionsAsync_ShouldDeleteOldSessions()
{ {
// Arrange // Arrange
var oldSession = _sessionStorage.GetOrCreate(99999, "private", "Old Chat"); var oldSession = await _sessionStorage.GetOrCreateAsync(99999, "private", "Old Chat");
// Manually set CreatedAt to 2 days ago using test method // Manually set CreatedAt to 2 days ago using test method
oldSession.SetCreatedAtForTesting(DateTime.UtcNow.AddDays(-2)); oldSession.SetCreatedAtForTesting(DateTime.UtcNow.AddDays(-2));
var recentSession = _sessionStorage.GetOrCreate(88888, "private", "Recent Chat"); var recentSession = await _sessionStorage.GetOrCreateAsync(88888, "private", "Recent Chat");
// Manually set CreatedAt to 30 minutes ago using test method // Manually set CreatedAt to 30 minutes ago using test method
recentSession.SetCreatedAtForTesting(DateTime.UtcNow.AddMinutes(-30)); recentSession.SetCreatedAtForTesting(DateTime.UtcNow.AddMinutes(-30));
// Act // Act
_sessionStorage.CleanupOldSessions(1); // Delete sessions older than 1 day await _sessionStorage.CleanupOldSessionsAsync(1); // Delete sessions older than 1 day
// Assert // Assert
_sessionStorage.Get(99999).Should().BeNull(); // Old session should be deleted (await _sessionStorage.GetAsync(99999))
_sessionStorage.Get(88888).Should().NotBeNull(); // Recent session should remain .Should()
.BeNull(); // Old session should be deleted
(await _sessionStorage.GetAsync(88888)).Should().NotBeNull(); // Recent session should remain
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldNotDeleteRecentSessions() public async Task CleanupOldSessionsAsync_ShouldNotDeleteRecentSessions()
{ {
// Arrange // Arrange
var recentSession1 = _sessionStorage.GetOrCreate(12345, "private", "Recent 1"); var recentSession1 = await _sessionStorage.GetOrCreateAsync(12345, "private", "Recent 1");
recentSession1.CreatedAt = DateTime.UtcNow.AddHours(-1); recentSession1.CreatedAt = DateTime.UtcNow.AddHours(-1);
var recentSession2 = _sessionStorage.GetOrCreate(67890, "private", "Recent 2"); var recentSession2 = await _sessionStorage.GetOrCreateAsync(67890, "private", "Recent 2");
recentSession2.CreatedAt = DateTime.UtcNow.AddMinutes(-30); recentSession2.CreatedAt = DateTime.UtcNow.AddMinutes(-30);
// Act // Act
var deletedCount = _sessionStorage.CleanupOldSessions(24); // Delete sessions older than 24 hours var deletedCount = await _sessionStorage.CleanupOldSessionsAsync(24); // Delete sessions older than 24 hours
// Assert // Assert
deletedCount.Should().Be(0); deletedCount.Should().Be(0);
_sessionStorage.Get(12345).Should().NotBeNull(); (await _sessionStorage.GetAsync(12345)).Should().NotBeNull();
_sessionStorage.Get(67890).Should().NotBeNull(); (await _sessionStorage.GetAsync(67890)).Should().NotBeNull();
} }
[Fact] [Fact]
public void CleanupOldSessions_ShouldReturnZero_WhenNoSessions() public async Task CleanupOldSessionsAsync_ShouldReturnZero_WhenNoSessions()
{ {
// Act // Act
var deletedCount = _sessionStorage.CleanupOldSessions(1); var deletedCount = await _sessionStorage.CleanupOldSessionsAsync(1);
// Assert // Assert
deletedCount.Should().Be(0); deletedCount.Should().Be(0);
} }
[Fact] [Fact]
public void GetOrCreate_ShouldCreateSessionWithCorrectTimestamp() public async Task GetOrCreateAsync_ShouldCreateSessionWithCorrectTimestamp()
{ {
// Act // Act
var session = _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); var session = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert // Assert
session.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1)); session.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1));
@@ -226,7 +228,7 @@ public class InMemorySessionStorageTests
public async Task SaveSessionAsync_ShouldUpdateLastUpdatedAt() public async Task SaveSessionAsync_ShouldUpdateLastUpdatedAt()
{ {
// Arrange // Arrange
var session = _sessionStorage.GetOrCreate(12345, "private", "Test Chat"); var session = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
var originalTime = session.LastUpdatedAt; var originalTime = session.LastUpdatedAt;
// Wait a bit to ensure time difference // Wait a bit to ensure time difference
@@ -238,12 +240,12 @@ public class InMemorySessionStorageTests
await _sessionStorage.SaveSessionAsync(session); await _sessionStorage.SaveSessionAsync(session);
// Assert // Assert
var savedSession = _sessionStorage.Get(12345); var savedSession = await _sessionStorage.GetAsync(12345);
savedSession!.LastUpdatedAt.Should().BeAfter(originalTime); savedSession!.LastUpdatedAt.Should().BeAfter(originalTime);
} }
[Fact] [Fact]
public async Task GetOrCreate_ShouldHandleConcurrentAccess() public async Task GetOrCreateAsync_ShouldHandleConcurrentAccess()
{ {
// Arrange // Arrange
var tasks = new List<Task<ChatSession>>(); var tasks = new List<Task<ChatSession>>();
@@ -253,40 +255,46 @@ public class InMemorySessionStorageTests
{ {
var chatId = 1000 + i; var chatId = 1000 + i;
tasks.Add( tasks.Add(
Task.Run(() => _sessionStorage.GetOrCreate(chatId, "private", $"Chat {chatId}")) Task.Run(async () =>
await _sessionStorage.GetOrCreateAsync(chatId, "private", $"Chat {chatId}")
)
); );
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
// Assert // Assert
_sessionStorage.GetActiveSessionsCount().Should().Be(100); (await _sessionStorage.GetActiveSessionsCountAsync())
.Should()
.Be(100);
// Verify all sessions were created // Verify all sessions were created
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
var chatId = 1000 + i; var chatId = 1000 + i;
var session = _sessionStorage.Get(chatId); var session = await _sessionStorage.GetAsync(chatId);
session.Should().NotBeNull(); session.Should().NotBeNull();
session!.ChatId.Should().Be(chatId); session!.ChatId.Should().Be(chatId);
} }
} }
[Fact] [Fact]
public void Remove_ShouldDecreaseActiveSessionsCount() public async Task RemoveAsync_ShouldDecreaseActiveSessionsCount()
{ {
// Arrange // Arrange
_sessionStorage.GetOrCreate(12345, "private", "Chat 1"); await _sessionStorage.GetOrCreateAsync(12345, "private", "Chat 1");
_sessionStorage.GetOrCreate(67890, "private", "Chat 2"); await _sessionStorage.GetOrCreateAsync(67890, "private", "Chat 2");
_sessionStorage.GetOrCreate(11111, "private", "Chat 3"); await _sessionStorage.GetOrCreateAsync(11111, "private", "Chat 3");
// Act // Act
_sessionStorage.Remove(67890); await _sessionStorage.RemoveAsync(67890);
// Assert // Assert
_sessionStorage.GetActiveSessionsCount().Should().Be(2); (await _sessionStorage.GetActiveSessionsCountAsync())
_sessionStorage.Get(12345).Should().NotBeNull(); .Should()
_sessionStorage.Get(67890).Should().BeNull(); .Be(2);
_sessionStorage.Get(11111).Should().NotBeNull(); (await _sessionStorage.GetAsync(12345)).Should().NotBeNull();
(await _sessionStorage.GetAsync(67890)).Should().BeNull();
(await _sessionStorage.GetAsync(11111)).Should().NotBeNull();
} }
} }

View File

@@ -19,41 +19,41 @@ public class ISessionStorageTests : UnitTestBase
// Assert // Assert
methods.Should().HaveCount(6); methods.Should().HaveCount(6);
// GetOrCreate method // GetOrCreateAsync method
var getOrCreateMethod = methods.FirstOrDefault(m => m.Name == "GetOrCreate"); var getOrCreateMethod = methods.FirstOrDefault(m => m.Name == "GetOrCreateAsync");
getOrCreateMethod.Should().NotBeNull(); getOrCreateMethod.Should().NotBeNull();
getOrCreateMethod!.ReturnType.Should().Be<ChatSession>(); getOrCreateMethod!.ReturnType.Should().Be(typeof(Task<ChatSession>));
getOrCreateMethod.GetParameters().Should().HaveCount(3); getOrCreateMethod.GetParameters().Should().HaveCount(3);
getOrCreateMethod.GetParameters()[0].ParameterType.Should().Be<long>(); getOrCreateMethod.GetParameters()[0].ParameterType.Should().Be<long>();
getOrCreateMethod.GetParameters()[1].ParameterType.Should().Be<string>(); getOrCreateMethod.GetParameters()[1].ParameterType.Should().Be<string>();
getOrCreateMethod.GetParameters()[2].ParameterType.Should().Be<string>(); getOrCreateMethod.GetParameters()[2].ParameterType.Should().Be<string>();
// Get method // GetAsync method
var getMethod = methods.FirstOrDefault(m => m.Name == "Get"); var getMethod = methods.FirstOrDefault(m => m.Name == "GetAsync");
getMethod.Should().NotBeNull(); getMethod.Should().NotBeNull();
getMethod!.ReturnType.Should().Be<ChatSession>(); getMethod!.ReturnType.Should().Be(typeof(Task<ChatSession?>));
getMethod.GetParameters().Should().HaveCount(1); getMethod.GetParameters().Should().HaveCount(1);
getMethod.GetParameters()[0].ParameterType.Should().Be<long>(); getMethod.GetParameters()[0].ParameterType.Should().Be<long>();
// Remove method // RemoveAsync method
var removeMethod = methods.FirstOrDefault(m => m.Name == "Remove"); var removeMethod = methods.FirstOrDefault(m => m.Name == "RemoveAsync");
removeMethod.Should().NotBeNull(); removeMethod.Should().NotBeNull();
removeMethod!.ReturnType.Should().Be<bool>(); removeMethod!.ReturnType.Should().Be(typeof(Task<bool>));
removeMethod.GetParameters().Should().HaveCount(1); removeMethod.GetParameters().Should().HaveCount(1);
removeMethod.GetParameters()[0].ParameterType.Should().Be<long>(); removeMethod.GetParameters()[0].ParameterType.Should().Be<long>();
// GetActiveSessionsCount method // GetActiveSessionsCountAsync method
var getActiveSessionsCountMethod = methods.FirstOrDefault(m => var getActiveSessionsCountMethod = methods.FirstOrDefault(m =>
m.Name == "GetActiveSessionsCount" m.Name == "GetActiveSessionsCountAsync"
); );
getActiveSessionsCountMethod.Should().NotBeNull(); getActiveSessionsCountMethod.Should().NotBeNull();
getActiveSessionsCountMethod!.ReturnType.Should().Be<int>(); getActiveSessionsCountMethod!.ReturnType.Should().Be(typeof(Task<int>));
getActiveSessionsCountMethod.GetParameters().Should().BeEmpty(); getActiveSessionsCountMethod.GetParameters().Should().BeEmpty();
// CleanupOldSessions method // CleanupOldSessionsAsync method
var cleanupOldSessionsMethod = methods.FirstOrDefault(m => m.Name == "CleanupOldSessions"); var cleanupOldSessionsMethod = methods.FirstOrDefault(m => m.Name == "CleanupOldSessionsAsync");
cleanupOldSessionsMethod.Should().NotBeNull(); cleanupOldSessionsMethod.Should().NotBeNull();
cleanupOldSessionsMethod!.ReturnType.Should().Be<int>(); cleanupOldSessionsMethod!.ReturnType.Should().Be(typeof(Task<int>));
cleanupOldSessionsMethod.GetParameters().Should().HaveCount(1); cleanupOldSessionsMethod.GetParameters().Should().HaveCount(1);
cleanupOldSessionsMethod.GetParameters()[0].ParameterType.Should().Be<int>(); cleanupOldSessionsMethod.GetParameters()[0].ParameterType.Should().Be<int>();
@@ -88,7 +88,7 @@ public class ISessionStorageTests : UnitTestBase
} }
[Fact] [Fact]
public void ISessionStorage_GetOrCreate_ShouldReturnChatSession() public async Task ISessionStorage_GetOrCreateAsync_ShouldReturnChatSession()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
@@ -97,120 +97,120 @@ public class ISessionStorageTests : UnitTestBase
var chatTitle = "Test Chat"; var chatTitle = "Test Chat";
var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, chatType); var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, chatType);
mock.Setup(x => x.GetOrCreate(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>())) mock.Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(expectedSession); .ReturnsAsync(expectedSession);
// Act // Act
var result = mock.Object.GetOrCreate(chatId, chatType, chatTitle); var result = await mock.Object.GetOrCreateAsync(chatId, chatType, chatTitle);
// Assert // Assert
result.Should().Be(expectedSession); result.Should().Be(expectedSession);
mock.Verify(x => x.GetOrCreate(chatId, chatType, chatTitle), Times.Once); mock.Verify(x => x.GetOrCreateAsync(chatId, chatType, chatTitle), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_Get_ShouldReturnChatSessionOrNull() public async Task ISessionStorage_GetAsync_ShouldReturnChatSessionOrNull()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var chatId = 12345L; var chatId = 12345L;
var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, "private"); var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, "private");
mock.Setup(x => x.Get(It.IsAny<long>())).Returns(expectedSession); mock.Setup(x => x.GetAsync(It.IsAny<long>())).ReturnsAsync(expectedSession);
// Act // Act
var result = mock.Object.Get(chatId); var result = await mock.Object.GetAsync(chatId);
// Assert // Assert
result.Should().Be(expectedSession); result.Should().Be(expectedSession);
mock.Verify(x => x.Get(chatId), Times.Once); mock.Verify(x => x.GetAsync(chatId), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_Get_ShouldReturnNullWhenSessionNotFound() public async Task ISessionStorage_GetAsync_ShouldReturnNullWhenSessionNotFound()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var chatId = 12345L; var chatId = 12345L;
mock.Setup(x => x.Get(It.IsAny<long>())).Returns((ChatSession?)null); mock.Setup(x => x.GetAsync(It.IsAny<long>())).ReturnsAsync((ChatSession?)null);
// Act // Act
var result = mock.Object.Get(chatId); var result = await mock.Object.GetAsync(chatId);
// Assert // Assert
result.Should().BeNull(); result.Should().BeNull();
mock.Verify(x => x.Get(chatId), Times.Once); mock.Verify(x => x.GetAsync(chatId), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_Remove_ShouldReturnBoolean() public async Task ISessionStorage_RemoveAsync_ShouldReturnBoolean()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var chatId = 12345L; var chatId = 12345L;
var expectedResult = true; var expectedResult = true;
mock.Setup(x => x.Remove(It.IsAny<long>())).Returns(expectedResult); mock.Setup(x => x.RemoveAsync(It.IsAny<long>())).ReturnsAsync(expectedResult);
// Act // Act
var result = mock.Object.Remove(chatId); var result = await mock.Object.RemoveAsync(chatId);
// Assert // Assert
result.Should().Be(expectedResult); result.Should().Be(expectedResult);
mock.Verify(x => x.Remove(chatId), Times.Once); mock.Verify(x => x.RemoveAsync(chatId), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_GetActiveSessionsCount_ShouldReturnInt() public async Task ISessionStorage_GetActiveSessionsCountAsync_ShouldReturnInt()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var expectedCount = 5; var expectedCount = 5;
mock.Setup(x => x.GetActiveSessionsCount()).Returns(expectedCount); mock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount);
// Act // Act
var result = mock.Object.GetActiveSessionsCount(); var result = await mock.Object.GetActiveSessionsCountAsync();
// Assert // Assert
result.Should().Be(expectedCount); result.Should().Be(expectedCount);
mock.Verify(x => x.GetActiveSessionsCount(), Times.Once); mock.Verify(x => x.GetActiveSessionsCountAsync(), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_CleanupOldSessions_ShouldReturnInt() public async Task ISessionStorage_CleanupOldSessionsAsync_ShouldReturnInt()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var hoursOld = 24; var hoursOld = 24;
var expectedCleanedCount = 3; var expectedCleanedCount = 3;
mock.Setup(x => x.CleanupOldSessions(It.IsAny<int>())).Returns(expectedCleanedCount); mock.Setup(x => x.CleanupOldSessionsAsync(It.IsAny<int>())).ReturnsAsync(expectedCleanedCount);
// Act // Act
var result = mock.Object.CleanupOldSessions(hoursOld); var result = await mock.Object.CleanupOldSessionsAsync(hoursOld);
// Assert // Assert
result.Should().Be(expectedCleanedCount); result.Should().Be(expectedCleanedCount);
mock.Verify(x => x.CleanupOldSessions(hoursOld), Times.Once); mock.Verify(x => x.CleanupOldSessionsAsync(hoursOld), Times.Once);
} }
[Fact] [Fact]
public void ISessionStorage_CleanupOldSessions_ShouldUseDefaultValue() public async Task ISessionStorage_CleanupOldSessionsAsync_ShouldUseDefaultValue()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var expectedCleanedCount = 2; var expectedCleanedCount = 2;
mock.Setup(x => x.CleanupOldSessions(It.IsAny<int>())).Returns(expectedCleanedCount); mock.Setup(x => x.CleanupOldSessionsAsync(It.IsAny<int>())).ReturnsAsync(expectedCleanedCount);
// Act // Act
var result = mock.Object.CleanupOldSessions(); var result = await mock.Object.CleanupOldSessionsAsync();
// Assert // Assert
result.Should().Be(expectedCleanedCount); result.Should().Be(expectedCleanedCount);
mock.Verify(x => x.CleanupOldSessions(24), Times.Once); // Default value is 24 mock.Verify(x => x.CleanupOldSessionsAsync(24), Times.Once); // Default value is 24
} }
[Fact] [Fact]
@@ -230,22 +230,22 @@ public class ISessionStorageTests : UnitTestBase
} }
[Fact] [Fact]
public void ISessionStorage_GetOrCreate_ShouldUseDefaultValues() public async Task ISessionStorage_GetOrCreateAsync_ShouldUseDefaultValues()
{ {
// Arrange // Arrange
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var chatId = 12345L; var chatId = 12345L;
var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, "private"); var expectedSession = TestDataBuilder.ChatSessions.CreateBasicSession(chatId, "private");
mock.Setup(x => x.GetOrCreate(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>())) mock.Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(expectedSession); .ReturnsAsync(expectedSession);
// Act // Act
var result = mock.Object.GetOrCreate(chatId); var result = await mock.Object.GetOrCreateAsync(chatId);
// Assert // Assert
result.Should().Be(expectedSession); result.Should().Be(expectedSession);
mock.Verify(x => x.GetOrCreate(chatId, "private", ""), Times.Once); // Default values mock.Verify(x => x.GetOrCreateAsync(chatId, "private", ""), Times.Once); // Default values
} }
[Fact] [Fact]
@@ -280,15 +280,15 @@ public class ISessionStorageTests : UnitTestBase
// All methods should be public // All methods should be public
methods.All(m => m.IsPublic).Should().BeTrue(); methods.All(m => m.IsPublic).Should().BeTrue();
// GetOrCreate should have default parameters // GetOrCreateAsync should have default parameters
var getOrCreateMethod = methods.First(m => m.Name == "GetOrCreate"); var getOrCreateMethod = methods.First(m => m.Name == "GetOrCreateAsync");
getOrCreateMethod.GetParameters()[1].HasDefaultValue.Should().BeTrue(); getOrCreateMethod.GetParameters()[1].HasDefaultValue.Should().BeTrue();
getOrCreateMethod.GetParameters()[1].DefaultValue.Should().Be("private"); getOrCreateMethod.GetParameters()[1].DefaultValue.Should().Be("private");
getOrCreateMethod.GetParameters()[2].HasDefaultValue.Should().BeTrue(); getOrCreateMethod.GetParameters()[2].HasDefaultValue.Should().BeTrue();
getOrCreateMethod.GetParameters()[2].DefaultValue.Should().Be(""); getOrCreateMethod.GetParameters()[2].DefaultValue.Should().Be("");
// CleanupOldSessions should have default parameter // CleanupOldSessionsAsync should have default parameter
var cleanupMethod = methods.First(m => m.Name == "CleanupOldSessions"); var cleanupMethod = methods.First(m => m.Name == "CleanupOldSessionsAsync");
cleanupMethod.GetParameters()[0].HasDefaultValue.Should().BeTrue(); cleanupMethod.GetParameters()[0].HasDefaultValue.Should().BeTrue();
cleanupMethod.GetParameters()[0].DefaultValue.Should().Be(24); cleanupMethod.GetParameters()[0].DefaultValue.Should().Be(24);
} }

View File

@@ -44,7 +44,7 @@ public class SettingsCommandTests : UnitTestBase
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId); var session = TestDataBuilder.ChatSessions.CreateBasicSession(chatId);
_chatServiceMock.Setup(x => x.GetSession(chatId)).Returns(session); _chatServiceMock.Setup(x => x.GetSessionAsync(chatId)).ReturnsAsync(session);
var context = new TelegramCommandContext var context = new TelegramCommandContext
{ {
@@ -70,8 +70,8 @@ public class SettingsCommandTests : UnitTestBase
// Arrange // Arrange
var chatId = 12345L; var chatId = 12345L;
_chatServiceMock _chatServiceMock
.Setup(x => x.GetSession(chatId)) .Setup(x => x.GetSessionAsync(chatId))
.Returns((ChatBot.Models.ChatSession?)null); .ReturnsAsync((ChatBot.Models.ChatSession?)null);
var context = new TelegramCommandContext var context = new TelegramCommandContext
{ {

View File

@@ -375,7 +375,7 @@ public class StatusCommandTests : UnitTestBase
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private"); var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
session.AddUserMessage("Test", "user"); session.AddUserMessage("Test", "user");
chatServiceMock.Setup(x => x.GetSession(12345)).Returns(session); chatServiceMock.Setup(x => x.GetSessionAsync(12345)).ReturnsAsync(session);
var statusCommand = new StatusCommand( var statusCommand = new StatusCommand(
chatServiceMock.Object, chatServiceMock.Object,

View File

@@ -236,7 +236,7 @@ public class TelegramCommandProcessorTests : UnitTestBase
var chatTitle = "Test Chat"; var chatTitle = "Test Chat";
// Act // Act
var result = await _processor.ProcessMessageAsync( Func<Task> act = async () => await _processor.ProcessMessageAsync(
messageText, messageText,
chatId, chatId,
username, username,
@@ -245,7 +245,8 @@ public class TelegramCommandProcessorTests : UnitTestBase
); );
// Assert // Assert
result.Should().NotBeNull(); await act.Should().ThrowAsync<ArgumentException>()
.WithMessage("ChatId cannot be 0*");
} }
[Fact] [Fact]
@@ -746,7 +747,7 @@ public class TelegramCommandProcessorTests : UnitTestBase
); );
// Assert // Assert
result.Should().Contain("Произошла ошибка"); result.Should().Contain("Произошла непредвиденная ошибка");
} }
[Fact] [Fact]

View File

@@ -152,9 +152,8 @@ public static class TestDataBuilder
var mock = new Mock<ISessionStorage>(); var mock = new Mock<ISessionStorage>();
var sessions = new Dictionary<long, ChatSession>(); var sessions = new Dictionary<long, ChatSession>();
mock.Setup(x => x.GetOrCreate(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>())) mock.Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns<long, string, string>( .ReturnsAsync((long chatId, string chatType, string chatTitle) =>
(chatId, chatType, chatTitle) =>
{ {
if (!sessions.TryGetValue(chatId, out var session)) if (!sessions.TryGetValue(chatId, out var session))
{ {
@@ -166,11 +165,10 @@ public static class TestDataBuilder
sessions[chatId] = session; sessions[chatId] = session;
} }
return session; return session;
} });
);
mock.Setup(x => x.Get(It.IsAny<long>())) mock.Setup(x => x.GetAsync(It.IsAny<long>()))
.Returns<long>(chatId => .ReturnsAsync((long chatId) =>
sessions.TryGetValue(chatId, out var session) ? session : null sessions.TryGetValue(chatId, out var session) ? session : null
); );
@@ -181,12 +179,12 @@ public static class TestDataBuilder
return Task.CompletedTask; return Task.CompletedTask;
}); });
mock.Setup(x => x.Remove(It.IsAny<long>())) mock.Setup(x => x.RemoveAsync(It.IsAny<long>()))
.Returns<long>(chatId => sessions.Remove(chatId)); .ReturnsAsync((long chatId) => sessions.Remove(chatId));
mock.Setup(x => x.GetActiveSessionsCount()).Returns(() => sessions.Count); mock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(() => sessions.Count);
mock.Setup(x => x.CleanupOldSessions(It.IsAny<int>())).Returns<int>(hoursOld => 0); mock.Setup(x => x.CleanupOldSessionsAsync(It.IsAny<int>())).ReturnsAsync((int hoursOld) => 0);
return mock; return mock;
} }

View File

@@ -10,6 +10,7 @@ namespace ChatBot.Models
public class ChatSession public class ChatSession
{ {
private readonly object _lock = new object(); private readonly object _lock = new object();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly List<ChatMessage> _messageHistory = new List<ChatMessage>(); private readonly List<ChatMessage> _messageHistory = new List<ChatMessage>();
private IHistoryCompressionService? _compressionService; private IHistoryCompressionService? _compressionService;
@@ -110,11 +111,11 @@ namespace ChatBot.Models
int compressionTarget int compressionTarget
) )
{ {
lock (_lock) await _semaphore.WaitAsync();
try
{ {
_messageHistory.Add(message); _messageHistory.Add(message);
LastUpdatedAt = DateTime.UtcNow; LastUpdatedAt = DateTime.UtcNow;
}
// Check if compression is needed and perform it asynchronously // Check if compression is needed and perform it asynchronously
if ( if (
@@ -130,15 +131,21 @@ namespace ChatBot.Models
await TrimHistoryAsync(); await TrimHistoryAsync();
} }
} }
finally
{
_semaphore.Release();
}
}
/// <summary> /// <summary>
/// Compress message history using the compression service /// Compress message history using the compression service
/// Note: This method should be called within a semaphore lock
/// </summary> /// </summary>
private async Task CompressHistoryAsync(int targetCount) private async Task CompressHistoryAsync(int targetCount)
{ {
if (_compressionService == null) if (_compressionService == null)
{ {
await TrimHistoryAsync(); TrimHistoryInternal();
return; return;
} }
@@ -149,29 +156,33 @@ namespace ChatBot.Models
targetCount targetCount
); );
lock (_lock)
{
_messageHistory.Clear(); _messageHistory.Clear();
_messageHistory.AddRange(compressedMessages); _messageHistory.AddRange(compressedMessages);
LastUpdatedAt = DateTime.UtcNow; LastUpdatedAt = DateTime.UtcNow;
} }
}
catch (Exception) catch (Exception)
{ {
// Log error and fallback to simple trimming // Log error and fallback to simple trimming
// Note: We can't inject ILogger here, so we'll just fallback // Note: We can't inject ILogger here, so we'll just fallback
await TrimHistoryAsync(); TrimHistoryInternal();
} }
} }
/// <summary> /// <summary>
/// Simple history trimming without compression /// Simple history trimming without compression (async wrapper)
/// Note: This method should be called within a semaphore lock
/// </summary> /// </summary>
private async Task TrimHistoryAsync() private Task TrimHistoryAsync()
{ {
await Task.Run(() => TrimHistoryInternal();
{ return Task.CompletedTask;
lock (_lock) }
/// <summary>
/// Internal method to trim history without async overhead
/// Note: This method should be called within a semaphore lock
/// </summary>
private void TrimHistoryInternal()
{ {
if (_messageHistory.Count > MaxHistoryLength) if (_messageHistory.Count > MaxHistoryLength)
{ {
@@ -192,8 +203,6 @@ namespace ChatBot.Models
LastUpdatedAt = DateTime.UtcNow; LastUpdatedAt = DateTime.UtcNow;
} }
} }
});
}
/// <summary> /// <summary>
/// Add a user message with username information /// Add a user message with username information

View File

@@ -35,13 +35,13 @@ namespace ChatBot.Services
/// <summary> /// <summary>
/// Get or create a chat session for the given chat ID /// Get or create a chat session for the given chat ID
/// </summary> /// </summary>
public ChatSession GetOrCreateSession( public async Task<ChatSession> GetOrCreateSessionAsync(
long chatId, long chatId,
string chatType = ChatTypes.Private, string chatType = ChatTypes.Private,
string chatTitle = "" string chatTitle = ""
) )
{ {
var session = _sessionStorage.GetOrCreate(chatId, chatType, chatTitle); var session = await _sessionStorage.GetOrCreateAsync(chatId, chatType, chatTitle);
// Set compression service if compression is enabled // Set compression service if compression is enabled
if (_aiSettings.EnableHistoryCompression) if (_aiSettings.EnableHistoryCompression)
@@ -66,7 +66,7 @@ namespace ChatBot.Services
{ {
try try
{ {
var session = GetOrCreateSession(chatId, chatType, chatTitle); var session = await GetOrCreateSessionAsync(chatId, chatType, chatTitle);
// Add user message to history with username // Add user message to history with username
if (_aiSettings.EnableHistoryCompression) if (_aiSettings.EnableHistoryCompression)
@@ -157,7 +157,7 @@ namespace ChatBot.Services
/// </summary> /// </summary>
public async Task UpdateSessionParametersAsync(long chatId, string? model = null) public async Task UpdateSessionParametersAsync(long chatId, string? model = null)
{ {
var session = _sessionStorage.Get(chatId); var session = await _sessionStorage.GetAsync(chatId);
if (session != null) if (session != null)
{ {
if (!string.IsNullOrEmpty(model)) if (!string.IsNullOrEmpty(model))
@@ -177,7 +177,7 @@ namespace ChatBot.Services
/// </summary> /// </summary>
public virtual async Task ClearHistoryAsync(long chatId) public virtual async Task ClearHistoryAsync(long chatId)
{ {
var session = _sessionStorage.Get(chatId); var session = await _sessionStorage.GetAsync(chatId);
if (session != null) if (session != null)
{ {
session.ClearHistory(); session.ClearHistory();
@@ -192,33 +192,33 @@ namespace ChatBot.Services
/// <summary> /// <summary>
/// Get session information /// Get session information
/// </summary> /// </summary>
public virtual ChatSession? GetSession(long chatId) public virtual async Task<ChatSession?> GetSessionAsync(long chatId)
{ {
return _sessionStorage.Get(chatId); return await _sessionStorage.GetAsync(chatId);
} }
/// <summary> /// <summary>
/// Remove a session /// Remove a session
/// </summary> /// </summary>
public bool RemoveSession(long chatId) public async Task<bool> RemoveSessionAsync(long chatId)
{ {
return _sessionStorage.Remove(chatId); return await _sessionStorage.RemoveAsync(chatId);
} }
/// <summary> /// <summary>
/// Get all active sessions count /// Get all active sessions count
/// </summary> /// </summary>
public int GetActiveSessionsCount() public async Task<int> GetActiveSessionsCountAsync()
{ {
return _sessionStorage.GetActiveSessionsCount(); return await _sessionStorage.GetActiveSessionsCountAsync();
} }
/// <summary> /// <summary>
/// Clean up old sessions (older than specified hours) /// Clean up old sessions (older than specified hours)
/// </summary> /// </summary>
public int CleanupOldSessions(int hoursOld = 24) public async Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
{ {
return _sessionStorage.CleanupOldSessions(hoursOld); return await _sessionStorage.CleanupOldSessionsAsync(hoursOld);
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using ChatBot.Data;
using ChatBot.Data.Interfaces; using ChatBot.Data.Interfaces;
using ChatBot.Models; using ChatBot.Models;
using ChatBot.Models.Dto; using ChatBot.Models.Dto;
@@ -15,19 +16,22 @@ namespace ChatBot.Services
private readonly IChatSessionRepository _repository; private readonly IChatSessionRepository _repository;
private readonly ILogger<DatabaseSessionStorage> _logger; private readonly ILogger<DatabaseSessionStorage> _logger;
private readonly IHistoryCompressionService? _compressionService; private readonly IHistoryCompressionService? _compressionService;
private readonly ChatBotDbContext _context;
public DatabaseSessionStorage( public DatabaseSessionStorage(
IChatSessionRepository repository, IChatSessionRepository repository,
ILogger<DatabaseSessionStorage> logger, ILogger<DatabaseSessionStorage> logger,
ChatBotDbContext context,
IHistoryCompressionService? compressionService = null IHistoryCompressionService? compressionService = null
) )
{ {
_repository = repository; _repository = repository;
_logger = logger; _logger = logger;
_context = context;
_compressionService = compressionService; _compressionService = compressionService;
} }
public ChatSession GetOrCreate( public async Task<ChatSession> GetOrCreateAsync(
long chatId, long chatId,
string chatType = "private", string chatType = "private",
string chatTitle = "" string chatTitle = ""
@@ -35,10 +39,7 @@ namespace ChatBot.Services
{ {
try try
{ {
var sessionEntity = _repository var sessionEntity = await _repository.GetOrCreateAsync(chatId, chatType, chatTitle);
.GetOrCreateAsync(chatId, chatType, chatTitle)
.GetAwaiter()
.GetResult();
return ConvertToChatSession(sessionEntity); return ConvertToChatSession(sessionEntity);
} }
catch (Exception ex) catch (Exception ex)
@@ -51,11 +52,11 @@ namespace ChatBot.Services
} }
} }
public ChatSession? Get(long chatId) public async Task<ChatSession?> GetAsync(long chatId)
{ {
try try
{ {
var sessionEntity = _repository.GetByChatIdAsync(chatId).GetAwaiter().GetResult(); var sessionEntity = await _repository.GetByChatIdAsync(chatId);
return sessionEntity != null ? ConvertToChatSession(sessionEntity) : null; return sessionEntity != null ? ConvertToChatSession(sessionEntity) : null;
} }
catch (Exception ex) catch (Exception ex)
@@ -65,11 +66,11 @@ namespace ChatBot.Services
} }
} }
public bool Remove(long chatId) public async Task<bool> RemoveAsync(long chatId)
{ {
try try
{ {
return _repository.DeleteAsync(chatId).GetAwaiter().GetResult(); return await _repository.DeleteAsync(chatId);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -78,11 +79,11 @@ namespace ChatBot.Services
} }
} }
public int GetActiveSessionsCount() public async Task<int> GetActiveSessionsCountAsync()
{ {
try try
{ {
return _repository.GetActiveSessionsCountAsync().GetAwaiter().GetResult(); return await _repository.GetActiveSessionsCountAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -91,11 +92,11 @@ namespace ChatBot.Services
} }
} }
public int CleanupOldSessions(int hoursOld = 24) public async Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
{ {
try try
{ {
return _repository.CleanupOldSessionsAsync(hoursOld).GetAwaiter().GetResult(); return await _repository.CleanupOldSessionsAsync(hoursOld);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -105,10 +106,11 @@ namespace ChatBot.Services
} }
/// <summary> /// <summary>
/// Save session changes to database /// Save session changes to database with transaction support
/// </summary> /// </summary>
public async Task SaveSessionAsync(ChatSession session) public async Task SaveSessionAsync(ChatSession session)
{ {
await using var transaction = await _context.Database.BeginTransactionAsync();
try try
{ {
var sessionEntity = await _repository.GetByChatIdAsync(session.ChatId); var sessionEntity = await _repository.GetByChatIdAsync(session.ChatId);
@@ -126,7 +128,7 @@ namespace ChatBot.Services
sessionEntity.MaxHistoryLength = session.MaxHistoryLength; sessionEntity.MaxHistoryLength = session.MaxHistoryLength;
sessionEntity.LastUpdatedAt = DateTime.UtcNow; sessionEntity.LastUpdatedAt = DateTime.UtcNow;
// Clear existing messages and add new ones // Clear existing messages and add new ones in a transaction
await _repository.ClearMessagesAsync(sessionEntity.Id); await _repository.ClearMessagesAsync(sessionEntity.Id);
var messages = session.GetAllMessages(); var messages = session.GetAllMessages();
@@ -141,9 +143,14 @@ namespace ChatBot.Services
} }
await _repository.UpdateAsync(sessionEntity); await _repository.UpdateAsync(sessionEntity);
// Commit transaction if all operations succeeded
await transaction.CommitAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
// Transaction will be automatically rolled back on exception
await transaction.RollbackAsync();
_logger.LogError(ex, "Failed to save session for chat {ChatId}", session.ChatId); _logger.LogError(ex, "Failed to save session for chat {ChatId}", session.ChatId);
throw new InvalidOperationException( throw new InvalidOperationException(
$"Failed to save session for chat {session.ChatId}", $"Failed to save session for chat {session.ChatId}",

View File

@@ -17,7 +17,7 @@ namespace ChatBot.Services
_logger = logger; _logger = logger;
} }
public ChatSession GetOrCreate( public Task<ChatSession> GetOrCreateAsync(
long chatId, long chatId,
string chatType = "private", string chatType = "private",
string chatTitle = "" string chatTitle = ""
@@ -54,31 +54,31 @@ namespace ChatBot.Services
} }
} }
return session; return Task.FromResult(session);
} }
public ChatSession? Get(long chatId) public Task<ChatSession?> GetAsync(long chatId)
{ {
_sessions.TryGetValue(chatId, out var session); _sessions.TryGetValue(chatId, out var session);
return session; return Task.FromResult(session);
} }
public bool Remove(long chatId) public Task<bool> RemoveAsync(long chatId)
{ {
var removed = _sessions.TryRemove(chatId, out _); var removed = _sessions.TryRemove(chatId, out _);
if (removed) if (removed)
{ {
_logger.LogInformation("Removed session for chat {ChatId}", chatId); _logger.LogInformation("Removed session for chat {ChatId}", chatId);
} }
return removed; return Task.FromResult(removed);
} }
public int GetActiveSessionsCount() public Task<int> GetActiveSessionsCountAsync()
{ {
return _sessions.Count; return Task.FromResult(_sessions.Count);
} }
public int CleanupOldSessions(int hoursOld = 24) public Task<int> CleanupOldSessionsAsync(int hoursOld = 24)
{ {
var cutoffTime = DateTime.UtcNow.AddHours(-hoursOld); var cutoffTime = DateTime.UtcNow.AddHours(-hoursOld);
var sessionsToRemove = _sessions var sessionsToRemove = _sessions
@@ -97,7 +97,7 @@ namespace ChatBot.Services
} }
_logger.LogInformation("Cleaned up {DeletedCount} old sessions", deletedCount); _logger.LogInformation("Cleaned up {DeletedCount} old sessions", deletedCount);
return deletedCount; return Task.FromResult(deletedCount);
} }
public Task SaveSessionAsync(ChatSession session) public Task SaveSessionAsync(ChatSession session)

View File

@@ -10,27 +10,27 @@ namespace ChatBot.Services.Interfaces
/// <summary> /// <summary>
/// Get or create a chat session /// Get or create a chat session
/// </summary> /// </summary>
ChatSession GetOrCreate(long chatId, string chatType = "private", string chatTitle = ""); Task<ChatSession> GetOrCreateAsync(long chatId, string chatType = "private", string chatTitle = "");
/// <summary> /// <summary>
/// Get a session by chat ID /// Get a session by chat ID
/// </summary> /// </summary>
ChatSession? Get(long chatId); Task<ChatSession?> GetAsync(long chatId);
/// <summary> /// <summary>
/// Remove a session /// Remove a session
/// </summary> /// </summary>
bool Remove(long chatId); Task<bool> RemoveAsync(long chatId);
/// <summary> /// <summary>
/// Get count of active sessions /// Get count of active sessions
/// </summary> /// </summary>
int GetActiveSessionsCount(); Task<int> GetActiveSessionsCountAsync();
/// <summary> /// <summary>
/// Clean up old sessions /// Clean up old sessions
/// </summary> /// </summary>
int CleanupOldSessions(int hoursOld = 24); Task<int> CleanupOldSessionsAsync(int hoursOld = 24);
/// <summary> /// <summary>
/// Save session changes to storage (for database implementations) /// Save session changes to storage (for database implementations)

View File

@@ -24,17 +24,15 @@ namespace ChatBot.Services.Telegram.Commands
public override string CommandName => "/settings"; public override string CommandName => "/settings";
public override string Description => "Показать настройки чата"; public override string Description => "Показать настройки чата";
public override Task<string> ExecuteAsync( public override async Task<string> ExecuteAsync(
TelegramCommandContext context, TelegramCommandContext context,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
{ {
var session = _chatService.GetSession(context.ChatId); var session = await _chatService.GetSessionAsync(context.ChatId);
if (session == null) if (session == null)
{ {
return Task.FromResult( return "Сессия не найдена. Отправьте любое сообщение для создания новой сессии.";
"Сессия не найдена. Отправьте любое сообщение для создания новой сессии."
);
} }
var compressionStatus = _aiSettings.EnableHistoryCompression ? "Включено" : "Отключено"; var compressionStatus = _aiSettings.EnableHistoryCompression ? "Включено" : "Отключено";
@@ -42,15 +40,13 @@ namespace ChatBot.Services.Telegram.Commands
? $"\nСжатие истории: {compressionStatus}\nПорог сжатия: {_aiSettings.CompressionThreshold} сообщений\nЦелевое количество: {_aiSettings.CompressionTarget} сообщений" ? $"\nСжатие истории: {compressionStatus}\nПорог сжатия: {_aiSettings.CompressionThreshold} сообщений\nЦелевое количество: {_aiSettings.CompressionTarget} сообщений"
: $"\nСжатие истории: {compressionStatus}"; : $"\nСжатие истории: {compressionStatus}";
return Task.FromResult( return $"Настройки чата:\n"
$"Настройки чата:\n"
+ $"Тип чата: {session.ChatType}\n" + $"Тип чата: {session.ChatType}\n"
+ $"Название: {session.ChatTitle}\n" + $"Название: {session.ChatTitle}\n"
+ $"Модель: {session.Model}\n" + $"Модель: {session.Model}\n"
+ $"Сообщений в истории: {session.GetMessageCount()}\n" + $"Сообщений в истории: {session.GetMessageCount()}\n"
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}" + $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}"
+ compressionInfo + compressionInfo;
);
} }
} }
} }

View File

@@ -40,7 +40,7 @@ namespace ChatBot.Services.Telegram.Commands
statusBuilder.AppendLine(); statusBuilder.AppendLine();
// Информация о сессии // Информация о сессии
var session = _chatService.GetSession(context.ChatId); var session = await _chatService.GetSessionAsync(context.ChatId);
if (session != null) if (session != null)
{ {
statusBuilder.AppendLine($"📊 **Сессия:**"); statusBuilder.AppendLine($"📊 **Сессия:**");

View File

@@ -39,6 +39,23 @@ namespace ChatBot.Services.Telegram.Commands
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
{ {
// Input validation
if (string.IsNullOrWhiteSpace(messageText))
{
_logger.LogWarning("Empty message received for chat {ChatId}", chatId);
return string.Empty;
}
if (chatId == 0)
{
_logger.LogError("Invalid chatId (0) received");
throw new ArgumentException("ChatId cannot be 0", nameof(chatId));
}
username = username ?? "Unknown";
chatType = chatType ?? "private";
chatTitle = chatTitle ?? string.Empty;
try try
{ {
// Получаем информацию о боте // Получаем информацию о боте
@@ -121,10 +138,25 @@ namespace ChatBot.Services.Telegram.Commands
cancellationToken cancellationToken
); );
} }
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Network error processing message for chat {ChatId}", chatId);
return "Ошибка сети при обработке сообщения. Проверьте подключение к AI сервису.";
}
catch (TaskCanceledException ex)
{
_logger.LogWarning(ex, "Request timeout for chat {ChatId}", chatId);
return "Превышено время ожидания ответа. Попробуйте еще раз.";
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, "Invalid operation for chat {ChatId}", chatId);
return "Ошибка в работе системы. Попробуйте позже.";
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error processing message for chat {ChatId}", chatId); _logger.LogError(ex, "Unexpected error processing message for chat {ChatId}", chatId);
return "Произошла ошибка при обработке сообщения. Попробуйте еще раз."; return "Произошла непредвиденная ошибка. Попробуйте еще раз.";
} }
} }
} }