// 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
}