using ChatBot.Data; using ChatBot.Data.Interfaces; using ChatBot.Data.Repositories; using ChatBot.Tests.TestUtilities; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; namespace ChatBot.Tests.Data.Repositories; public class ChatSessionRepositoryTests : TestBase { private ChatBotDbContext _dbContext = null!; private ChatSessionRepository _repository = null!; public ChatSessionRepositoryTests() { SetupServices(); } protected override void ConfigureServices(IServiceCollection services) { // Add in-memory database with unique name per test services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()) ); // Add logger services.AddSingleton(Mock.Of>()); // Add repository services.AddScoped(); } protected override void SetupServices() { base.SetupServices(); _dbContext = ServiceProvider.GetRequiredService(); _repository = (ChatSessionRepository) ServiceProvider.GetRequiredService(); // Ensure database is created _dbContext.Database.EnsureCreated(); } private void CleanupDatabase() { _dbContext.ChatSessions.RemoveRange(_dbContext.ChatSessions); _dbContext.ChatMessages.RemoveRange(_dbContext.ChatMessages); _dbContext.SaveChanges(); } [Fact] public async Task GetOrCreateAsync_ShouldReturnExistingSession_WhenSessionExists() { // Arrange CleanupDatabase(); var chatId = 12345L; var chatType = "private"; var chatTitle = "Test Chat"; var existingSession = TestDataBuilder.Mocks.CreateChatSessionEntity( 1, chatId, "existing-session", chatType, chatTitle ); _dbContext.ChatSessions.Add(existingSession); await _dbContext.SaveChangesAsync(); // Act var result = await _repository.GetOrCreateAsync(chatId, chatType, chatTitle); // Assert result.Should().NotBeNull(); result.ChatId.Should().Be(chatId); result.SessionId.Should().Be("existing-session"); } [Fact] public async Task GetOrCreateAsync_ShouldCreateNewSession_WhenSessionDoesNotExist() { // Arrange CleanupDatabase(); var chatId = 12345L; var chatType = "private"; var chatTitle = "Test Chat"; // Act var result = await _repository.GetOrCreateAsync(chatId, chatType, chatTitle); // Assert result.Should().NotBeNull(); result.ChatId.Should().Be(chatId); result.ChatType.Should().Be(chatType); result.ChatTitle.Should().Be(chatTitle); result.SessionId.Should().NotBeNullOrEmpty(); } [Fact] public async Task GetByChatIdAsync_ShouldReturnSession_WhenSessionExists() { // Arrange CleanupDatabase(); var chatId = 12345L; var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, chatId); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act var result = await _repository.GetByChatIdAsync(chatId); // Assert result.Should().NotBeNull(); result.ChatId.Should().Be(chatId); } [Fact] public async Task GetByChatIdAsync_ShouldReturnNull_WhenSessionDoesNotExist() { // Arrange CleanupDatabase(); var chatId = 12345L; // Act var result = await _repository.GetByChatIdAsync(chatId); // Assert result.Should().BeNull(); } [Fact] public async Task GetBySessionIdAsync_ShouldReturnSession_WhenSessionExists() { // Arrange CleanupDatabase(); var sessionId = "test-session"; var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345, sessionId); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act var result = await _repository.GetBySessionIdAsync(sessionId); // Assert result.Should().NotBeNull(); result.SessionId.Should().Be(sessionId); } [Fact] public async Task GetBySessionIdAsync_ShouldReturnNull_WhenSessionDoesNotExist() { // Arrange CleanupDatabase(); var sessionId = "nonexistent-session"; // Act var result = await _repository.GetBySessionIdAsync(sessionId); // Assert result.Should().BeNull(); } [Fact] public async Task UpdateAsync_ShouldUpdateSession() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); session.ChatTitle = "Updated Title"; // Act var result = await _repository.UpdateAsync(session); // Assert result.Should().NotBeNull(); result.ChatTitle.Should().Be("Updated Title"); } [Fact] public async Task DeleteAsync_ShouldReturnTrue_WhenSessionExists() { // Arrange CleanupDatabase(); var chatId = 12345L; var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, chatId); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act var result = await _repository.DeleteAsync(chatId); // Assert result.Should().BeTrue(); var deletedSession = await _repository.GetByChatIdAsync(chatId); deletedSession.Should().BeNull(); } [Fact] public async Task DeleteAsync_ShouldReturnFalse_WhenSessionDoesNotExist() { // Arrange CleanupDatabase(); var chatId = 12345L; // Act var result = await _repository.DeleteAsync(chatId); // Assert result.Should().BeFalse(); } [Fact] public async Task GetActiveSessionsCountAsync_ShouldReturnCorrectCount() { // Arrange CleanupDatabase(); var session1 = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); var session2 = TestDataBuilder.Mocks.CreateChatSessionEntity(2, 12346); _dbContext.ChatSessions.AddRange(session1, session2); await _dbContext.SaveChangesAsync(); // Act var count = await _repository.GetActiveSessionsCountAsync(); // Assert count.Should().Be(2); } [Fact] public async Task CleanupOldSessionsAsync_ShouldRemoveOldSessions() { // Arrange CleanupDatabase(); // Verify database is empty var initialCount = await _repository.GetActiveSessionsCountAsync(); initialCount.Should().Be(0); var oldSession = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); oldSession.LastUpdatedAt = DateTime.UtcNow.AddDays(-2); // 2 days old var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(2, 12346); recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-30); // 30 minutes old _dbContext.ChatSessions.AddRange(oldSession, recentSession); await _dbContext.SaveChangesAsync(); // Act var removedCount = await _repository.CleanupOldSessionsAsync(1); // Remove sessions older than 1 hour // Assert removedCount.Should().Be(1); var remainingSessions = await _repository.GetActiveSessionsCountAsync(); remainingSessions.Should().Be(1); } [Fact] public async Task GetMessagesAsync_ShouldReturnMessages_WhenMessagesExist() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0); await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1); // Act var messages = await _repository.GetMessagesAsync(session.Id); // Assert messages.Should().HaveCount(2); messages[0].Content.Should().Be("Message 1"); messages[0].Role.Should().Be("User"); messages[1].Content.Should().Be("Message 2"); messages[1].Role.Should().Be("Assistant"); } [Fact] public async Task GetMessagesAsync_ShouldReturnEmptyList_WhenNoMessages() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act var messages = await _repository.GetMessagesAsync(session.Id); // Assert messages.Should().BeEmpty(); } [Fact] public async Task AddMessageAsync_ShouldAddMessage() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act var message = await _repository.AddMessageAsync(session.Id, "Test message", "User", 0); // Assert message.Should().NotBeNull(); message.Content.Should().Be("Test message"); message.Role.Should().Be("User"); message.MessageOrder.Should().Be(0); message.SessionId.Should().Be(session.Id); var messages = await _repository.GetMessagesAsync(session.Id); messages.Should().HaveCount(1); } [Fact] public async Task ClearMessagesAsync_ShouldRemoveAllMessages() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0); await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1); await _repository.AddMessageAsync(session.Id, "Message 3", "User", 2); // Act await _repository.ClearMessagesAsync(session.Id); // Assert var messages = await _repository.GetMessagesAsync(session.Id); messages.Should().BeEmpty(); } [Fact] public async Task GetSessionsForCleanupAsync_ShouldReturnOldSessions() { // Arrange CleanupDatabase(); var oldSession1 = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); oldSession1.LastUpdatedAt = DateTime.UtcNow.AddDays(-2); var oldSession2 = TestDataBuilder.Mocks.CreateChatSessionEntity(2, 12346); oldSession2.LastUpdatedAt = DateTime.UtcNow.AddHours(-25); var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(3, 12347); recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-30); _dbContext.ChatSessions.AddRange(oldSession1, oldSession2, recentSession); await _dbContext.SaveChangesAsync(); // Act var sessionsForCleanup = await _repository.GetSessionsForCleanupAsync(24); // Assert sessionsForCleanup.Should().HaveCount(2); sessionsForCleanup.Should().Contain(s => s.ChatId == 12345); sessionsForCleanup.Should().Contain(s => s.ChatId == 12346); sessionsForCleanup.Should().NotContain(s => s.ChatId == 12347); } [Fact] public async Task GetSessionsForCleanupAsync_ShouldReturnEmptyList_WhenNoOldSessions() { // Arrange CleanupDatabase(); var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-30); _dbContext.ChatSessions.Add(recentSession); await _dbContext.SaveChangesAsync(); // Act var sessionsForCleanup = await _repository.GetSessionsForCleanupAsync(24); // Assert sessionsForCleanup.Should().BeEmpty(); } [Fact] public async Task UpdateAsync_ShouldUpdateLastUpdatedAt() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); var originalLastUpdated = DateTime.UtcNow.AddDays(-1); session.LastUpdatedAt = originalLastUpdated; _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act session.Model = "new-model"; var updatedSession = await _repository.UpdateAsync(session); // Assert updatedSession.LastUpdatedAt.Should().BeAfter(originalLastUpdated); updatedSession.Model.Should().Be("new-model"); } [Fact] public async Task GetByChatIdAsync_ShouldIncludeMessages() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); await _repository.AddMessageAsync(session.Id, "Test message", "User", 0); // Act var retrievedSession = await _repository.GetByChatIdAsync(12345); // Assert retrievedSession.Should().NotBeNull(); retrievedSession!.Messages.Should().HaveCount(1); retrievedSession.Messages.First().Content.Should().Be("Test message"); } [Fact] public async Task GetBySessionIdAsync_ShouldIncludeMessages() { // Arrange CleanupDatabase(); var sessionId = "test-session-id"; var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345, sessionId); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); await _repository.AddMessageAsync(session.Id, "Test message", "User", 0); // Act var retrievedSession = await _repository.GetBySessionIdAsync(sessionId); // Assert retrievedSession.Should().NotBeNull(); retrievedSession!.Messages.Should().HaveCount(1); } [Fact] public async Task AddMessageAsync_WithMultipleMessages_ShouldMaintainOrder() { // Arrange CleanupDatabase(); var session = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); _dbContext.ChatSessions.Add(session); await _dbContext.SaveChangesAsync(); // Act await _repository.AddMessageAsync(session.Id, "Message 1", "User", 0); await _repository.AddMessageAsync(session.Id, "Message 2", "Assistant", 1); await _repository.AddMessageAsync(session.Id, "Message 3", "User", 2); // Assert var messages = await _repository.GetMessagesAsync(session.Id); messages.Should().HaveCount(3); messages[0].MessageOrder.Should().Be(0); messages[1].MessageOrder.Should().Be(1); messages[2].MessageOrder.Should().Be(2); } [Fact] public async Task CleanupOldSessionsAsync_WithNoOldSessions_ShouldReturnZero() { // Arrange CleanupDatabase(); var recentSession = TestDataBuilder.Mocks.CreateChatSessionEntity(1, 12345); recentSession.LastUpdatedAt = DateTime.UtcNow.AddMinutes(-10); _dbContext.ChatSessions.Add(recentSession); await _dbContext.SaveChangesAsync(); // Act var removedCount = await _repository.CleanupOldSessionsAsync(24); // Assert removedCount.Should().Be(0); } }