mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
address automated code review and formatting issues
This commit is contained in:
@@ -52,7 +52,7 @@ internal class A2AContinuationToken : ResponseContinuationToken
|
||||
{
|
||||
case "taskId":
|
||||
reader.Read();
|
||||
taskId = reader.GetString()!;
|
||||
taskId = reader.GetString() ?? throw new JsonException("The 'taskId' property must contain a non-null string value.");
|
||||
break;
|
||||
default:
|
||||
throw new JsonException($"Unrecognized property '{propertyName}'.");
|
||||
|
||||
@@ -138,7 +138,7 @@ internal sealed class A2AAgentHandler : IAgentHandler
|
||||
catch (Exception)
|
||||
{
|
||||
var failUpdater = new TaskUpdater(eventQueue, context.TaskId, contextId);
|
||||
await failUpdater.FailAsync(message: null, cancellationToken).ConfigureAwait(false);
|
||||
await failUpdater.FailAsync(message: null, CancellationToken.None).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,18 @@ public sealed class A2AContinuationTokenTests
|
||||
Assert.Throws<ArgumentException>(() => A2AContinuationToken.FromToken(emptyToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromToken_WithNullTaskIdValue_ThrowsJsonException()
|
||||
{
|
||||
// Arrange
|
||||
var jsonWithNullTaskId = System.Text.Encoding.UTF8.GetBytes("{ \"taskId\": null }").AsMemory();
|
||||
var mockToken = new MockResponseContinuationToken(jsonWithNullTaskId);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<JsonException>(() => A2AContinuationToken.FromToken(mockToken));
|
||||
Assert.Contains("taskId", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromToken_WithMissingTaskIdProperty_ThrowsException()
|
||||
{
|
||||
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
@@ -368,6 +368,47 @@ public sealed class A2AAgentHandlerTests
|
||||
Assert.True(events.StatusUpdates.Count > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when the agent throws during a continuation and the cancellation token
|
||||
/// is already cancelled, the handler still emits a Failed status and re-throws the
|
||||
/// original exception (not an OperationCanceledException from FailAsync).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_OnContinuation_WhenAgentThrowsWithCancelledToken_StillEmitsFailedStatusAsync()
|
||||
{
|
||||
// Arrange
|
||||
int callCount = 0;
|
||||
Mock<AIAgent> agentMock = CreateAgentMockWithCallCount(ref callCount, _ =>
|
||||
throw new InvalidOperationException("Agent failed"));
|
||||
A2AAgentHandler handler = CreateHandler(agentMock);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel(); // Pre-cancel the token
|
||||
|
||||
// Act & Assert - the original InvalidOperationException should be thrown, not OperationCanceledException
|
||||
var events = new EventCollector();
|
||||
var eventQueue = new AgentEventQueue();
|
||||
var readerTask = ReadEventsAsync(eventQueue, events);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
handler.ExecuteAsync(
|
||||
new RequestContext
|
||||
{
|
||||
StreamingResponse = false,
|
||||
Message = new Message { MessageId = "empty", Role = Role.User, Parts = [] },
|
||||
TaskId = "task-1",
|
||||
ContextId = "ctx-1",
|
||||
|
||||
Task = new AgentTask { Id = "task-1", ContextId = "ctx-1", History = [new Message { Role = Role.User, Parts = [new Part { Text = "Hello" }] }] }
|
||||
},
|
||||
eventQueue,
|
||||
cts.Token));
|
||||
eventQueue.Complete(null);
|
||||
await readerTask;
|
||||
|
||||
// Assert - should have emitted Failed status even with a cancelled token
|
||||
Assert.True(events.StatusUpdates.Count > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that when the agent throws OperationCanceledException during a continuation,
|
||||
/// no Failed status is emitted.
|
||||
|
||||
+119
@@ -5,6 +5,7 @@ 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;
|
||||
|
||||
@@ -392,6 +393,124 @@ public sealed class A2AEndpointRouteBuilderExtensionsTests
|
||||
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.
|
||||
|
||||
+36
@@ -251,6 +251,42 @@ public sealed class A2AServerServiceCollectionExtensionsTests
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddA2AServer(agent: null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AddA2AServer with an agent instance throws when the agent's Name is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AddA2AServer_WithAgent_NullName_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
var agentMock = new Mock<AIAgent>();
|
||||
agentMock.Setup(a => a.Name).Returns((string?)null);
|
||||
|
||||
// Act & Assert
|
||||
ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() =>
|
||||
services.AddA2AServer(agentMock.Object));
|
||||
|
||||
Assert.Equal("agent.Name", exception.ParamName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AddA2AServer with an agent instance throws when the agent's Name is whitespace.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AddA2AServer_WithAgent_WhitespaceName_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
var agentMock = new Mock<AIAgent>();
|
||||
agentMock.Setup(a => a.Name).Returns(" ");
|
||||
|
||||
// Act & Assert
|
||||
ArgumentException exception = Assert.Throws<ArgumentException>(() =>
|
||||
services.AddA2AServer(agentMock.Object));
|
||||
|
||||
Assert.Equal("agent.Name", exception.ParamName);
|
||||
}
|
||||
|
||||
private static Mock<AIAgent> CreateAgentMock(string name)
|
||||
{
|
||||
Mock<AIAgent> agentMock = new() { CallBase = true };
|
||||
|
||||
+65
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI.Hosting.A2A.Converters;
|
||||
@@ -82,4 +83,68 @@ public class MessageConverterTests
|
||||
var textContent = Assert.IsType<TextContent>(chatMessage.Contents.First());
|
||||
Assert.Equal("Hello, world!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToParts_NullList_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
IList<ChatMessage>? messages = null;
|
||||
|
||||
// Act
|
||||
var result = messages!.ToParts();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToParts_EmptyList_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
IList<ChatMessage> messages = [];
|
||||
|
||||
// Act
|
||||
var result = messages.ToParts();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToParts_WithTextContent_ReturnsTextPart()
|
||||
{
|
||||
// Arrange
|
||||
IList<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.Assistant, "Hello from the agent!")
|
||||
];
|
||||
|
||||
// Act
|
||||
var result = messages.ToParts();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal("Hello from the agent!", result[0].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToParts_WithMultipleMessages_ReturnsAllParts()
|
||||
{
|
||||
// Arrange
|
||||
IList<ChatMessage> messages =
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "First message"),
|
||||
new ChatMessage(ChatRole.Assistant, "Second message")
|
||||
];
|
||||
|
||||
// Act
|
||||
var result = messages.ToParts();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("First message", result[0].Text);
|
||||
Assert.Equal("Second message", result[1].Text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user