From c03de646cc0a90e29852798de541bfb9979030e8 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 22 Oct 2025 04:41:56 +0300 Subject: [PATCH 1/2] Add tests --- .../Models/ChatSessionCompressionTests.cs | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 ChatBot.Tests/Models/ChatSessionCompressionTests.cs diff --git a/ChatBot.Tests/Models/ChatSessionCompressionTests.cs b/ChatBot.Tests/Models/ChatSessionCompressionTests.cs new file mode 100644 index 0000000..6b9ecf6 --- /dev/null +++ b/ChatBot.Tests/Models/ChatSessionCompressionTests.cs @@ -0,0 +1,224 @@ +using ChatBot.Models; +using ChatBot.Services.Interfaces; +using FluentAssertions; +using Moq; +using OllamaSharp.Models.Chat; + +namespace ChatBot.Tests.Models; + +public class ChatSessionCompressionTests +{ + [Fact] + public async Task CompressHistoryAsync_ShouldCompressMessages_WhenCompressionServiceAvailable() + { + // Arrange + var session = new ChatSession(); + var compressionServiceMock = new Mock(); + session.SetCompressionService(compressionServiceMock.Object); + + // Setup compression service to return compressed messages + compressionServiceMock + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync( + new List + { + new() { Role = ChatRole.System, Content = "System prompt" }, + new() { Role = ChatRole.User, Content = "Compressed user message" }, + } + ); + + // Add messages to session + for (int i = 0; i < 10; i++) + { + session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" }); + } + + // Act + await session.AddMessageWithCompressionAsync( + new ChatMessage { Role = ChatRole.User, Content = "New message" }, + compressionThreshold: 5, + compressionTarget: 2 + ); + + // Assert + var messages = session.GetAllMessages(); + messages.Should().HaveCount(2); + messages[0].Role.Should().Be(ChatRole.System); + messages[1].Role.Should().Be(ChatRole.User); + messages[1].Content.Should().Be("Compressed user message"); + } + + [Fact] + public async Task CompressHistoryAsync_ShouldFallbackToTrimming_WhenCompressionFails() + { + // Arrange + var session = new ChatSession { MaxHistoryLength = 3 }; + var compressionServiceMock = new Mock(); + session.SetCompressionService(compressionServiceMock.Object); + + // Setup compression service to throw an exception + compressionServiceMock + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) + .ThrowsAsync(new Exception("Compression failed")); + + // Add messages to session + for (int i = 0; i < 5; i++) + { + session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" }); + } + + // Act + await session.AddMessageWithCompressionAsync( + new ChatMessage { Role = ChatRole.User, Content = "New message" }, + compressionThreshold: 3, + compressionTarget: 2 + ); + + // Assert - Should fall back to simple trimming + var messages = session.GetAllMessages(); + messages.Should().HaveCount(3); + } + + [Fact] + public async Task AddMessageWithCompressionAsync_ShouldNotCompress_WhenBelowThreshold() + { + // Arrange + var session = new ChatSession(); + var compressionServiceMock = new Mock(); + session.SetCompressionService(compressionServiceMock.Object); + + // Add messages to session (below threshold) + session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = "Message 1" }); + session.AddMessage(new ChatMessage { Role = ChatRole.Assistant, Content = "Response 1" }); + + // Act - Set threshold higher than current message count + await session.AddMessageWithCompressionAsync( + new ChatMessage { Role = ChatRole.User, Content = "Message 2" }, + compressionThreshold: 5, + compressionTarget: 2 + ); + + // Assert - Should not call compression service + compressionServiceMock.Verify( + x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny()), + Times.Never + ); + + var messages = session.GetAllMessages(); + messages.Should().HaveCount(3); + } + + [Fact] + public async Task AddMessageWithCompressionAsync_ShouldHandleConcurrentAccess() + { + // Arrange + var session = new ChatSession(); + var compressionServiceMock = new Mock(); + session.SetCompressionService(compressionServiceMock.Object); + + // Setup compression service to delay to simulate processing time + compressionServiceMock + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync( + (List messages, int target) => + Task.Delay(50) + .ContinueWith(_ => new List + { + new() { Role = ChatRole.System, Content = "Compressed" }, + }) + ); + + var tasks = new List(); + int messageCount = 5; + + // Act - Start multiple concurrent operations + for (int i = 0; i < messageCount; i++) + { + tasks.Add( + session.AddMessageWithCompressionAsync( + new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" }, + compressionThreshold: 2, + compressionTarget: 1 + ) + ); + } + + // Wait for all operations to complete + await Task.WhenAll(tasks); + + // Assert - Should handle concurrent access without exceptions + // and maintain thread safety + session.GetMessageCount().Should().Be(1); + } + + [Fact] + public void SetCompressionService_ShouldNotThrow_WhenCalledMultipleTimes() + { + // Arrange + var session = new ChatSession(); + var compressionService1 = new Mock().Object; + var compressionService2 = new Mock().Object; + + // Act & Assert + session.Invoking(s => s.SetCompressionService(compressionService1)).Should().NotThrow(); + + // Should not throw when setting a different service + session.Invoking(s => s.SetCompressionService(compressionService2)).Should().NotThrow(); + } + + [Fact] + public async Task CompressHistoryAsync_ShouldPreserveSystemMessage_WhenCompressing() + { + // Arrange + var session = new ChatSession(); + var compressionServiceMock = new Mock(); + session.SetCompressionService(compressionServiceMock.Object); + + // Setup compression service to preserve system message + compressionServiceMock + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync( + (List messages, int target) => + { + var systemMessage = messages.FirstOrDefault(m => m.Role == ChatRole.System); + var compressed = new List(); + + if (systemMessage != null) + { + compressed.Add(systemMessage); + } + + compressed.Add( + new ChatMessage + { + Role = ChatRole.User, + Content = "Compressed user messages", + } + ); + + return compressed; + } + ); + + // Add system message and some user messages + session.AddMessage(new ChatMessage { Role = ChatRole.System, Content = "System prompt" }); + for (int i = 0; i < 10; i++) + { + session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = $"Message {i}" }); + } + + // Act + await session.AddMessageWithCompressionAsync( + new ChatMessage { Role = ChatRole.User, Content = "New message" }, + compressionThreshold: 5, + compressionTarget: 2 + ); + + // Assert - System message should be preserved + var messages = session.GetAllMessages(); + messages.Should().HaveCount(2); + messages[0].Role.Should().Be(ChatRole.System); + messages[0].Content.Should().Be("System prompt"); + messages[1].Content.Should().Be("Compressed user messages"); + } +} -- 2.49.1 From 594e4a1782983d03a86fcb5cda358cc13f5c23d3 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 22 Oct 2025 05:16:58 +0300 Subject: [PATCH 2/2] fix tests --- .../Models/ChatSessionCompressionTests.cs | 100 ++++++++++-------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/ChatBot.Tests/Models/ChatSessionCompressionTests.cs b/ChatBot.Tests/Models/ChatSessionCompressionTests.cs index 6b9ecf6..4ef094c 100644 --- a/ChatBot.Tests/Models/ChatSessionCompressionTests.cs +++ b/ChatBot.Tests/Models/ChatSessionCompressionTests.cs @@ -1,8 +1,10 @@ using ChatBot.Models; +using ChatBot.Models.Dto; using ChatBot.Services.Interfaces; using FluentAssertions; using Moq; using OllamaSharp.Models.Chat; +using System.Collections.Concurrent; namespace ChatBot.Tests.Models; @@ -17,15 +19,17 @@ public class ChatSessionCompressionTests session.SetCompressionService(compressionServiceMock.Object); // Setup compression service to return compressed messages + var compressedMessages = new List + { + new ChatMessage { Role = ChatRole.System.ToString(), Content = "System prompt" }, + new ChatMessage { Role = ChatRole.User.ToString(), Content = "Compressed user message" } + }; compressionServiceMock - .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync( - new List - { - new() { Role = ChatRole.System, Content = "System prompt" }, - new() { Role = ChatRole.User, Content = "Compressed user message" }, - } - ); + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .ReturnsAsync(compressedMessages); + compressionServiceMock + .Setup(x => x.ShouldCompress(It.IsAny(), It.IsAny())) + .Returns(true); // Add messages to session for (int i = 0; i < 10; i++) @@ -57,9 +61,13 @@ public class ChatSessionCompressionTests session.SetCompressionService(compressionServiceMock.Object); // Setup compression service to throw an exception + var exception = new Exception("Compression failed"); compressionServiceMock - .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) - .ThrowsAsync(new Exception("Compression failed")); + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .ThrowsAsync(exception); + compressionServiceMock + .Setup(x => x.ShouldCompress(It.IsAny(), It.IsAny())) + .Returns(true); // Add messages to session for (int i = 0; i < 5; i++) @@ -86,6 +94,11 @@ public class ChatSessionCompressionTests var session = new ChatSession(); var compressionServiceMock = new Mock(); session.SetCompressionService(compressionServiceMock.Object); + + // Setup compression service to return false for ShouldCompress when count is below threshold + compressionServiceMock + .Setup(x => x.ShouldCompress(It.Is(c => c < 5), It.Is(t => t == 5))) + .Returns(false); // Add messages to session (below threshold) session.AddMessage(new ChatMessage { Role = ChatRole.User, Content = "Message 1" }); @@ -100,7 +113,7 @@ public class ChatSessionCompressionTests // Assert - Should not call compression service compressionServiceMock.Verify( - x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny()), + x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never ); @@ -116,17 +129,21 @@ public class ChatSessionCompressionTests var compressionServiceMock = new Mock(); session.SetCompressionService(compressionServiceMock.Object); - // Setup compression service to delay to simulate processing time + // Setup compression service to simulate processing time + var delayedResult = new List + { + new ChatMessage { Role = ChatRole.System.ToString(), Content = "Compressed" } + }; compressionServiceMock - .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync( - (List messages, int target) => - Task.Delay(50) - .ContinueWith(_ => new List - { - new() { Role = ChatRole.System, Content = "Compressed" }, - }) - ); + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns(async (List messages, int target, CancellationToken ct) => + { + await Task.Delay(50); + return delayedResult; + }); + compressionServiceMock + .Setup(x => x.ShouldCompress(It.IsAny(), It.IsAny())) + .Returns(true); var tasks = new List(); int messageCount = 5; @@ -176,29 +193,28 @@ public class ChatSessionCompressionTests // Setup compression service to preserve system message compressionServiceMock - .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny())) - .ReturnsAsync( - (List messages, int target) => + .Setup(x => x.CompressHistoryAsync(It.IsAny>(), It.IsAny(), It.IsAny())) + .Returns((List messages, int target, CancellationToken ct) => + { + var systemMessage = messages.FirstOrDefault(m => m.Role == ChatRole.System.ToString()); + var compressed = new List(); + + if (systemMessage != null) { - var systemMessage = messages.FirstOrDefault(m => m.Role == ChatRole.System); - var compressed = new List(); - - if (systemMessage != null) - { - compressed.Add(systemMessage); - } - - compressed.Add( - new ChatMessage - { - Role = ChatRole.User, - Content = "Compressed user messages", - } - ); - - return compressed; + compressed.Add(systemMessage); } - ); + + compressed.Add(new ChatMessage + { + Role = ChatRole.User.ToString(), + Content = "Compressed user messages" + }); + + return Task.FromResult(compressed); + }); + compressionServiceMock + .Setup(x => x.ShouldCompress(It.IsAny(), It.IsAny())) + .Returns(true); // Add system message and some user messages session.AddMessage(new ChatMessage { Role = ChatRole.System, Content = "System prompt" }); -- 2.49.1