// 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.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Moq;
namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests;
///
/// Unit tests for the class.
///
public sealed class AGUIEndpointRouteBuilderExtensionsTests
{
[Fact]
public void MapAGUIServer_MapsEndpoint_AtSpecifiedPattern()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
serviceProviderMock.As();
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
endpointsMock.Setup(e => e.DataSources).Returns([]);
const string Pattern = "/api/agent";
AIAgent agent = new TestAgent();
// Act
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer(Pattern, agent);
// Assert
Assert.NotNull(result);
}
[Fact]
public void MapAGUIServer_WithAgentName_ResolvesKeyedAgentFromDI()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
AIAgent agent = new NamedTestAgent();
serviceProviderMock.As()
.Setup(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"))
.Returns(agent);
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
endpointsMock.Setup(e => e.DataSources).Returns([]);
// Act
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("test-agent", "/api/agent");
// Assert
Assert.NotNull(result);
serviceProviderMock.As()
.Verify(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"), Times.Once);
}
[Fact]
public void MapAGUIServer_WithHostedAgentBuilder_ResolvesAgentByBuilderName()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
Mock agentBuilderMock = new();
AIAgent agent = new NamedTestAgent();
agentBuilderMock.Setup(b => b.Name).Returns("test-agent");
serviceProviderMock.As()
.Setup(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"))
.Returns(agent);
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
endpointsMock.Setup(e => e.DataSources).Returns([]);
// Act
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer(agentBuilderMock.Object, "/api/agent");
// Assert
Assert.NotNull(result);
serviceProviderMock.As()
.Verify(sp => sp.GetRequiredKeyedService(typeof(AIAgent), "test-agent"), Times.Once);
}
[Fact]
public void MapAGUIServer_WithAgent_ResolvesSessionStoreFromDI()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
Mock sessionStoreMock = new();
AIAgent agent = new NamedTestAgent();
serviceProviderMock.As()
.Setup(sp => sp.GetKeyedService(typeof(AgentSessionStore), "test-agent"))
.Returns(sessionStoreMock.Object);
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
endpointsMock.Setup(e => e.DataSources).Returns([]);
// Act
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("/api/agent", agent);
// Assert
Assert.NotNull(result);
serviceProviderMock.As()
.Verify(sp => sp.GetKeyedService(typeof(AgentSessionStore), "test-agent"), Times.Once);
}
[Fact]
public void MapAGUIServer_WithoutSessionStore_FallsBackToNoopStore()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
AIAgent agent = new TestAgent();
// No session store registered - IKeyedServiceProvider returns null by default
serviceProviderMock.As();
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
endpointsMock.Setup(e => e.DataSources).Returns([]);
// Act - should not throw (falls back to NoopAgentSessionStore)
IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUIServer("/api/agent", agent);
// Assert
Assert.NotNull(result);
}
[Fact]
public void MapAGUIServer_WithNullEndpoints_ThrowsArgumentNullException()
{
// Arrange
AIAgent agent = new TestAgent();
// Act & Assert
Assert.Throws(() =>
AGUIEndpointRouteBuilderExtensions.MapAGUIServer(null!, "/api/agent", agent));
}
[Fact]
public void MapAGUIServer_WithNullAgent_ThrowsArgumentNullException()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
serviceProviderMock.As();
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
// Act & Assert
Assert.Throws(() =>
endpointsMock.Object.MapAGUIServer("/api/agent", (AIAgent)null!));
}
[Fact]
public void MapAGUIServer_WithNullAgentName_ThrowsArgumentNullException()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
serviceProviderMock.As();
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
// Act & Assert
Assert.Throws(() =>
endpointsMock.Object.MapAGUIServer((string)null!, "/api/agent"));
}
[Fact]
public void MapAGUIServer_WithNullAgentBuilder_ThrowsArgumentNullException()
{
// Arrange
Mock endpointsMock = new();
Mock serviceProviderMock = new();
endpointsMock.Setup(e => e.ServiceProvider).Returns(serviceProviderMock.Object);
// Act & Assert
Assert.Throws(() =>
endpointsMock.Object.MapAGUIServer((IHostedAgentBuilder)null!, "/api/agent"));
}
private sealed class TestAgent : AIAgent
{
protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
}
private sealed class NamedTestAgent : AIAgent
{
protected override string? IdCore => "named-test-agent";
public override string? Name => "test-agent";
protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override IAsyncEnumerable RunCoreStreamingAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
}
}