Skip to content

Commit a3a4c12

Browse files
authored
.Net: Add unit tests for AgentThread and ChatHistoryAgentThread (#11126)
### Description - Add unit tests for AgentThread and ChatHistoryAgentThread - Add unit tests for the OpenAIAssistantAgentThread ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄
1 parent 4439ed3 commit a3a4c12

File tree

3 files changed

+417
-0
lines changed

3 files changed

+417
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.SemanticKernel;
7+
using Microsoft.SemanticKernel.Agents;
8+
using Xunit;
9+
10+
namespace SemanticKernel.Agents.UnitTests.Core;
11+
12+
/// <summary>
13+
/// Contains tests for the <see cref="AgentThread"/> class.
14+
/// </summary>
15+
public class AgentThreadTests
16+
{
17+
/// <summary>
18+
/// Tests that the CreateAsync method sets the Id and invokes CreateInternalAsync once.
19+
/// </summary>
20+
[Fact]
21+
public async Task CreateShouldSetIdAndInvokeCreateInternalOnceAsync()
22+
{
23+
// Arrange
24+
var thread = new TestAgentThread();
25+
26+
// Act
27+
await thread.CreateAsync();
28+
await thread.CreateAsync();
29+
30+
// Assert
31+
Assert.Equal("test-thread-id", thread.Id);
32+
Assert.Equal(1, thread.CreateInternalAsyncCount);
33+
}
34+
35+
/// <summary>
36+
/// Tests that the CreateAsync method throws an InvalidOperationException if the thread is deleted.
37+
/// </summary>
38+
[Fact]
39+
public async Task CreateShouldThrowIfThreadDeletedAsync()
40+
{
41+
// Arrange
42+
var thread = new TestAgentThread();
43+
await thread.CreateAsync();
44+
await thread.DeleteAsync();
45+
46+
// Act & Assert
47+
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.CreateAsync());
48+
Assert.Equal(1, thread.CreateInternalAsyncCount);
49+
Assert.Equal(1, thread.DeleteInternalAsyncCount);
50+
}
51+
52+
/// <summary>
53+
/// Tests that the DeleteAsync method sets IsDeleted and invokes DeleteInternalAsync.
54+
/// </summary>
55+
[Fact]
56+
public async Task DeleteShouldSetIsDeletedAndInvokeDeleteInternalAsync()
57+
{
58+
// Arrange
59+
var thread = new TestAgentThread();
60+
await thread.CreateAsync();
61+
62+
// Act
63+
await thread.DeleteAsync();
64+
65+
// Assert
66+
Assert.True(thread.IsDeleted);
67+
Assert.Equal(1, thread.CreateInternalAsyncCount);
68+
Assert.Equal(1, thread.DeleteInternalAsyncCount);
69+
}
70+
71+
/// <summary>
72+
/// Tests that the DeleteAsync method does not invoke DeleteInternalAsync if the thread is already deleted.
73+
/// </summary>
74+
[Fact]
75+
public async Task DeleteShouldNotInvokeDeleteInternalIfAlreadyDeletedAsync()
76+
{
77+
// Arrange
78+
var thread = new TestAgentThread();
79+
await thread.CreateAsync();
80+
await thread.DeleteAsync();
81+
82+
// Act
83+
await thread.DeleteAsync();
84+
85+
// Assert
86+
Assert.True(thread.IsDeleted);
87+
Assert.Equal(1, thread.CreateInternalAsyncCount);
88+
Assert.Equal(1, thread.DeleteInternalAsyncCount);
89+
}
90+
91+
/// <summary>
92+
/// Tests that the DeleteAsync method throws an InvalidOperationException if the thread was never created.
93+
/// </summary>
94+
[Fact]
95+
public async Task DeleteShouldThrowIfNeverCreatedAsync()
96+
{
97+
// Arrange
98+
var thread = new TestAgentThread();
99+
100+
// Act & Assert
101+
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.DeleteAsync());
102+
Assert.Equal(0, thread.CreateInternalAsyncCount);
103+
Assert.Equal(0, thread.DeleteInternalAsyncCount);
104+
}
105+
106+
/// <summary>
107+
/// Tests that the OnNewMessageAsync method creates the thread if it is not already created.
108+
/// </summary>
109+
[Fact]
110+
public async Task OnNewMessageShouldCreateThreadIfNotCreatedAsync()
111+
{
112+
// Arrange
113+
var thread = new TestAgentThread();
114+
var message = new ChatMessageContent();
115+
116+
// Act
117+
await thread.OnNewMessageAsync(message);
118+
119+
// Assert
120+
Assert.Equal("test-thread-id", thread.Id);
121+
Assert.Equal(1, thread.CreateInternalAsyncCount);
122+
Assert.Equal(1, thread.OnNewMessageInternalAsyncCount);
123+
}
124+
125+
/// <summary>
126+
/// Tests that the OnNewMessageAsync method throws an InvalidOperationException if the thread is deleted.
127+
/// </summary>
128+
[Fact]
129+
public async Task OnNewMessageShouldThrowIfThreadDeletedAsync()
130+
{
131+
// Arrange
132+
var thread = new TestAgentThread();
133+
await thread.CreateAsync();
134+
await thread.DeleteAsync();
135+
var message = new ChatMessageContent();
136+
137+
// Act & Assert
138+
await Assert.ThrowsAsync<InvalidOperationException>(() => thread.OnNewMessageAsync(message));
139+
Assert.Equal(1, thread.CreateInternalAsyncCount);
140+
Assert.Equal(1, thread.DeleteInternalAsyncCount);
141+
Assert.Equal(0, thread.OnNewMessageInternalAsyncCount);
142+
}
143+
144+
private sealed class TestAgentThread : AgentThread
145+
{
146+
public int CreateInternalAsyncCount { get; private set; }
147+
public int DeleteInternalAsyncCount { get; private set; }
148+
public int OnNewMessageInternalAsyncCount { get; private set; }
149+
150+
protected override Task<string?> CreateInternalAsync(CancellationToken cancellationToken)
151+
{
152+
this.CreateInternalAsyncCount++;
153+
return Task.FromResult<string?>("test-thread-id");
154+
}
155+
156+
protected override Task DeleteInternalAsync(CancellationToken cancellationToken)
157+
{
158+
this.DeleteInternalAsyncCount++;
159+
return Task.CompletedTask;
160+
}
161+
162+
protected override Task OnNewMessageInternalAsync(ChatMessageContent newMessage, CancellationToken cancellationToken = default)
163+
{
164+
this.OnNewMessageInternalAsyncCount++;
165+
return Task.CompletedTask;
166+
}
167+
}
168+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Microsoft.SemanticKernel;
7+
using Microsoft.SemanticKernel.Agents;
8+
using Microsoft.SemanticKernel.ChatCompletion;
9+
using Xunit;
10+
11+
namespace SemanticKernel.Agents.UnitTests.Core;
12+
13+
/// <summary>
14+
/// Contains tests for the <see cref="ChatHistoryAgentThread"/> class.
15+
/// </summary>
16+
public class ChatHistoryAgentThreadTests
17+
{
18+
/// <summary>
19+
/// Tests that creating a thread generates a unique Id and doesn't change IsDeleted.
20+
/// </summary>
21+
[Fact]
22+
public async Task CreateShouldGenerateIdAsync()
23+
{
24+
// Arrange
25+
var thread = new ChatHistoryAgentThread();
26+
27+
// Act
28+
await thread.CreateAsync();
29+
30+
// Assert
31+
Assert.NotNull(thread.Id);
32+
Assert.False(thread.IsDeleted);
33+
}
34+
35+
/// <summary>
36+
/// Tests that deleting a thread marks it as deleted.
37+
/// </summary>
38+
[Fact]
39+
public async Task DeleteShouldMarkThreadAsDeletedAsync()
40+
{
41+
// Arrange
42+
var thread = new ChatHistoryAgentThread();
43+
await thread.CreateAsync();
44+
45+
// Act
46+
await thread.DeleteAsync();
47+
48+
// Assert
49+
Assert.True(thread.IsDeleted);
50+
}
51+
52+
/// <summary>
53+
/// Tests that adding a new message to the thread adds it to the message history.
54+
/// </summary>
55+
[Fact]
56+
public async Task OnNewMessageShouldAddMessageToHistoryAsync()
57+
{
58+
// Arrange
59+
var thread = new ChatHistoryAgentThread();
60+
var message = new ChatMessageContent(AuthorRole.User, "Hello");
61+
62+
// Act
63+
await thread.OnNewMessageAsync(message);
64+
65+
// Assert
66+
var messages = await thread.GetMessagesAsync().ToListAsync();
67+
Assert.Single(messages);
68+
Assert.Equal("Hello", messages[0].Content);
69+
}
70+
71+
/// <summary>
72+
/// Tests that GetMessagesAsync returns all messages in the thread.
73+
/// </summary>
74+
[Fact]
75+
public async Task GetMessagesShouldReturnAllMessagesAsync()
76+
{
77+
// Arrange
78+
var thread = new ChatHistoryAgentThread();
79+
var message1 = new ChatMessageContent(AuthorRole.User, "Hello");
80+
var message2 = new ChatMessageContent(AuthorRole.Assistant, "Hi there");
81+
82+
await thread.OnNewMessageAsync(message1);
83+
await thread.OnNewMessageAsync(message2);
84+
85+
// Act
86+
var messages = await thread.GetMessagesAsync().ToListAsync();
87+
88+
// Assert
89+
Assert.Equal(2, messages.Count);
90+
Assert.Equal("Hello", messages[0].Content);
91+
Assert.Equal("Hi there", messages[1].Content);
92+
}
93+
94+
/// <summary>
95+
/// Tests that GetMessagesAsync throws an InvalidOperationException if the thread is deleted.
96+
/// </summary>
97+
[Fact]
98+
public async Task GetMessagesShouldThrowIfThreadIsDeletedAsync()
99+
{
100+
// Arrange
101+
var thread = new ChatHistoryAgentThread();
102+
await thread.CreateAsync();
103+
await thread.DeleteAsync();
104+
105+
// Act & Assert
106+
await Assert.ThrowsAsync<InvalidOperationException>(async () => await thread.GetMessagesAsync().ToListAsync());
107+
}
108+
109+
/// <summary>
110+
/// Tests that GetMessagesAsync creates the thread if it has not been created yet.
111+
/// </summary>
112+
[Fact]
113+
public async Task GetMessagesShouldCreateThreadIfNotCreatedAsync()
114+
{
115+
// Arrange
116+
var thread = new ChatHistoryAgentThread();
117+
118+
// Act
119+
var messages = await thread.GetMessagesAsync().ToListAsync();
120+
121+
// Assert
122+
Assert.NotNull(thread.Id);
123+
Assert.False(thread.IsDeleted);
124+
Assert.Empty(messages);
125+
}
126+
}

0 commit comments

Comments
 (0)