mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
66e02c10e3
* update a2a agent to the latest a2a sdk (#5257) * Move A2A samples from 04-hosting to 02-agents (#5267) Move the A2A sample projects (A2AAgent_AsFunctionTools and A2AAgent_PollingForTaskCompletion) from samples/04-hosting/A2A/ to samples/02-agents/A2A/ to better align with the sample directory structure. Update solution file and samples README accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Fix stream reconnection for A2AAgent (#5275) * Add SSE stream reconnection support to A2AAgent Implement automatic reconnection for SSE streams that disconnect mid-task, using the Last-Event-ID header to resume from where the stream left off. Changes: - Add InvokeStreamingWithReconnectAsync method to A2AAgent with configurable max retries and delay between attempts - Add new log messages for reconnection events - Add A2AAgent_StreamReconnection sample demonstrating the feature - Update existing polling sample to use simplified SendMessageAsync API - Add unit tests for stream reconnection logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address comments * Address PR review feedback - Dispose SSE enumerator before GetTaskAsync fallback to release HTTP connection - Wrap StreamWriter in using blocks with leaveOpen:true and explicit UTF-8 encoding - Print update.Text instead of update object in stream reconnection sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Use IA2AClientFactory to create A2AClient (#5277) * Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample - Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions - Update A2ACardResolverExtensions to accept IA2AClientFactory - Update A2AClientExtensions to accept IA2AClientFactory - Update A2AAgent to use IA2AClientFactory for client creation - Add A2AAgent_ProtocolSelection sample demonstrating protocol selection - Add comprehensive unit tests for all changes - Update README files with new sample reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reorder params: options before loggerFactory in A2A extensions Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent and GetAIAgentAsync extension methods to follow the repo convention of keeping LoggerFactory and CancellationToken as the last parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Migrate A2A hosting to A2A SDK v1 (#5363) * .NET: Migrate A2A hosting to A2A SDK v1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove unused agent card --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Split A2A endpoint mapping into protocol-specific methods (#5413) * .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions - Rename A2AHostingOptions to A2AServerRegistrationOptions - Move server registration logic from A2AEndpointRouteBuilderExtensions and AIAgentExtensions into new A2AServerServiceCollectionExtensions - Remove A2AProtocolBinding and AIAgentExtensions (consolidated) - Update samples and tests to use the new registration API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address copilot comments --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary using directive in AgentWebChat.AgentHost Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * restore AsyncEnumerable package version * address copilot initial feedback * address automated code review and formatting issues * fix formatting issues * Add DI wiring verification tests for AddA2AServer Add three tests to A2AServerServiceCollectionExtensionsTests that verify custom keyed services are actually wired through to the A2AServer, not just that the server resolves non-null: - Custom IAgentHandler: verifies the keyed handler is invoked when processing a SendMessageRequest instead of the default A2AAgentHandler. - Custom AgentSessionStore (no handler): verifies the keyed session store's GetSessionAsync is called during request processing when no custom handler is registered. - Default stores end-to-end: verifies the InMemoryAgentSessionStore and InMemoryTaskStore defaults successfully process a request. Uses a new CreateAgentMockForRequests helper that includes SerializeSessionCoreAsync setup needed by InMemoryAgentSessionStore. All tests call A2AServer.SendMessageAsync directly (no HTTP layer needed) and use CancellationToken timeouts to guard against hangs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
560 lines
21 KiB
C#
560 lines
21 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using Microsoft.Agents.AI.Hosting.A2A.UnitTests.Internal;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Moq;
|
|
|
|
namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Tests for A2AEndpointRouteBuilderExtensions and A2AServerServiceCollectionExtensions methods.
|
|
/// </summary>
|
|
public sealed class A2AEndpointRouteBuilderExtensionsTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null endpoints.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentBuilder_NullEndpoints_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
endpoints.MapA2AHttpJson(agentBuilder, "/a2a"));
|
|
|
|
Assert.Equal("endpoints", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null agentBuilder.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentBuilder_NullAgentBuilder_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
IHostedAgentBuilder agentBuilder = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AHttpJson(agentBuilder, "/a2a"));
|
|
|
|
Assert.Equal("agentBuilder", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson with IHostedAgentBuilder correctly maps the agent with default configuration.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentBuilder_DefaultConfiguration_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var result = app.MapA2AHttpJson(agentBuilder, "/a2a");
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson with string agent name correctly maps the agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentName_DefaultConfiguration_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
builder.Services.AddA2AServer("agent");
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var result = app.MapA2AHttpJson("agent", "/a2a");
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc with IHostedAgentBuilder correctly maps the agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAgentBuilder_DefaultConfiguration_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var result = app.MapA2AJsonRpc(agentBuilder, "/a2a");
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc with string agent name correctly maps the agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAgentName_DefaultConfiguration_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
builder.Services.AddA2AServer("agent");
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var result = app.MapA2AJsonRpc("agent", "/a2a");
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that both MapA2AHttpJson and MapA2AJsonRpc can be called for the same agent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_And_MapA2AJsonRpc_SameAgent_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var httpResult = app.MapA2AHttpJson(agentBuilder, "/a2a");
|
|
var rpcResult = app.MapA2AJsonRpc(agentBuilder, "/a2a");
|
|
Assert.NotNull(httpResult);
|
|
Assert.NotNull(rpcResult);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that multiple agents can be mapped to different paths.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_MultipleAgents_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agent1Builder = builder.AddAIAgent("agent1", "Instructions1", chatClientServiceKey: "chat-client");
|
|
IHostedAgentBuilder agent2Builder = builder.AddAIAgent("agent2", "Instructions2", chatClientServiceKey: "chat-client");
|
|
agent1Builder.AddA2AServer();
|
|
agent2Builder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
app.MapA2AHttpJson(agent1Builder, "/a2a/agent1");
|
|
app.MapA2AHttpJson(agent2Builder, "/a2a/agent2");
|
|
Assert.NotNull(app);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that custom paths can be specified for A2A endpoints.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithCustomPath_AcceptsValidPath()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
app.MapA2AHttpJson(agentBuilder, "/custom/a2a/path");
|
|
Assert.NotNull(app);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that AddA2AServer with custom A2AServerRegistrationOptions succeeds.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AddA2AServer_WithCustomOptions_Succeeds()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer(options => options.AgentRunMode = AgentRunMode.AllowBackgroundIfSupported);
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert - Should not throw
|
|
var result = app.MapA2AHttpJson(agentBuilder, "/a2a");
|
|
Assert.NotNull(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null endpoints when using string agent name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentName_NullEndpoints_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
endpoints.MapA2AHttpJson("agent", "/a2a"));
|
|
|
|
Assert.Equal("endpoints", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc throws ArgumentNullException for null endpoints when using string agent name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAgentName_NullEndpoints_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
endpoints.MapA2AJsonRpc("agent", "/a2a"));
|
|
|
|
Assert.Equal("endpoints", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null agentName.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentName_NullAgentName_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AHttpJson((string)null!, "/a2a"));
|
|
|
|
Assert.Equal("agentName", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentException for empty agentName.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAgentName_EmptyAgentName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
|
|
app.MapA2AHttpJson(string.Empty, "/a2a"));
|
|
|
|
Assert.Equal("agentName", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null path.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_NullPath_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AHttpJson(agentBuilder, null!));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentException for whitespace-only path.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WhitespacePath_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
agentBuilder.AddA2AServer();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
Assert.Throws<ArgumentException>(() =>
|
|
app.MapA2AHttpJson(agentBuilder, " "));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that AddA2AServer throws ArgumentNullException for null services.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AddA2AServer_NullServices_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
IServiceCollection services = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
services.AddA2AServer("agent"));
|
|
|
|
Assert.Equal("services", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that AddA2AServer throws ArgumentNullException for null agentName.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AddA2AServer_NullAgentName_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
IServiceCollection services = new ServiceCollection();
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
services.AddA2AServer((string)null!));
|
|
|
|
Assert.Equal("agentName", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that AddA2AServer throws ArgumentException for empty agentName.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AddA2AServer_EmptyAgentName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
IServiceCollection services = new ServiceCollection();
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
|
|
services.AddA2AServer(string.Empty));
|
|
|
|
Assert.Equal("agentName", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that AddA2AServer on IHostedAgentBuilder throws ArgumentNullException for null builder.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AddA2AServer_NullAgentBuilder_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
IHostedAgentBuilder agentBuilder = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
agentBuilder.AddA2AServer());
|
|
|
|
Assert.Equal("agentBuilder", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for null AIAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAIAgent_NullAgent_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
AIAgent agent = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AHttpJson(agent, "/a2a"));
|
|
|
|
Assert.Equal("agent", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentNullException for AIAgent with null Name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAIAgent_NullName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
var agentMock = new Mock<AIAgent>();
|
|
agentMock.Setup(a => a.Name).Returns((string?)null);
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AHttpJson(agentMock.Object, "/a2a"));
|
|
|
|
Assert.Equal("agent.Name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws ArgumentException for AIAgent with whitespace Name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithAIAgent_WhitespaceName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
var agentMock = new Mock<AIAgent>();
|
|
agentMock.Setup(a => a.Name).Returns(" ");
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
|
|
app.MapA2AHttpJson(agentMock.Object, "/a2a"));
|
|
|
|
Assert.Equal("agent.Name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc throws ArgumentNullException for null AIAgent.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAIAgent_NullAgent_ThrowsArgumentNullException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
AIAgent agent = null!;
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AJsonRpc(agent, "/a2a"));
|
|
|
|
Assert.Equal("agent", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc throws ArgumentNullException for AIAgent with null Name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAIAgent_NullName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
var agentMock = new Mock<AIAgent>();
|
|
agentMock.Setup(a => a.Name).Returns((string?)null);
|
|
|
|
// Act & Assert
|
|
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
|
app.MapA2AJsonRpc(agentMock.Object, "/a2a"));
|
|
|
|
Assert.Equal("agent.Name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc throws ArgumentException for AIAgent with whitespace Name.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithAIAgent_WhitespaceName_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
var agentMock = new Mock<AIAgent>();
|
|
agentMock.Setup(a => a.Name).Returns(" ");
|
|
|
|
// Act & Assert
|
|
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
|
|
app.MapA2AJsonRpc(agentMock.Object, "/a2a"));
|
|
|
|
Assert.Equal("agent.Name", exception.ParamName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AHttpJson throws InvalidOperationException when no A2AServer has been
|
|
/// registered for the specified agent via AddA2AServer.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AHttpJson_WithoutAddA2AServer_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = Assert.Throws<InvalidOperationException>(() =>
|
|
app.MapA2AHttpJson("agent", "/a2a"));
|
|
|
|
Assert.Contains("agent", exception.Message);
|
|
Assert.Contains("AddA2AServer", exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that MapA2AJsonRpc throws InvalidOperationException when no A2AServer has been
|
|
/// registered for the specified agent via AddA2AServer.
|
|
/// </summary>
|
|
[Fact]
|
|
public void MapA2AJsonRpc_WithoutAddA2AServer_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
|
IChatClient mockChatClient = new DummyChatClient();
|
|
builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
|
|
builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
|
|
builder.Services.AddLogging();
|
|
using WebApplication app = builder.Build();
|
|
|
|
// Act & Assert
|
|
InvalidOperationException exception = Assert.Throws<InvalidOperationException>(() =>
|
|
app.MapA2AJsonRpc("agent", "/a2a"));
|
|
|
|
Assert.Contains("agent", exception.Message);
|
|
Assert.Contains("AddA2AServer", exception.Message);
|
|
}
|
|
}
|