// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Compaction; using Microsoft.Extensions.AI; using Moq; namespace Microsoft.Agents.AI.UnitTests.Compaction; /// /// Contains tests for . /// public class CompactionStrategyCreateTests { private static IChatClient CreateMockChatClient() { Mock mock = new(); mock.Setup(c => c.GetResponseAsync( It.IsAny>(), It.IsAny(), It.IsAny())) .ReturnsAsync(new ChatResponse([new ChatMessage(ChatRole.Assistant, "summary")])); return mock.Object; } // ── Gentle ──────────────────────────────────────────────────────────────── [Fact] public void CreateGentleCompactReturnsTwoStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact)); Assert.Equal(2, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); } [Fact] public void CreateGentleModerateReturnsTwoStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate)); Assert.Equal(2, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); } [Fact] public void CreateGentleGenerousReturnsTwoStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Generous)); Assert.Equal(2, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); } [Fact] public void CreateGentleDoesNotRequireChatClient() { // No chatClient supplied — should succeed without throwing. CompactionStrategy strategy = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate); Assert.NotNull(strategy); } // ── Balanced ────────────────────────────────────────────────────────────── [Fact] public void CreateBalancedCompactReturnsThreeStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Compact, CreateMockChatClient())); Assert.Equal(3, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); } [Fact] public void CreateBalancedModerateReturnsThreeStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Moderate, CreateMockChatClient())); Assert.Equal(3, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); } [Fact] public void CreateBalancedGenerousReturnsThreeStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Generous, CreateMockChatClient())); Assert.Equal(3, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); } [Fact] public void CreateBalancedNullChatClientThrows() { Assert.Throws( () => CompactionStrategy.Create(CompactionApproach.Balanced, CompactionSize.Moderate, null)); } // ── Aggressive ──────────────────────────────────────────────────────────── [Fact] public void CreateAggressiveCompactReturnsFourStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Aggressive, CompactionSize.Compact, CreateMockChatClient())); Assert.Equal(4, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); Assert.IsType(pipeline.Strategies[3]); } [Fact] public void CreateAggressiveModerateReturnsFourStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Aggressive, CompactionSize.Moderate, CreateMockChatClient())); Assert.Equal(4, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); Assert.IsType(pipeline.Strategies[3]); } [Fact] public void CreateAggressiveGenerousReturnsFourStrategyPipeline() { PipelineCompactionStrategy pipeline = Assert.IsType( CompactionStrategy.Create(CompactionApproach.Aggressive, CompactionSize.Generous, CreateMockChatClient())); Assert.Equal(4, pipeline.Strategies.Count); Assert.IsType(pipeline.Strategies[0]); Assert.IsType(pipeline.Strategies[1]); Assert.IsType(pipeline.Strategies[2]); Assert.IsType(pipeline.Strategies[3]); } [Fact] public void CreateAggressiveNullChatClientThrows() { Assert.Throws( () => CompactionStrategy.Create(CompactionApproach.Aggressive, CompactionSize.Moderate, null)); } // ── Invalid enum values ─────────────────────────────────────────────────── [Fact] public void CreateInvalidApproachThrows() { Assert.Throws( () => CompactionStrategy.Create((CompactionApproach)99, CompactionSize.Moderate)); } [Fact] public void CreateInvalidSizeThrows() { Assert.Throws( () => CompactionStrategy.Create(CompactionApproach.Gentle, (CompactionSize)99)); } // ── Size-threshold behavioral verification ──────────────────────────────── /// /// Verifies that and /// configure different message thresholds: 18 messages (17 groups) should trigger the Compact /// ToolResultCompaction stage (threshold 10) but NOT the Moderate one (threshold 20). /// [Fact] public async Task CreateGentleSizeDifferentiatesMessageThresholdsAsync() { // Arrange: 17 groups = 1 ToolCall + 8 User + 8 AssistantText = 18 messages. // Compact ToolResult triggers at MessagesExceed(10) → 18 > 10 → fires. // Moderate ToolResult triggers at MessagesExceed(20) → 18 < 20 → does not fire. // // With minimumPreservedGroups=16 and 17 total groups the oldest 1 group (the tool-call // group) is eligible for collapsing, making the behavioral difference observable. CompactionStrategy compactPipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Compact); CompactionStrategy moderatePipeline = CompactionStrategy.Create(CompactionApproach.Gentle, CompactionSize.Moderate); List messages = [ // 1 tool-call group (assistant FunctionCall + tool result = 2 messages, 1 group) new(ChatRole.Assistant, [new FunctionCallContent("c1", "fetch")]), new(ChatRole.Tool, "data"), ]; for (int index = 0; index < 99; ++index) { messages.Add(new(ChatRole.User, $"Q{index}")); messages.Add(new(ChatRole.Assistant, $"A{index}")); } // Two separate indexes so strategies run independently. CompactionMessageIndex compactIndex = CompactionMessageIndex.Create(messages); CompactionMessageIndex moderateIndex = CompactionMessageIndex.Create(messages); // Act bool compactCompacted = await compactPipeline.CompactAsync(compactIndex); bool moderateCompacted = await moderatePipeline.CompactAsync(moderateIndex); // Assert Assert.True(compactCompacted, "Compact size should trigger ToolResult compaction at 18 messages (> threshold of 10)."); Assert.False(moderateCompacted, "Moderate size should NOT trigger ToolResult compaction at 18 messages (< threshold of 20)."); } }