// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Moq; using Moq.Protected; using OpenAI.Responses; using ChatMessage = Microsoft.Extensions.AI.ChatMessage; using ChatRole = Microsoft.Extensions.AI.ChatRole; using OpenAIChatMessage = OpenAI.Chat.ChatMessage; using TextContent = Microsoft.Extensions.AI.TextContent; namespace Microsoft.Agents.AI.OpenAI.UnitTests.Extensions; /// /// Unit tests for the class. /// public sealed class AIAgentWithOpenAIExtensionsTests { /// /// Verify that RunAsync throws ArgumentNullException when agent is null. /// [Fact] public async Task RunAsync_WithNullAgent_ThrowsArgumentNullExceptionAsync() { // Arrange AIAgent? agent = null; var messages = new List { OpenAIChatMessage.CreateUserMessage("Test message") }; // Act & Assert var exception = await Assert.ThrowsAsync( () => agent!.RunAsync(messages)); Assert.Equal("agent", exception.ParamName); } /// /// Verify that RunAsync throws ArgumentNullException when messages is null. /// [Fact] public async Task RunAsync_WithNullMessages_ThrowsArgumentNullExceptionAsync() { // Arrange var mockAgent = new Mock(); IEnumerable? messages = null; // Act & Assert var exception = await Assert.ThrowsAsync( () => mockAgent.Object.RunAsync(messages!)); Assert.Equal("messages", exception.ParamName); } /// /// Verify that the RunAsync extension method calls the underlying agent's RunAsync with converted messages and parameters. /// [Fact] public async Task RunAsync_CallsUnderlyingAgentAsync() { // Arrange var mockAgent = new Mock(); var mockThread = new Mock(); var options = new AgentRunOptions(); var cancellationToken = new CancellationToken(false); const string TestMessageText = "Hello, assistant!"; const string ResponseText = "This is the assistant's response."; var openAiMessages = new List { OpenAIChatMessage.CreateUserMessage(TestMessageText) }; var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent(ResponseText)]); mockAgent .Protected() .Setup>("RunCoreAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(new AgentResponse([responseMessage])); // Act var result = await mockAgent.Object.RunAsync(openAiMessages, mockThread.Object, options, cancellationToken); // Assert mockAgent.Protected() .Verify("RunCoreAsync", Times.Once(), ItExpr.Is>(msgs => msgs.ToList().Count == 1 && msgs.ToList()[0].Text == TestMessageText), mockThread.Object, options, cancellationToken ); Assert.NotNull(result); Assert.NotEmpty(result.Content); Assert.Equal(ResponseText, result.Content.Last().Text); } /// /// Verify that RunStreamingAsync throws ArgumentNullException when agent is null. /// [Fact] public void RunStreamingAsync_WithNullAgent_ThrowsArgumentNullException() { // Arrange AIAgent? agent = null; var messages = new List { OpenAIChatMessage.CreateUserMessage("Test message") }; // Act & Assert Assert.Throws( "agent", () => agent!.RunStreamingAsync(messages)); } /// /// Verify that RunStreamingAsync throws ArgumentNullException when messages is null. /// [Fact] public void RunStreamingAsync_WithNullMessages_ThrowsArgumentNullException() { // Arrange var mockAgent = new Mock(); IEnumerable? messages = null; // Act & Assert var exception = Assert.Throws( () => mockAgent.Object.RunStreamingAsync(messages!)); Assert.Equal("messages", exception.ParamName); } /// /// Verify that the RunStreamingAsync extension method calls the underlying agent's RunStreamingAsync with converted messages and parameters. /// [Fact] public async Task RunStreamingAsync_CallsUnderlyingAgentAsync() { // Arrange var mockAgent = new Mock(); var mockThread = new Mock(); var options = new AgentRunOptions(); var cancellationToken = new CancellationToken(false); const string TestMessageText = "Hello, assistant!"; const string ResponseText1 = "This is "; const string ResponseText2 = "the assistant's response."; var openAiMessages = new List { OpenAIChatMessage.CreateUserMessage(TestMessageText) }; var responseUpdates = new List { new(ChatRole.Assistant, ResponseText1), new(ChatRole.Assistant, ResponseText2) }; mockAgent .Protected() .Setup>("RunCoreStreamingAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .Returns(ToAsyncEnumerableAsync(responseUpdates)); // Act var result = mockAgent.Object.RunStreamingAsync(openAiMessages, mockThread.Object, options, cancellationToken); var updateCount = 0; await foreach (var update in result) { updateCount++; } // Assert mockAgent.Protected() .Verify("RunCoreStreamingAsync", Times.Once(), ItExpr.Is>(msgs => msgs.ToList().Count == 1 && msgs.ToList()[0].Text == TestMessageText), mockThread.Object, options, cancellationToken ); Assert.True(updateCount > 0, "Expected at least one streaming update"); } /// /// Helper method to convert a list of AgentResponseUpdate to an async enumerable. /// private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable updates) { foreach (var update in updates) { yield return await Task.FromResult(update); } } #region ResponseItem overload tests /// /// Verify that RunAsync with ResponseItem throws ArgumentNullException when agent is null. /// [Fact] public async Task RunAsync_ResponseItem_WithNullAgent_ThrowsArgumentNullExceptionAsync() { // Arrange AIAgent? agent = null; IEnumerable messages = [ResponseItem.CreateUserMessageItem("Test message")]; // Act & Assert var exception = await Assert.ThrowsAsync( () => agent!.RunAsync(messages)); Assert.Equal("agent", exception.ParamName); } /// /// Verify that RunAsync with ResponseItem throws ArgumentNullException when messages is null. /// [Fact] public async Task RunAsync_ResponseItem_WithNullMessages_ThrowsArgumentNullExceptionAsync() { // Arrange var mockAgent = new Mock(); IEnumerable? messages = null; // Act & Assert var exception = await Assert.ThrowsAsync( () => mockAgent.Object.RunAsync(messages!)); Assert.Equal("messages", exception.ParamName); } /// /// Verify that the RunAsync with ResponseItem extension method calls the underlying agent's RunAsync with converted messages and parameters. /// [Fact] public async Task RunAsync_ResponseItem_CallsUnderlyingAgentAsync() { // Arrange var mockAgent = new Mock(); var mockThread = new Mock(); var options = new AgentRunOptions(); var cancellationToken = new CancellationToken(false); const string TestMessageText = "Hello, assistant!"; const string ResponseText = "This is the assistant's response."; IEnumerable responseItemMessages = [ResponseItem.CreateUserMessageItem(TestMessageText)]; var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent(ResponseText)]); mockAgent .Protected() .Setup>("RunCoreAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(new AgentResponse([responseMessage])); // Act ResponseResult result = await mockAgent.Object.RunAsync(responseItemMessages, mockThread.Object, options, cancellationToken); // Assert mockAgent.Protected() .Verify("RunCoreAsync", Times.Once(), ItExpr.IsAny>(), mockThread.Object, options, cancellationToken ); Assert.NotNull(result); } /// /// Verify that RunStreamingAsync with ResponseItem throws ArgumentNullException when agent is null. /// [Fact] public void RunStreamingAsync_ResponseItem_WithNullAgent_ThrowsArgumentNullException() { // Arrange AIAgent? agent = null; IEnumerable messages = [ResponseItem.CreateUserMessageItem("Test message")]; // Act & Assert Assert.Throws( "agent", () => agent!.RunStreamingAsync(messages)); } /// /// Verify that RunStreamingAsync with ResponseItem throws ArgumentNullException when messages is null. /// [Fact] public void RunStreamingAsync_ResponseItem_WithNullMessages_ThrowsArgumentNullException() { // Arrange var mockAgent = new Mock(); IEnumerable? messages = null; // Act & Assert var exception = Assert.Throws( () => mockAgent.Object.RunStreamingAsync(messages!)); Assert.Equal("messages", exception.ParamName); } /// /// Verify that the RunStreamingAsync with ResponseItem extension method calls the underlying agent's RunStreamingAsync with converted messages and parameters. /// [Fact] public async Task RunStreamingAsync_ResponseItem_CallsUnderlyingAgentAsync() { // Arrange var mockAgent = new Mock(); var mockThread = new Mock(); var options = new AgentRunOptions(); var cancellationToken = new CancellationToken(false); const string TestMessageText = "Hello, assistant!"; const string ResponseText1 = "This is "; const string ResponseText2 = "the assistant's response."; IEnumerable responseItemMessages = [ResponseItem.CreateUserMessageItem(TestMessageText)]; var responseUpdates = new List { new(ChatRole.Assistant, ResponseText1), new(ChatRole.Assistant, ResponseText2) }; mockAgent .Protected() .Setup>("RunCoreStreamingAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .Returns(ToAsyncEnumerableAsync(responseUpdates)); // Act var result = mockAgent.Object.RunStreamingAsync(responseItemMessages, mockThread.Object, options, cancellationToken); var updateCount = 0; await foreach (var update in result) { updateCount++; } // Assert mockAgent.Protected() .Verify("RunCoreStreamingAsync", Times.Once(), ItExpr.IsAny>(), mockThread.Object, options, cancellationToken ); } #endregion }