// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; 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 class. /// public class ChatStrategyExtensionsTests { [Fact] public void AsChatReducerNullStrategyThrows() { // Act & Assert Assert.Throws(() => ((CompactionStrategy)null!).AsChatReducer()); } [Fact] public void AsChatReducerReturnsIChatReducer() { // Arrange ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Always); // Act IChatReducer reducer = strategy.AsChatReducer(); // Assert Assert.NotNull(reducer); } [Fact] public async Task ReduceAsyncReturnsAllMessagesWhenStrategyDoesNotCompactAsync() { // Arrange — trigger never fires, so no compaction occurs ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Never); IChatReducer reducer = strategy.AsChatReducer(); List messages = [ new(ChatRole.User, "Hello"), new(ChatRole.Assistant, "Hi!"), ]; // Act IEnumerable result = await reducer.ReduceAsync(messages, CancellationToken.None); // Assert Assert.Equal(messages, result); } [Fact] public async Task ReduceAsyncCompactsMessagesWhenStrategyFiresAsync() { // Arrange — reducer keeps only the last message ChatReducerCompactionStrategy strategy = new( new TakeLastReducer(1), CompactionTriggers.Always); IChatReducer reducer = strategy.AsChatReducer(); List messages = [ new(ChatRole.User, "First"), new(ChatRole.Assistant, "Response 1"), new(ChatRole.User, "Second"), ]; // Act IEnumerable result = await reducer.ReduceAsync(messages, CancellationToken.None); // Assert List resultList = [.. result]; Assert.Single(resultList); Assert.Equal("Second", resultList[0].Text); } [Fact] public async Task ReduceAsyncPassesCancellationTokenToStrategyAsync() { // Arrange using CancellationTokenSource cts = new(); CancellationToken capturedToken = default; CapturingReducer capturingReducer = new(token => capturedToken = token); ChatReducerCompactionStrategy strategy = new(capturingReducer, CompactionTriggers.Always); IChatReducer reducer = strategy.AsChatReducer(); List messages = [ new(ChatRole.User, "Hello"), new(ChatRole.User, "World"), ]; // Act await reducer.ReduceAsync(messages, cts.Token); // Assert Assert.Equal(cts.Token, capturedToken); } [Fact] public async Task ReduceAsyncEmptyMessagesReturnsEmptyAsync() { // Arrange ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Always); IChatReducer reducer = strategy.AsChatReducer(); // Act IEnumerable result = await reducer.ReduceAsync([], CancellationToken.None); // Assert Assert.Empty(result); } /// /// An that returns messages unchanged. /// private sealed class IdentityReducer : IChatReducer { public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) => Task.FromResult(messages); } /// /// An that keeps only the last n messages. /// private sealed class TakeLastReducer : IChatReducer { private readonly int _count; public TakeLastReducer(int count) => this._count = count; public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) => Task.FromResult(messages.Reverse().Take(this._count)); } /// /// An that captures the passed to . /// private sealed class CapturingReducer : IChatReducer { private readonly Action _capture; public CapturingReducer(Action capture) => this._capture = capture; public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) { this._capture(cancellationToken); IEnumerable reducedMessages = [messages.Reverse().First()]; return Task.FromResult(reducedMessages); } } }