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