Skip to content

.Net: Add unit tests for AgentThread and ChatHistoryAgentThread #11126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions dotnet/src/Agents/UnitTests/Core/AgentThreadTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Xunit;

namespace SemanticKernel.Agents.UnitTests.Core;

/// <summary>
/// Contains tests for the <see cref="AgentThread"/> class.
/// </summary>
public class AgentThreadTests
{
/// <summary>
/// Tests that the CreateAsync method sets the Id and invokes CreateInternalAsync once.
/// </summary>
[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);
}

/// <summary>
/// Tests that the CreateAsync method throws an InvalidOperationException if the thread is deleted.
/// </summary>
[Fact]
public async Task CreateShouldThrowIfThreadDeletedAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.CreateAsync());
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.DeleteInternalAsyncCount);
}

/// <summary>
/// Tests that the DeleteAsync method sets IsDeleted and invokes DeleteInternalAsync.
/// </summary>
[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);
}

/// <summary>
/// Tests that the DeleteAsync method does not invoke DeleteInternalAsync if the thread is already deleted.
/// </summary>
[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);
}

/// <summary>
/// Tests that the DeleteAsync method throws an InvalidOperationException if the thread was never created.
/// </summary>
[Fact]
public async Task DeleteShouldThrowIfNeverCreatedAsync()
{
// Arrange
var thread = new TestAgentThread();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.DeleteAsync());
Assert.Equal(0, thread.CreateInternalAsyncCount);
Assert.Equal(0, thread.DeleteInternalAsyncCount);
}

/// <summary>
/// Tests that the OnNewMessageAsync method creates the thread if it is not already created.
/// </summary>
[Fact]
public async Task OnNewMessageShouldCreateThreadIfNotCreatedAsync()
{
// Arrange
var thread = new TestAgentThread();
var message = new ChatMessageContent();

// Act
await thread.OnNewMessageAsync(message);

// Assert
Assert.Equal("test-thread-id", thread.Id);
Assert.Equal(1, thread.CreateInternalAsyncCount);
Assert.Equal(1, thread.OnNewMessageInternalAsyncCount);
}

/// <summary>
/// Tests that the OnNewMessageAsync method throws an InvalidOperationException if the thread is deleted.
/// </summary>
[Fact]
public async Task OnNewMessageShouldThrowIfThreadDeletedAsync()
{
// Arrange
var thread = new TestAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();
var message = new ChatMessageContent();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => 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; }

protected override Task<string?> CreateInternalAsync(CancellationToken cancellationToken)
{
this.CreateInternalAsyncCount++;
return Task.FromResult<string?>("test-thread-id");
}

protected override Task DeleteInternalAsync(CancellationToken cancellationToken)
{
this.DeleteInternalAsyncCount++;
return Task.CompletedTask;
}

protected override Task OnNewMessageInternalAsync(ChatMessageContent newMessage, CancellationToken cancellationToken = default)
{
this.OnNewMessageInternalAsyncCount++;
return Task.CompletedTask;
}
}
}
126 changes: 126 additions & 0 deletions dotnet/src/Agents/UnitTests/Core/ChatHistoryAgentThreadTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using Xunit;

namespace SemanticKernel.Agents.UnitTests.Core;

/// <summary>
/// Contains tests for the <see cref="ChatHistoryAgentThread"/> class.
/// </summary>
public class ChatHistoryAgentThreadTests
{
/// <summary>
/// Tests that creating a thread generates a unique Id and doesn't change IsDeleted.
/// </summary>
[Fact]
public async Task CreateShouldGenerateIdAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();

// Act
await thread.CreateAsync();

// Assert
Assert.NotNull(thread.Id);
Assert.False(thread.IsDeleted);
}

/// <summary>
/// Tests that deleting a thread marks it as deleted.
/// </summary>
[Fact]
public async Task DeleteShouldMarkThreadAsDeletedAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();
await thread.CreateAsync();

// Act
await thread.DeleteAsync();

// Assert
Assert.True(thread.IsDeleted);
}

/// <summary>
/// Tests that adding a new message to the thread adds it to the message history.
/// </summary>
[Fact]
public async Task OnNewMessageShouldAddMessageToHistoryAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();
var message = new ChatMessageContent(AuthorRole.User, "Hello");

// Act
await thread.OnNewMessageAsync(message);

// Assert
var messages = await thread.GetMessagesAsync().ToListAsync();
Assert.Single(messages);
Assert.Equal("Hello", messages[0].Content);
}

/// <summary>
/// Tests that GetMessagesAsync returns all messages in the thread.
/// </summary>
[Fact]
public async Task GetMessagesShouldReturnAllMessagesAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();
var message1 = new ChatMessageContent(AuthorRole.User, "Hello");
var message2 = new ChatMessageContent(AuthorRole.Assistant, "Hi there");

await thread.OnNewMessageAsync(message1);
await thread.OnNewMessageAsync(message2);

// Act
var messages = await thread.GetMessagesAsync().ToListAsync();

// Assert
Assert.Equal(2, messages.Count);
Assert.Equal("Hello", messages[0].Content);
Assert.Equal("Hi there", messages[1].Content);
}

/// <summary>
/// Tests that GetMessagesAsync throws an InvalidOperationException if the thread is deleted.
/// </summary>
[Fact]
public async Task GetMessagesShouldThrowIfThreadIsDeletedAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();
await thread.CreateAsync();
await thread.DeleteAsync();

// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(async () => await thread.GetMessagesAsync().ToListAsync());
}

/// <summary>
/// Tests that GetMessagesAsync creates the thread if it has not been created yet.
/// </summary>
[Fact]
public async Task GetMessagesShouldCreateThreadIfNotCreatedAsync()
{
// Arrange
var thread = new ChatHistoryAgentThread();

// Act
var messages = await thread.GetMessagesAsync().ToListAsync();

// Assert
Assert.NotNull(thread.Id);
Assert.False(thread.IsDeleted);
Assert.Empty(messages);
}
}
Loading
Loading