Skip to content

Commit 1a45bbc

Browse files
.NET: Improve unit test coverage for Microsoft.Agents.AI.OpenAI (microsoft#3349)
* Initial plan * Add unit tests for Microsoft.Agents.AI.OpenAI to improve code coverage Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Address code review feedback: remove unused using directives Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix format issues: file encoding and remove unused using directives Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Fix redundant cast error by using named parameter Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> * Remove excessive inline comments per PR review feedback --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
1 parent dacb129 commit 1a45bbc

7 files changed

Lines changed: 1144 additions & 0 deletions

dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
1818
</ItemGroup>
1919

20+
<ItemGroup>
21+
<InternalsVisibleTo Include="Microsoft.Agents.AI.OpenAI.UnitTests" />
22+
</ItemGroup>
23+
2024
<PropertyGroup>
2125
<!-- NuGet Package Settings -->
2226
<Title>Microsoft Agent Framework OpenAI</Title>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ClientModel;
4+
using System.Collections.Generic;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.AI;
7+
using OpenAI.Chat;
8+
9+
namespace Microsoft.Agents.AI.OpenAI.UnitTests.ChatClient;
10+
11+
/// <summary>
12+
/// Unit tests for the <see cref="AsyncStreamingChatCompletionUpdateCollectionResult"/> class.
13+
/// </summary>
14+
public sealed class AsyncStreamingChatCompletionUpdateCollectionResultTests
15+
{
16+
/// <summary>
17+
/// Verify that GetContinuationToken returns null.
18+
/// </summary>
19+
[Fact]
20+
public void GetContinuationToken_ReturnsNull()
21+
{
22+
// Arrange
23+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
24+
AsyncCollectionResult<StreamingChatCompletionUpdate> collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
25+
26+
// Act
27+
ContinuationToken? token = collectionResult.GetContinuationToken(null!);
28+
29+
// Assert
30+
Assert.Null(token);
31+
}
32+
33+
/// <summary>
34+
/// Verify that GetRawPagesAsync returns a single page.
35+
/// </summary>
36+
[Fact]
37+
public async Task GetRawPagesAsync_ReturnsSinglePageAsync()
38+
{
39+
// Arrange
40+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
41+
AsyncCollectionResult<StreamingChatCompletionUpdate> collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
42+
43+
// Act
44+
List<ClientResult> pages = [];
45+
await foreach (ClientResult page in collectionResult.GetRawPagesAsync())
46+
{
47+
pages.Add(page);
48+
}
49+
50+
// Assert
51+
Assert.Single(pages);
52+
}
53+
54+
/// <summary>
55+
/// Verify that iterating through the collection yields streaming updates.
56+
/// </summary>
57+
[Fact]
58+
public async Task IterateCollection_YieldsUpdatesAsync()
59+
{
60+
// Arrange
61+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
62+
AsyncCollectionResult<StreamingChatCompletionUpdate> collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
63+
64+
// Act
65+
List<StreamingChatCompletionUpdate> results = [];
66+
await foreach (StreamingChatCompletionUpdate update in collectionResult)
67+
{
68+
results.Add(update);
69+
}
70+
71+
// Assert
72+
Assert.Single(results);
73+
}
74+
75+
/// <summary>
76+
/// Verify that iterating through the collection with multiple updates yields all updates.
77+
/// </summary>
78+
[Fact]
79+
public async Task IterateCollection_WithMultipleUpdates_YieldsAllUpdatesAsync()
80+
{
81+
// Arrange
82+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateMultipleTestUpdatesAsync();
83+
AsyncCollectionResult<StreamingChatCompletionUpdate> collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
84+
85+
// Act
86+
List<StreamingChatCompletionUpdate> results = [];
87+
await foreach (StreamingChatCompletionUpdate update in collectionResult)
88+
{
89+
results.Add(update);
90+
}
91+
92+
// Assert
93+
Assert.Equal(3, results.Count);
94+
}
95+
96+
private static async IAsyncEnumerable<AgentResponseUpdate> CreateTestUpdatesAsync()
97+
{
98+
yield return new AgentResponseUpdate(ChatRole.Assistant, "test");
99+
await Task.CompletedTask;
100+
}
101+
102+
private static async IAsyncEnumerable<AgentResponseUpdate> CreateMultipleTestUpdatesAsync()
103+
{
104+
yield return new AgentResponseUpdate(ChatRole.Assistant, "first");
105+
yield return new AgentResponseUpdate(ChatRole.Assistant, "second");
106+
yield return new AgentResponseUpdate(ChatRole.Assistant, "third");
107+
await Task.CompletedTask;
108+
}
109+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System;
4+
using System.ClientModel;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.AI;
8+
using OpenAI.Responses;
9+
10+
namespace Microsoft.Agents.AI.OpenAI.UnitTests.ChatClient;
11+
12+
/// <summary>
13+
/// Unit tests for the <see cref="AsyncStreamingResponseUpdateCollectionResult"/> class.
14+
/// </summary>
15+
public sealed class AsyncStreamingResponseUpdateCollectionResultTests
16+
{
17+
/// <summary>
18+
/// Verify that GetContinuationToken returns null.
19+
/// </summary>
20+
[Fact]
21+
public void GetContinuationToken_ReturnsNull()
22+
{
23+
// Arrange
24+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
25+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
26+
27+
// Act
28+
ContinuationToken? token = collectionResult.GetContinuationToken(null!);
29+
30+
// Assert
31+
Assert.Null(token);
32+
}
33+
34+
/// <summary>
35+
/// Verify that GetRawPagesAsync returns a single page.
36+
/// </summary>
37+
[Fact]
38+
public async Task GetRawPagesAsync_ReturnsSinglePageAsync()
39+
{
40+
// Arrange
41+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
42+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
43+
44+
// Act
45+
List<ClientResult> pages = [];
46+
await foreach (ClientResult page in collectionResult.GetRawPagesAsync())
47+
{
48+
pages.Add(page);
49+
}
50+
51+
// Assert
52+
Assert.Single(pages);
53+
}
54+
55+
/// <summary>
56+
/// Verify that iterating through the collection yields streaming updates when RawRepresentation is a StreamingResponseUpdate.
57+
/// </summary>
58+
[Fact]
59+
public async Task IterateCollection_WithStreamingResponseUpdateRawRepresentation_YieldsUpdatesAsync()
60+
{
61+
// Arrange
62+
StreamingResponseUpdate rawUpdate = CreateStreamingResponseUpdate();
63+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesWithRawRepresentationAsync(rawUpdate);
64+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
65+
66+
// Act
67+
List<StreamingResponseUpdate> results = [];
68+
await foreach (StreamingResponseUpdate update in collectionResult)
69+
{
70+
results.Add(update);
71+
}
72+
73+
// Assert
74+
Assert.Single(results);
75+
Assert.Same(rawUpdate, results[0]);
76+
}
77+
78+
/// <summary>
79+
/// Verify that iterating through the collection yields updates when RawRepresentation is a ChatResponseUpdate containing a StreamingResponseUpdate.
80+
/// </summary>
81+
[Fact]
82+
public async Task IterateCollection_WithChatResponseUpdateContainingStreamingResponseUpdate_YieldsUpdatesAsync()
83+
{
84+
// Arrange
85+
StreamingResponseUpdate rawUpdate = CreateStreamingResponseUpdate();
86+
ChatResponseUpdate chatResponseUpdate = new() { RawRepresentation = rawUpdate };
87+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesWithChatResponseUpdateAsync(chatResponseUpdate);
88+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
89+
90+
// Act
91+
List<StreamingResponseUpdate> results = [];
92+
await foreach (StreamingResponseUpdate update in collectionResult)
93+
{
94+
results.Add(update);
95+
}
96+
97+
// Assert
98+
Assert.Single(results);
99+
Assert.Same(rawUpdate, results[0]);
100+
}
101+
102+
/// <summary>
103+
/// Verify that iterating through the collection skips updates when RawRepresentation is not a StreamingResponseUpdate.
104+
/// </summary>
105+
[Fact]
106+
public async Task IterateCollection_WithNonStreamingResponseUpdateRawRepresentation_SkipsUpdateAsync()
107+
{
108+
// Arrange
109+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesAsync();
110+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
111+
112+
// Act
113+
List<StreamingResponseUpdate> results = [];
114+
await foreach (StreamingResponseUpdate update in collectionResult)
115+
{
116+
results.Add(update);
117+
}
118+
119+
// Assert
120+
Assert.Empty(results);
121+
}
122+
123+
/// <summary>
124+
/// Verify that iterating through the collection skips updates when RawRepresentation is a ChatResponseUpdate without StreamingResponseUpdate.
125+
/// </summary>
126+
[Fact]
127+
public async Task IterateCollection_WithChatResponseUpdateWithoutStreamingResponseUpdate_SkipsUpdateAsync()
128+
{
129+
// Arrange
130+
ChatResponseUpdate chatResponseUpdate = new() { RawRepresentation = "not a streaming update" };
131+
IAsyncEnumerable<AgentResponseUpdate> updates = CreateTestUpdatesWithChatResponseUpdateAsync(chatResponseUpdate);
132+
AsyncCollectionResult<StreamingResponseUpdate> collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
133+
134+
// Act
135+
List<StreamingResponseUpdate> results = [];
136+
await foreach (StreamingResponseUpdate update in collectionResult)
137+
{
138+
results.Add(update);
139+
}
140+
141+
// Assert
142+
Assert.Empty(results);
143+
}
144+
145+
private static async IAsyncEnumerable<AgentResponseUpdate> CreateTestUpdatesAsync()
146+
{
147+
yield return new AgentResponseUpdate(ChatRole.Assistant, "test");
148+
await Task.CompletedTask;
149+
}
150+
151+
private static async IAsyncEnumerable<AgentResponseUpdate> CreateTestUpdatesWithRawRepresentationAsync(object rawRepresentation)
152+
{
153+
AgentResponseUpdate update = new(ChatRole.Assistant, "test")
154+
{
155+
RawRepresentation = rawRepresentation
156+
};
157+
yield return update;
158+
await Task.CompletedTask;
159+
}
160+
161+
private static async IAsyncEnumerable<AgentResponseUpdate> CreateTestUpdatesWithChatResponseUpdateAsync(ChatResponseUpdate chatResponseUpdate)
162+
{
163+
AgentResponseUpdate update = new(ChatRole.Assistant, "test")
164+
{
165+
RawRepresentation = chatResponseUpdate
166+
};
167+
yield return update;
168+
await Task.CompletedTask;
169+
}
170+
171+
private static StreamingResponseUpdate CreateStreamingResponseUpdate()
172+
{
173+
const string Json = """
174+
{
175+
"type": "response.output_item.added",
176+
"sequence_number": 1,
177+
"output_index": 0,
178+
"item": {
179+
"id": "item_abc123",
180+
"type": "message",
181+
"status": "in_progress",
182+
"role": "assistant",
183+
"content": []
184+
}
185+
}
186+
""";
187+
188+
return System.ClientModel.Primitives.ModelReaderWriter.Read<StreamingResponseUpdate>(BinaryData.FromString(Json))!;
189+
}
190+
}

0 commit comments

Comments
 (0)