// 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).");
}
}