// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using A2A; namespace Microsoft.Agents.AI.A2A.UnitTests; /// /// Unit tests for the class. /// public sealed class A2AAgentCardExtensionsTests { private readonly AgentCard _agentCard; public A2AAgentCardExtensionsTests() { this._agentCard = new AgentCard { Name = "Test Agent", Description = "A test agent for unit testing", SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }] }; } [Fact] public void AsAIAgent_ReturnsAIAgent() { // Act var agent = this._agentCard.AsAIAgent(); // Assert Assert.NotNull(agent); Assert.IsType(agent); Assert.Equal("Test Agent", agent.Name); Assert.Equal("A test agent for unit testing", agent.Description); } [Fact] public async Task RunIAgentAsync_SendsRequestToTheUrlSpecifiedInAgentCardAsync() { // Arrange using var handler = new HttpMessageHandlerStub(); using var httpClient = new HttpClient(handler, false); handler.ResponsesToReturn.Enqueue(new Message { Role = Role.Agent, Parts = [Part.FromText("Response")], }); var agent = this._agentCard.AsAIAgent(httpClient: httpClient); // Act await agent.RunAsync("Test input"); // Assert Assert.Single(handler.CapturedUris); Assert.Equal(new Uri("http://test-endpoint/agent"), handler.CapturedUris[0]); } [Fact] public async Task AsAIAgent_WithPreferredBindings_UsesMatchingInterfaceAsync() { // Arrange var card = new AgentCard { Name = "Multi-Interface Agent", Description = "An agent with multiple interfaces", SupportedInterfaces = [ new AgentInterface { Url = "http://first/agent", ProtocolBinding = ProtocolBindingNames.HttpJson }, new AgentInterface { Url = "http://second/agent", ProtocolBinding = ProtocolBindingNames.JsonRpc }, ] }; using var handler = new HttpMessageHandlerStub(); using var httpClient = new HttpClient(handler, false); handler.ResponsesToReturn.Enqueue(new Message { Role = Role.Agent, Parts = [Part.FromText("Response")], }); var options = new A2AClientOptions { PreferredBindings = [ProtocolBindingNames.JsonRpc] }; var agent = card.AsAIAgent(httpClient, options: options); // Act await agent.RunAsync("Test input"); // Assert Assert.Single(handler.CapturedUris); Assert.Equal(new Uri("http://second/agent"), handler.CapturedUris[0]); } [Fact] public void AsAIAgent_WithNullOptions_UsesDefaultBindingPreference() { // Arrange var card = new AgentCard { Name = "Default Options Agent", Description = "Tests default A2AClientOptions behavior", SupportedInterfaces = [ new AgentInterface { Url = "http://default/agent" }, ] }; // Act - null options should use defaults (HTTP+JSON first, JSON-RPC as fallback) var agent = card.AsAIAgent(options: null); // Assert Assert.NotNull(agent); Assert.IsType(agent); Assert.Equal("Default Options Agent", agent.Name); } [Fact] public void AsAIAgent_WithNoMatchingBinding_ThrowsException() { // Arrange var card = new AgentCard { Name = "Unmatched Binding Agent", Description = "Agent with unsupported binding only", SupportedInterfaces = [ new AgentInterface { Url = "http://grpc/agent", ProtocolBinding = "GRPC" }, ] }; var options = new A2AClientOptions { PreferredBindings = [ProtocolBindingNames.JsonRpc] }; // Act & Assert - factory should throw when no matching binding exists Assert.ThrowsAny(() => card.AsAIAgent(options: options)); } [Fact] public void AsAIAgent_WithNoSupportedInterfaces_ThrowsException() { // Arrange var card = new AgentCard { Name = "No Interfaces Agent", Description = "Agent with no supported interfaces", }; // Act & Assert Assert.ThrowsAny(() => card.AsAIAgent()); } [Fact] public void AsAIAgent_WithAgentOptions_OverridesCardValues() { // Arrange var card = new AgentCard { Name = "Card Agent", Description = "Card description", SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }] }; var agentOptions = new A2AAgentOptions { Id = "custom-id", Name = "Custom Agent", Description = "Custom description" }; // Act var agent = card.AsAIAgent(agentOptions); // Assert Assert.NotNull(agent); Assert.IsType(agent); Assert.Equal("custom-id", agent.Id); Assert.Equal("Custom Agent", agent.Name); Assert.Equal("Custom description", agent.Description); } [Fact] public void AsAIAgent_WithAgentOptions_FallsBackToCardValues() { // Arrange var card = new AgentCard { Name = "Card Agent", Description = "Card description", SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }] }; var agentOptions = new A2AAgentOptions { Id = "custom-id" }; // Act var agent = card.AsAIAgent(agentOptions); // Assert Assert.NotNull(agent); Assert.Equal("custom-id", agent.Id); Assert.Equal("Card Agent", agent.Name); Assert.Equal("Card description", agent.Description); } [Fact] public void AsAIAgent_WithEmptyAgentOptions_UsesCardValues() { // Arrange var card = new AgentCard { Name = "Card Agent", Description = "Card description", SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }] }; // Act var agent = card.AsAIAgent(new A2AAgentOptions()); // Assert Assert.NotNull(agent); Assert.Equal("Card Agent", agent.Name); Assert.Equal("Card description", agent.Description); } internal sealed class HttpMessageHandlerStub : HttpMessageHandler { public Queue ResponsesToReturn { get; } = new(); public List CapturedUris { get; } = []; protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { this.CapturedUris.Add(request.RequestUri!); var response = this.ResponsesToReturn.Dequeue(); if (response is AgentCard agentCard) { var json = JsonSerializer.Serialize(agentCard); return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(json, Encoding.UTF8, "application/json") }; } else if (response is Message message) { var sendMessageResponse = new SendMessageResponse { Message = message }; var jsonRpcResponse = new JsonRpcResponse { Id = "response-id", Result = JsonSerializer.SerializeToNode(sendMessageResponse, A2AJsonUtilities.DefaultOptions) }; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions), Encoding.UTF8, "application/json") }; } // Return empty agent card if none specified var emptyCard = new AgentCard(); var emptyJson = JsonSerializer.Serialize(emptyCard); return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(emptyJson, Encoding.UTF8, "application/json") }; } } }