// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.Abstractions.UnitTests;
///
/// Contains tests for the class.
///
public class AgentThreadTests
{
///
/// Tests that the CreateAsync method sets the Id and invokes CreateInternalAsync once.
///
[Fact]
public async Task CreateShouldSetIdAndInvokeCreateInternalOnceAsync()
{
// Arrange
var thread = new TestAgentThread();
// Act
await thread.CreateAsync();
await thread.CreateAsync();
// Assert
Assert.Equal("test-thread-id", thread.Id);
Assert.Equal(1, thread.CreateInternalAsyncCount);
}
///
/// Tests that the CreateAsync method throws an InvalidOperationException if the thread is deleted.
///
[Fact]
public async Task CreateShouldThrowIfThreadDeletedAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();
// Act & Assert
await Assert.ThrowsAsync(() => thread.CreateAsync());
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.DeleteInternalAsyncCount);
}
///
/// Tests that the DeleteAsync method sets IsDeleted and invokes DeleteInternalAsync.
///
[Fact]
public async Task DeleteShouldSetIsDeletedAndInvokeDeleteInternalAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
// Act
await thread.DeleteAsync();
// Assert
Assert.True(thread.IsDeleted);
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.DeleteInternalAsyncCount);
}
///
/// Tests that the DeleteAsync method does not invoke DeleteInternalAsync if the thread is already deleted.
///
[Fact]
public async Task DeleteShouldNotInvokeDeleteInternalIfAlreadyDeletedAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();
// Act
await thread.DeleteAsync();
// Assert
Assert.True(thread.IsDeleted);
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.DeleteInternalAsyncCount);
}
///
/// Tests that the DeleteAsync method throws an InvalidOperationException if the thread was never created.
///
[Fact]
public async Task DeleteShouldThrowIfNeverCreatedAsync()
{
// Arrange
var thread = new TestAgentThread();
// Act & Assert
await Assert.ThrowsAsync(() => thread.DeleteAsync());
Assert.Equal(0, thread.CreateInternalAsyncCount);
Assert.Equal(0, thread.DeleteInternalAsyncCount);
}
///
/// Tests that the OnNewMessageAsync method creates the thread if it is not already created.
///
[Fact]
public async Task OnNewMessageShouldCreateThreadIfNotCreatedAsync()
{
// Arrange
var thread = new TestAgentThread();
var message = new ChatMessage();
// Act
await thread.OnNewMessageAsync(message);
// Assert
Assert.Equal("test-thread-id", thread.Id);
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.OnNewMessageInternalAsyncCount);
}
///
/// Tests that the OnNewMessageAsync method throws an InvalidOperationException if the thread is deleted.
///
[Fact]
public async Task OnNewMessageShouldThrowIfThreadDeletedAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();
var message = new ChatMessage();
// Act & Assert
await Assert.ThrowsAsync(() => thread.OnNewMessageAsync(message));
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.DeleteInternalAsyncCount);
Assert.Equal(0, thread.OnNewMessageInternalAsyncCount);
}
private sealed class TestAgentThread : AgentThread
{
public int CreateInternalAsyncCount { get; private set; }
public int DeleteInternalAsyncCount { get; private set; }
public int OnNewMessageInternalAsyncCount { get; private set; }
public new Task CreateAsync(CancellationToken cancellationToken = default)
{
return base.CreateAsync(cancellationToken);
}
protected override Task CreateCoreAsync(CancellationToken cancellationToken)
{
this.CreateInternalAsyncCount++;
return Task.FromResult("test-thread-id");
}
protected override Task DeleteCoreAsync(CancellationToken cancellationToken)
{
this.DeleteInternalAsyncCount++;
return Task.CompletedTask;
}
protected override Task OnNewMessageCoreAsync(ChatMessage newMessage, CancellationToken cancellationToken = default)
{
this.OnNewMessageInternalAsyncCount++;
return Task.CompletedTask;
}
}
}