// 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 A2ACardResolverExtensionsTests : IDisposable
{
private readonly HttpClient _httpClient;
private readonly HttpMessageHandlerStub _handler;
private readonly A2ACardResolver _resolver;
public A2ACardResolverExtensionsTests()
{
this._handler = new HttpMessageHandlerStub();
this._httpClient = new HttpClient(this._handler, false);
this._resolver = new A2ACardResolver(new Uri("http://test-host"), httpClient: this._httpClient);
}
[Fact]
public async Task GetAIAgentAsync_WithValidAgentCard_ReturnsAIAgentAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Test Agent",
Description = "A test agent for unit testing",
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
// Act
var agent = await this._resolver.GetAIAgentAsync();
// Assert
Assert.NotNull(agent);
Assert.IsType(agent);
Assert.Equal("Test Agent", agent.Name);
Assert.Equal("A test agent for unit testing", agent.Description);
// Verify that there was only one request made to retrieve the agent card
Assert.Single(this._handler.CapturedUris);
Assert.StartsWith("http://test-host/", this._handler.CapturedUris[0].ToString());
}
[Fact]
public async Task RunIAgentAsync_WithUrlFromAgentCard_SendsRequestToTheUrlAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
});
this._handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var agent = await this._resolver.GetAIAgentAsync(httpClient: this._httpClient);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Equal(2, this._handler.CapturedUris.Count); // One for getting the card, one for sending the message to the agent
Assert.Equal(new Uri("http://test-endpoint/agent"), this._handler.CapturedUris[1]);
}
[Fact]
public async Task GetAIAgentAsync_WithOptions_PassesOptionsToFactoryAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(new AgentCard
{
Name = "Options Agent",
Description = "Agent with multiple interfaces",
SupportedInterfaces =
[
new AgentInterface { Url = "http://httpjson/agent", ProtocolBinding = ProtocolBindingNames.HttpJson },
new AgentInterface { Url = "http://jsonrpc/agent", ProtocolBinding = ProtocolBindingNames.JsonRpc },
]
});
this._handler.ResponsesToReturn.Enqueue(new Message
{
Role = Role.Agent,
Parts = [Part.FromText("Response")],
});
var options = new A2AClientOptions
{
PreferredBindings = [ProtocolBindingNames.JsonRpc]
};
var agent = await this._resolver.GetAIAgentAsync(httpClient: this._httpClient, options: options);
// Act
await agent.RunAsync("Test input");
// Assert
Assert.Equal(2, this._handler.CapturedUris.Count);
Assert.Equal(new Uri("http://jsonrpc/agent"), this._handler.CapturedUris[1]);
}
[Fact]
public async Task GetAIAgentAsync_WithAgentOptions_OverridesCardValuesAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(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 = await this._resolver.GetAIAgentAsync(agentOptions, httpClient: this._httpClient);
// 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 async Task GetAIAgentAsync_WithAgentOptions_FallsBackToCardValuesAsync()
{
// Arrange
this._handler.ResponsesToReturn.Enqueue(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 = await this._resolver.GetAIAgentAsync(agentOptions, httpClient: this._httpClient);
// Assert
Assert.NotNull(agent);
Assert.Equal("custom-id", agent.Id);
Assert.Equal("Card Agent", agent.Name);
Assert.Equal("Card description", agent.Description);
}
public void Dispose()
{
this._handler.Dispose();
this._httpClient.Dispose();
}
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")
};
}
}
}