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.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 _repositoryMock = null!; public DatabaseSessionStorageTests() { SetupServices(); } protected override void ConfigureServices(IServiceCollection services) { // Add in-memory database services.AddDbContext(options => options.UseInMemoryDatabase("TestDatabase") ); // Add mocked repository _repositoryMock = new Mock(); services.AddSingleton(_repositoryMock.Object); // Add logger services.AddSingleton(Mock.Of>()); // Add session storage services.AddScoped(); } protected override void SetupServices() { base.SetupServices(); _dbContext = ServiceProvider.GetRequiredService(); _sessionStorage = ServiceProvider.GetRequiredService(); // Ensure database is created _dbContext.Database.EnsureCreated(); } [Fact] public void GetOrCreate_ShouldReturnExistingSession_WhenSessionExists() { // Arrange var existingSession = TestDataBuilder.Mocks.CreateChatSessionEntity(); _repositoryMock .Setup(x => x.GetOrCreateAsync(12345, "private", "Test Chat")) .ReturnsAsync(existingSession); // Act var result = _sessionStorage.GetOrCreate(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 void Get_ShouldReturnSession_WhenSessionExists() { // Arrange var sessionEntity = TestDataBuilder.Mocks.CreateChatSessionEntity(); _repositoryMock.Setup(x => x.GetByChatIdAsync(12345)).ReturnsAsync(sessionEntity); // Act var result = _sessionStorage.Get(12345); // Assert result.Should().NotBeNull(); result.ChatId.Should().Be(12345); _repositoryMock.Verify(x => x.GetByChatIdAsync(12345), Times.Once); } [Fact] public void Get_ShouldReturnNull_WhenSessionDoesNotExist() { // Arrange _repositoryMock .Setup(x => x.GetByChatIdAsync(12345)) .ReturnsAsync((ChatSessionEntity?)null); // Act var result = _sessionStorage.Get(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())) .ReturnsAsync(sessionEntity); // Act await _sessionStorage.SaveSessionAsync(session); // Assert _repositoryMock.Verify(x => x.UpdateAsync(It.IsAny()), Times.Once); } [Fact] public void Remove_ShouldReturnTrue_WhenSessionExists() { // Arrange _repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(true); // Act var result = _sessionStorage.Remove(12345); // Assert result.Should().BeTrue(); _repositoryMock.Verify(x => x.DeleteAsync(12345), Times.Once); } [Fact] public void Remove_ShouldReturnFalse_WhenSessionDoesNotExist() { // Arrange _repositoryMock.Setup(x => x.DeleteAsync(12345)).ReturnsAsync(false); // Act var result = _sessionStorage.Remove(12345); // Assert result.Should().BeFalse(); _repositoryMock.Verify(x => x.DeleteAsync(12345), Times.Once); } [Fact] public void GetActiveSessionsCount_ShouldReturnCorrectCount() { // Arrange var expectedCount = 5; _repositoryMock.Setup(x => x.GetActiveSessionsCountAsync()).ReturnsAsync(expectedCount); // Act var result = _sessionStorage.GetActiveSessionsCount(); // Assert result.Should().Be(expectedCount); _repositoryMock.Verify(x => x.GetActiveSessionsCountAsync(), Times.Once); } [Fact] public void CleanupOldSessions_ShouldReturnCorrectCount() { // Arrange var expectedCount = 3; _repositoryMock.Setup(x => x.CleanupOldSessionsAsync(24)).ReturnsAsync(expectedCount); // Act var result = _sessionStorage.CleanupOldSessions(24); // Assert 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() .WithMessage("Failed to get or create session for chat 12345") .WithInnerException() .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()), 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() .WithMessage("Failed to save session for chat 12345"); exception .And.InnerException.Should() .BeOfType() .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())) .ReturnsAsync(sessionEntity); // Act await _sessionStorage.SaveSessionAsync(session); // Assert _repositoryMock.Verify(x => x.ClearMessagesAsync(It.IsAny()), Times.Once); _repositoryMock.Verify( x => x.AddMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ), Times.Exactly(2) ); _repositoryMock.Verify(x => x.UpdateAsync(It.IsAny()), 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>(), 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>(); 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())) .ReturnsAsync(sessionEntity); // Act await _sessionStorage.SaveSessionAsync(session); // Assert _repositoryMock.Verify(x => x.ClearMessagesAsync(It.IsAny()), Times.Once); _repositoryMock.Verify( x => x.AddMessageAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny() ), 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); } }