address automated code review and formatting issues

This commit is contained in:
SergeyMenshykh
2026-04-22 13:31:14 +01:00
Unverified
parent 8d208f3bb3
commit cd1f4c2a93
8 changed files with 275 additions and 3 deletions
@@ -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()
{
@@ -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.
@@ -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.
@@ -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 };
@@ -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);
}
}