Files
ChatBot/ChatBot.Tests/Services/DatabaseSessionStorageTests.cs
Leonid Pershin 1996fec14f
All checks were successful
SonarQube / Build and analyze (push) Successful in 2m54s
Add promt fix tests
2025-10-21 12:07:56 +03:00

443 lines
14 KiB
C#

using ChatBot.Data;
using ChatBot.Data.Interfaces;
using ChatBot.Models.Entities;
using ChatBot.Services;
using ChatBot.Tests.TestUtilities;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
namespace ChatBot.Tests.Services;
public class DatabaseSessionStorageTests : TestBase
{
private ChatBotDbContext _dbContext = null!;
private DatabaseSessionStorage _sessionStorage = null!;
private Mock<IChatSessionRepository> _repositoryMock = null!;
public DatabaseSessionStorageTests()
{
SetupServices();
}
protected override void ConfigureServices(IServiceCollection services)
{
// Add in-memory database
services.AddDbContext<ChatBotDbContext>(options =>
options.UseInMemoryDatabase("TestDatabase")
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
);
// Add mocked repository
_repositoryMock = new Mock<IChatSessionRepository>();
services.AddSingleton(_repositoryMock.Object);
// Add logger
services.AddSingleton(Mock.Of<ILogger<DatabaseSessionStorage>>());
// Add session storage
services.AddScoped<DatabaseSessionStorage>();
}
protected override void SetupServices()
{
base.SetupServices();
_dbContext = ServiceProvider.GetRequiredService<ChatBotDbContext>();
_sessionStorage = ServiceProvider.GetRequiredService<DatabaseSessionStorage>();
// Ensure database is created
_dbContext.Database.EnsureCreated();
}
[Fact]
public async Task GetOrCreateAsync_ShouldReturnExistingSession_WhenSessionExists()
{
// Arrange
var existingSession = TestDataBuilder.Mocks.CreateChatSessionEntity();
_repositoryMock
.Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat"))
.ReturnsAsync(existingSession);
// Act
var result = await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert
result.Should().NotBeNull();
result.ChatId.Should().Be(12345);
_repositoryMock.Verify(x => x.GetOrCreateAsync(12345, "private", "Test Chat"), Times.Once);
}
[Fact]
public async Task GetAsync_ShouldReturnSession_WhenSessionExists()
{
// Arrange
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
_repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity);
// Act
var result = await _sessionStorage.GetAsync(12345);
// Assert
result.Should().NotBeNull();
result.ChatId.Should().Be(12345);
_repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once);
}
[Fact]
public async Task GetAsync_ShouldReturnNull_WhenSessionDoesNotExist()
{
// Arrange
_repositoryMock
.Setup(x => x.GetByChatIdAsync(12345))
.ReturnsAsync((ChatSessionEntity?)null);
// Act
var result = await _sessionStorage.GetAsync(12345);
// Assert
result.Should().BeNull();
_repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once);
}
[Fact]
public async Task SaveSessionAsync_ShouldUpdateSession_WhenSessionExists()
{
// Arrange
var session = TestDataBuilder.ChatSessions.CreateBasicSession(12345, "private");
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.UpdateAsync(It.IsAny<ChatSessionEntity>()), Times.Once);
}
[Fact]
public async Task RemoveAsync_ShouldReturnTrue_WhenSessionExists()
{
// Arrange
_repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(true);
// Act
var result = await _sessionStorage.RemoveAsync(12345);
// Assert
result.Should().BeTrue();
_repositoryMock.Verify(x => x.DeleteAsync(12345), Times.Once);
}
[Fact]
public async Task RemoveAsync_ShouldReturnFalse_WhenSessionDoesNotExist()
{
// Arrange
_repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(false);
// Act
var result = await _sessionStorage.RemoveAsync(12345);
// Assert
result.Should().BeFalse();
_repositoryMock.Verify(x => x.DeleteAsync(12345), Times.Once);
}
[Fact]
public async Task GetActiveSessionsCountAsync_ShouldReturnCorrectCount()
{
// Arrange
var expectedCount = 5;
_repositoryMock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount);
// Act
var result = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert
result.Should().Be(expectedCount);
_repositoryMock.Verify(x => x.GetActiveSessionsCountAsync(), Times.Once);
}
[Fact]
public async Task CleanupOldSessionsAsync_ShouldReturnCorrectCount()
{
// Arrange
var expectedCount = 3;
_repositoryMock.Setup(x => x.CleanupOldSessionsAsync(24)).ReturnsAsync(expectedCount);
// Act
var result = await _sessionStorage.CleanupOldSessionsAsync(24);
// Assert
result.Should().Be(expectedCount);
_repositoryMock.Verify(x => x.CleanupOldSessionsAsync(24), Times.Once);
}
[Fact]
public async Task GetOrCreateAsync_ShouldThrowInvalidOperationException_WhenRepositoryThrows()
{
// Arrange
_repositoryMock
.Setup(x => x.GetOrCreateAsync(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<string>()))
.ThrowsAsync(new Exception("Database error"));
// Act
Func<Task> act = async () => await _sessionStorage.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert
await act.Should()
.ThrowAsync<InvalidOperationException>()
.WithMessage("Failed to get or create session for chat 12345");
}
[Fact]
public async Task GetAsync_ShouldReturnNull_WhenRepositoryThrows()
{
// Arrange
_repositoryMock
.Setup(x => x.GetByChatIdAsync(It.IsAny<long>()))
.ThrowsAsync(new Exception("Database error"));
// Act
var result = await _sessionStorage.GetAsync(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
Func<Task> 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 async Task RemoveAsync_ShouldReturnFalse_WhenRepositoryThrows()
{
// Arrange
_repositoryMock
.Setup(x => x.DeleteAsync(It.IsAny<long>()))
.ThrowsAsync(new Exception("Database error"));
// Act
var result = await _sessionStorage.RemoveAsync(12345);
// Assert
result.Should().BeFalse();
}
[Fact]
public async Task GetActiveSessionsCountAsync_ShouldReturnZero_WhenRepositoryThrows()
{
// Arrange
_repositoryMock
.Setup(x => x.GetActiveSessionsCountAsync())
.ThrowsAsync(new Exception("Database error"));
// Act
var result = await _sessionStorage.GetActiveSessionsCountAsync();
// Assert
result.Should().Be(0);
}
[Fact]
public async Task CleanupOldSessionsAsync_ShouldReturnZero_WhenRepositoryThrows()
{
// Arrange
_repositoryMock
.Setup(x => x.CleanupOldSessionsAsync(24))
.ThrowsAsync(new Exception("Database error"));
// Act
var result = await _sessionStorage.CleanupOldSessionsAsync(24);
// Assert
result.Should().Be(0);
}
[Fact]
public async Task GetOrCreateAsync_WithCompressionService_ShouldSetCompressionService()
{
// Arrange
var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
var storageWithCompression = new DatabaseSessionStorage(
_repositoryMock.Object,
Mock.Of<ILogger<DatabaseSessionStorage>>(),
_dbContext,
compressionServiceMock.Object
);
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
_repositoryMock
.Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat"))
.ReturnsAsync(sessionEntity);
// Act
var result = await storageWithCompression.GetOrCreateAsync(12345, "private", "Test Chat");
// Assert
result.Should().NotBeNull();
result.ChatId.Should().Be(12345);
}
[Fact]
public async Task GetAsync_WithCompressionService_ShouldSetCompressionService()
{
// Arrange
var loggerMock = new Mock<ILogger<DatabaseSessionStorage>>();
var compressionServiceMock = TestDataBuilder.Mocks.CreateCompressionServiceMock();
var storageWithCompression = new DatabaseSessionStorage(
_repositoryMock.Object,
loggerMock.Object,
_dbContext,
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 = await storageWithCompression.GetAsync(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 async Task GetOrCreateAsync_WithDefaultParameters_ShouldUseDefaults()
{
// Arrange
var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity();
_repositoryMock
.Setup(x => x.GetOrCreateAsync(12345, "private", ""))
.ReturnsAsync(sessionEntity);
// Act
var result = await _sessionStorage.GetOrCreateAsync(12345);
// Assert
result.Should().NotBeNull();
_repositoryMock.Verify(x => x.GetOrCreateAsync(12345, "private", ""), Times.Once);
}
}