// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Moq; using Moq.Protected; namespace Microsoft.Agents.AI.Abstractions.UnitTests; /// /// Unit tests for the class. /// public class DelegatingAIAgentTests { private readonly Mock _innerAgentMock; private readonly TestDelegatingAIAgent _delegatingAgent; private readonly AgentResponse _testResponse; private readonly List _testStreamingResponses; private readonly AgentSession _testSession; /// /// Initializes a new instance of the class. /// public DelegatingAIAgentTests() { this._innerAgentMock = new Mock { CallBase = true }; this._testResponse = new AgentResponse(new ChatMessage(ChatRole.Assistant, "Test response")); this._testStreamingResponses = [new AgentResponseUpdate(ChatRole.Assistant, "Test streaming response")]; this._testSession = new TestAgentSession(); // Setup inner agent mock this._innerAgentMock.Protected().SetupGet("IdCore").Returns("test-agent-id"); this._innerAgentMock.Setup(x => x.Name).Returns("Test Agent"); this._innerAgentMock.Setup(x => x.Description).Returns("Test Description"); this._innerAgentMock .Protected() .Setup>("CreateSessionCoreAsync", ItExpr.IsAny()) .ReturnsAsync(this._testSession); this._innerAgentMock .Protected() .Setup>("RunCoreAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(this._testResponse); this._innerAgentMock .Protected() .Setup>("RunCoreStreamingAsync", ItExpr.IsAny>(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .Returns(ToAsyncEnumerableAsync(this._testStreamingResponses)); this._delegatingAgent = new TestDelegatingAIAgent(this._innerAgentMock.Object); } #region Constructor Tests /// /// Verify that constructor throws ArgumentNullException when innerAgent is null. /// [Fact] public void RequiresInnerAgent() => // Act & Assert Assert.Throws("innerAgent", () => new TestDelegatingAIAgent(null!)); /// /// Verify that constructor sets the inner agent correctly. /// [Fact] public void Constructor_WithValidInnerAgent_SetsInnerAgent() { // Act var delegatingAgent = new TestDelegatingAIAgent(this._innerAgentMock.Object); // Assert Assert.Same(this._innerAgentMock.Object, delegatingAgent.InnerAgent); } #endregion #region Property Delegation Tests /// /// Verify that Id property delegates to inner agent. /// [Fact] public void Id_DelegatesToInnerAgent() { // Act var id = this._delegatingAgent.Id; // Assert Assert.Equal("test-agent-id", id); this._innerAgentMock.Protected().VerifyGet("IdCore", Times.Once()); } /// /// Verify that Name property delegates to inner agent. /// [Fact] public void Name_DelegatesToInnerAgent() { // Act var name = this._delegatingAgent.Name; // Assert Assert.Equal("Test Agent", name); this._innerAgentMock.Verify(x => x.Name, Times.Once); } /// /// Verify that Description property delegates to inner agent. /// [Fact] public void Description_DelegatesToInnerAgent() { // Act var description = this._delegatingAgent.Description; // Assert Assert.Equal("Test Description", description); this._innerAgentMock.Verify(x => x.Description, Times.Once); } #endregion #region Method Delegation Tests /// /// Verify that CreateSessionAsync delegates to inner agent. /// [Fact] public async Task CreateSessionAsync_DelegatesToInnerAgentAsync() { // Act var session = await this._delegatingAgent.CreateSessionAsync(); // Assert Assert.Same(this._testSession, session); this._innerAgentMock .Protected() .Verify>("CreateSessionCoreAsync", Times.Once(), ItExpr.IsAny()); } /// /// Verify that DeserializeSessionAsync delegates to inner agent. /// [Fact] public async Task DeserializeSessionAsync_DelegatesToInnerAgentAsync() { // Arrange var serializedSession = JsonSerializer.SerializeToElement("test-session-id", TestJsonSerializerContext.Default.String); this._innerAgentMock .Protected() .Setup>("DeserializeSessionCoreAsync", ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(this._testSession); // Act var session = await this._delegatingAgent.DeserializeSessionAsync(serializedSession); // Assert Assert.Same(this._testSession, session); this._innerAgentMock .Protected() .Verify>("DeserializeSessionCoreAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny(), ItExpr.IsAny()); } /// /// Verify that RunAsync delegates to inner agent with correct parameters. /// [Fact] public async Task RunAsyncDefaultsToInnerAgentAsync() { // Arrange var expectedMessages = new[] { new ChatMessage(ChatRole.User, "Test message") }; var expectedSession = new TestAgentSession(); var expectedOptions = new AgentRunOptions(); var expectedCancellationToken = new CancellationToken(); var expectedResult = new TaskCompletionSource(); var expectedResponse = new AgentResponse(); var innerAgentMock = new Mock(); innerAgentMock .Protected() .Setup>("RunCoreAsync", ItExpr.Is>(m => m == expectedMessages), ItExpr.Is(t => t == expectedSession), ItExpr.Is(o => o == expectedOptions), ItExpr.Is(ct => ct == expectedCancellationToken)) .Returns(expectedResult.Task); var delegatingAgent = new TestDelegatingAIAgent(innerAgentMock.Object); // Act var resultTask = delegatingAgent.RunAsync(expectedMessages, expectedSession, expectedOptions, expectedCancellationToken); // Assert Assert.False(resultTask.IsCompleted); expectedResult.SetResult(expectedResponse); Assert.True(resultTask.IsCompleted); Assert.Same(expectedResponse, await resultTask); } /// /// Verify that RunStreamingAsync delegates to inner agent with correct parameters. /// [Fact] public async Task RunStreamingAsyncDefaultsToInnerAgentAsync() { // Arrange var expectedMessages = new[] { new ChatMessage(ChatRole.User, "Test message") }; var expectedSession = new TestAgentSession(); var expectedOptions = new AgentRunOptions(); var expectedCancellationToken = new CancellationToken(); AgentResponseUpdate[] expectedResults = [ new(ChatRole.Assistant, "Message 1"), new(ChatRole.Assistant, "Message 2") ]; var innerAgentMock = new Mock(); innerAgentMock .Protected() .Setup>("RunCoreStreamingAsync", ItExpr.Is>(m => m == expectedMessages), ItExpr.Is(t => t == expectedSession), ItExpr.Is(o => o == expectedOptions), ItExpr.Is(ct => ct == expectedCancellationToken)) .Returns(ToAsyncEnumerableAsync(expectedResults)); var delegatingAgent = new TestDelegatingAIAgent(innerAgentMock.Object); // Act var resultAsyncEnumerable = delegatingAgent.RunStreamingAsync(expectedMessages, expectedSession, expectedOptions, expectedCancellationToken); // Assert var enumerator = resultAsyncEnumerable.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); Assert.Same(expectedResults[0], enumerator.Current); Assert.True(await enumerator.MoveNextAsync()); Assert.Same(expectedResults[1], enumerator.Current); Assert.False(await enumerator.MoveNextAsync()); } #endregion #region GetService Tests /// /// Verify that GetService throws ArgumentNullException when serviceType is null. /// [Fact] public void GetServiceThrowsForNullType() => // Act & Assert Assert.Throws("serviceType", () => this._delegatingAgent.GetService(null!)); /// /// Verify that GetService returns the delegating agent itself when requesting compatible type and key is null. /// [Fact] public void GetServiceReturnsSelfIfCompatibleWithRequestAndKeyIsNull() { // Act var agent = this._delegatingAgent.GetService(); // Assert Assert.Same(this._delegatingAgent, agent); } /// /// Verify that GetService delegates to inner agent when service key is not null. /// [Fact] public void GetServiceDelegatesToInnerIfKeyIsNotNull() { // Arrange var expectedKey = new object(); var expectedResult = new Mock().Object; var innerAgentMock = new Mock(); innerAgentMock.Setup(x => x.GetService(typeof(AIAgent), expectedKey)).Returns(expectedResult); var delegatingAgent = new TestDelegatingAIAgent(innerAgentMock.Object); // Act var agent = delegatingAgent.GetService(expectedKey); // Assert Assert.Same(expectedResult, agent); } /// /// Verify that GetService delegates to inner agent when not compatible with request. /// [Fact] public void GetServiceDelegatesToInnerIfNotCompatibleWithRequest() { // Arrange var expectedResult = TimeZoneInfo.Local; var expectedKey = new object(); var innerAgentMock = new Mock(); innerAgentMock .Setup(x => x.GetService(typeof(TimeZoneInfo), expectedKey)) .Returns(expectedResult); var delegatingAgent = new TestDelegatingAIAgent(innerAgentMock.Object); // Act var tzi = delegatingAgent.GetService(expectedKey); // Assert Assert.Same(expectedResult, tzi); } #endregion #region Helper Methods private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable values) { await Task.Yield(); foreach (var value in values) { yield return value; } } #endregion #region Test Implementation /// /// Test implementation of DelegatingAIAgent for testing purposes. /// private sealed class TestDelegatingAIAgent(AIAgent innerAgent) : DelegatingAIAgent(innerAgent) { public new AIAgent InnerAgent => base.InnerAgent; } private sealed class TestAgentSession : AgentSession; #endregion }