Files
wehub-resource-sync db620d33df
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:25 +08:00

288 lines
8.6 KiB
C#

// 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;
/// <summary>
/// Unit tests for the <see cref="A2AAgentCardExtensions"/> class.
/// </summary>
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<A2AAgent>(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<A2AAgent>(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<Exception>(() => 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<Exception>(() => 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<A2AAgent>(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<Uri> CapturedUris { get; } = [];
protected override async Task<HttpResponseMessage> 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")
};
}
}
}