// Copyright (c) Microsoft. All rights reserved. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Compaction; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.UnitTests.Compaction; /// /// Contains tests for the abstract base class. /// public class CompactionStrategyTests { [Fact] public void ConstructorNullTriggerThrows() { // Act & Assert Assert.Throws(() => new TestStrategy(null!)); } [Fact] public async Task CompactAsyncTriggerNotMetReturnsFalseAsync() { // Arrange — trigger never fires TestStrategy strategy = new(_ => false); MessageIndex index = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]); // Act bool result = await strategy.CompactAsync(index); // Assert Assert.False(result); Assert.Equal(0, strategy.ApplyCallCount); } [Fact] public async Task CompactAsyncTriggerMetCallsApplyAsync() { // Arrange — trigger always fires TestStrategy strategy = new(_ => true, applyFunc: _ => true); MessageIndex index = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]); // Act bool result = await strategy.CompactAsync(index); // Assert Assert.True(result); Assert.Equal(1, strategy.ApplyCallCount); } [Fact] public async Task CompactAsyncReturnsFalseWhenApplyReturnsFalseAsync() { // Arrange — trigger fires but Apply does nothing TestStrategy strategy = new(_ => true, applyFunc: _ => false); MessageIndex index = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]); // Act bool result = await strategy.CompactAsync(index); // Assert Assert.False(result); Assert.Equal(1, strategy.ApplyCallCount); } [Fact] public async Task CompactAsyncDefaultTargetIsInverseOfTriggerAsync() { // Arrange — trigger fires when groups > 2 // Default target should be: stop when groups <= 2 (i.e., !trigger) CompactionTrigger trigger = CompactionTriggers.GroupsExceed(2); TestStrategy strategy = new(trigger, applyFunc: index => { // Exclude oldest non-system group one at a time foreach (MessageGroup group in index.Groups) { if (!group.IsExcluded && group.Kind != MessageGroupKind.System) { group.IsExcluded = true; // Target (default = !trigger) returns true when groups <= 2 // So the strategy would check Target after this exclusion break; } } return true; }); MessageIndex index = MessageIndex.Create( [ new ChatMessage(ChatRole.User, "Q1"), new ChatMessage(ChatRole.Assistant, "A1"), new ChatMessage(ChatRole.User, "Q2"), new ChatMessage(ChatRole.Assistant, "A2"), ]); // Act bool result = await strategy.CompactAsync(index); // Assert — trigger fires (4 > 2), Apply is called Assert.True(result); Assert.Equal(1, strategy.ApplyCallCount); } [Fact] public async Task CompactAsyncCustomTargetIsPassedToStrategyAsync() { // Arrange — custom target that always signals stop bool targetCalled = false; CompactionTrigger customTarget = _ => { targetCalled = true; return true; }; TestStrategy strategy = new(_ => true, customTarget, _ => { // Access the target from within the strategy return true; }); MessageIndex index = MessageIndex.Create([new ChatMessage(ChatRole.User, "Hello")]); // Act await strategy.CompactAsync(index); // Assert — the custom target is accessible (verified by TestStrategy checking it) Assert.Equal(1, strategy.ApplyCallCount); // The target is accessible to derived classes via the protected property Assert.True(strategy.InvokeTarget(index)); Assert.True(targetCalled); } /// /// A concrete test implementation of for testing the base class. /// private sealed class TestStrategy : CompactionStrategy { private readonly Func? _applyFunc; public TestStrategy( CompactionTrigger trigger, CompactionTrigger? target = null, Func? applyFunc = null) : base(trigger, target) { this._applyFunc = applyFunc; } public int ApplyCallCount { get; private set; } /// /// Exposes the protected Target property for test verification. /// public bool InvokeTarget(MessageIndex index) => this.Target(index); protected override Task ApplyCompactionAsync(MessageIndex index, CancellationToken cancellationToken) { this.ApplyCallCount++; bool result = this._applyFunc?.Invoke(index) ?? false; return Task.FromResult(result); } } }