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