Skip to content

Commit 4feafd9

Browse files
feat: add unit tests (#14)
* add tests * add tests * fix test * change lists initialization * address comments * restore Directory.Packages.props formatting * remove JsonRpcErrorResponse * remove the unnecessary json ignore attribute
1 parent d2b7d79 commit 4feafd9

File tree

11 files changed

+538
-18
lines changed

11 files changed

+538
-18
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
2727
<PackageVersion Include="Polly" Version="8.4.2" />
2828
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
29-
29+
3030
<!-- Build / infrastructure dependencies -->
3131
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
3232
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />

src/A2A.AspNetCore/A2AJsonRpcProcessor.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ internal static async Task<JsonRpcResponseResult> SingleResponse(TaskManager tas
108108
}
109109

110110
return new JsonRpcResponseResult(response);
111-
}
111+
}
112+
112113
internal static async Task<IResult> StreamResponse(TaskManager taskManager, string requestId, string method, JsonElement? parameters)
113114
{
114115
using var activity = ActivitySource.StartActivity("StreamResponse", ActivityKind.Server);
@@ -169,11 +170,11 @@ public JsonRpcResponseResult(JsonRpcResponse jsonRpcResponse)
169170
public async Task ExecuteAsync(HttpContext httpContext)
170171
{
171172
httpContext.Response.ContentType = "application/json";
172-
httpContext.Response.StatusCode = jsonRpcResponse is JsonRpcErrorResponse ?
173+
httpContext.Response.StatusCode = jsonRpcResponse.Error is not null ?
173174
StatusCodes.Status400BadRequest :
174175
StatusCodes.Status200OK;
175176

176-
await JsonSerializer.SerializeAsync(httpContext.Response.Body, jsonRpcResponse, A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcResponse)));
177+
await JsonSerializer.SerializeAsync(httpContext.Response.Body, jsonRpcResponse, A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcResponse)));
177178
}
178179
}
179180

src/A2A/A2AJsonUtilities.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public static partial class A2AJsonUtilities
4545
[JsonSerializable(typeof(MessageSendParams))]
4646
[JsonSerializable(typeof(TaskIdParams))]
4747
[JsonSerializable(typeof(TaskPushNotificationConfig))]
48+
[JsonSerializable(typeof(TaskQueryParams))]
4849

4950
[ExcludeFromCodeCoverage]
5051
internal sealed partial class JsonContext : JsonSerializerContext;

src/A2A/JsonRpc/JsonRpcError.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace A2A;
44

55
public static class JsonRpcErrorResponses
66
{
7-
public static JsonRpcErrorResponse InvalidParamsResponse(string requestId) => new()
7+
public static JsonRpcResponse InvalidParamsResponse(string requestId) => new()
88
{
99
Id = requestId,
1010
Error = new JsonRpcError()
@@ -14,7 +14,7 @@ public static class JsonRpcErrorResponses
1414
},
1515
};
1616

17-
public static JsonRpcErrorResponse MethodNotFoundResponse(string requestId) => new()
17+
public static JsonRpcResponse MethodNotFoundResponse(string requestId) => new()
1818
{
1919
Id = requestId,
2020
Error = new JsonRpcError
@@ -24,7 +24,7 @@ public static class JsonRpcErrorResponses
2424
},
2525
};
2626

27-
public static JsonRpcErrorResponse InternalErrorResponse(string requestId, string message) => new()
27+
public static JsonRpcResponse InternalErrorResponse(string requestId, string message) => new()
2828
{
2929
Id = requestId,
3030
Error = new JsonRpcError
@@ -34,7 +34,7 @@ public static class JsonRpcErrorResponses
3434
},
3535
};
3636

37-
public static JsonRpcErrorResponse ParseErrorResponse(string requestId, string? message) => new()
37+
public static JsonRpcResponse ParseErrorResponse(string requestId, string? message) => new()
3838
{
3939
Id = requestId,
4040
Error = new JsonRpcError

src/A2A/JsonRpc/JsonRpcResponse.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public class JsonRpcResponse
1616
[JsonPropertyName("result")]
1717
public JsonNode? Result { get; set; }
1818

19+
[JsonPropertyName("error")]
20+
public JsonRpcError? Error { get; set; }
21+
1922
public static JsonRpcResponse CreateJsonRpcResponse<T>(string requestId, T result, JsonTypeInfo? resultTypeInfo = null)
2023
{
2124
resultTypeInfo ??= (JsonTypeInfo<T>)A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(T));
@@ -28,12 +31,6 @@ public static JsonRpcResponse CreateJsonRpcResponse<T>(string requestId, T resul
2831
}
2932
}
3033

31-
public class JsonRpcErrorResponse : JsonRpcResponse
32-
{
33-
[JsonPropertyName("error")]
34-
public JsonRpcError? Error { get; set; }
35-
36-
}
3734
// public class JsonRpcResponse<T> : JsonRpcResponse
3835
// {
3936
// public static JsonRpcResponse<T> CreateJsonRpcResponse(string requestId, T result)

src/A2A/Server/TaskManager.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ public async Task<AgentTask> CreateTaskAsync(string? contextId = null)
157157

158158
public async Task<IAsyncEnumerable<A2AEvent>> SendMessageStreamAsync(MessageSendParams messageSendParams)
159159
{
160-
161160
using var activity = ActivitySource.StartActivity("SendSubscribe", ActivityKind.Server);
162161
AgentTask? agentTask = null;
163162

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
using Microsoft.AspNetCore.Http;
2+
using System.Text.Json;
3+
4+
namespace A2A.AspNetCore.Tests;
5+
6+
public class A2AJsonRpcProcessorTests
7+
{
8+
[Fact]
9+
public async Task ProcessRequest_SingleResponse_MessageSend_Works()
10+
{
11+
// Arrange
12+
var taskManager = new TaskManager();
13+
var sendParams = new MessageSendParams
14+
{
15+
Message = new Message { MessageId = "test-message-id", Parts = [new TextPart { Text = "hi" }] }
16+
};
17+
var req = new JsonRpcRequest
18+
{
19+
Id = "1",
20+
Method = A2AMethods.MessageSend,
21+
Params = ToJsonElement(sendParams)
22+
};
23+
24+
// Act
25+
var result = await A2AJsonRpcProcessor.ProcessRequest(taskManager, req);
26+
27+
// Assert
28+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
29+
30+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
31+
32+
Assert.Equal(StatusCodes.Status200OK, StatusCode);
33+
Assert.Equal("application/json", ContentType);
34+
35+
Assert.NotNull(BodyContent.Result);
36+
var agentTask = JsonSerializer.Deserialize<AgentTask>(BodyContent.Result, A2AJsonUtilities.DefaultOptions);
37+
38+
Assert.NotNull(agentTask);
39+
Assert.Equal(TaskState.Submitted, agentTask.Status.State);
40+
Assert.NotEmpty(agentTask.History);
41+
Assert.Equal(MessageRole.User, agentTask.History[0].Role);
42+
Assert.Equal("hi", ((TextPart)agentTask.History[0].Parts[0]).Text);
43+
Assert.Equal("test-message-id", agentTask.History[0].MessageId);
44+
}
45+
46+
[Fact]
47+
public async Task ProcessRequest_SingleResponse_InvalidParams_ReturnsError()
48+
{
49+
// Arrange
50+
var taskManager = new TaskManager();
51+
var req = new JsonRpcRequest
52+
{
53+
Id = "2",
54+
Method = A2AMethods.MessageSend,
55+
Params = null
56+
};
57+
58+
// Act
59+
var result = await A2AJsonRpcProcessor.ProcessRequest(taskManager, req);
60+
61+
// Assert
62+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
63+
64+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
65+
66+
Assert.Equal(StatusCodes.Status400BadRequest, StatusCode);
67+
Assert.Equal("application/json", ContentType);
68+
69+
Assert.NotNull(BodyContent);
70+
Assert.Null(BodyContent.Result);
71+
72+
Assert.NotNull(BodyContent.Error);
73+
Assert.Equal(-32602, BodyContent.Error!.Code); // Invalid params
74+
Assert.Equal("Invalid parameters", BodyContent.Error.Message);
75+
}
76+
77+
[Fact]
78+
public async Task SingleResponse_TaskGet_Works()
79+
{
80+
// Arrange
81+
var taskManager = new TaskManager();
82+
var task = await taskManager.CreateTaskAsync();
83+
84+
var queryParams = new TaskQueryParams { Id = task.Id };
85+
86+
// Act
87+
var result = await A2AJsonRpcProcessor.SingleResponse(taskManager, "4", A2AMethods.TaskGet, ToJsonElement(queryParams));
88+
89+
// Assert
90+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
91+
92+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
93+
94+
Assert.Equal(StatusCodes.Status200OK, StatusCode);
95+
Assert.Equal("application/json", ContentType);
96+
Assert.NotNull(BodyContent);
97+
98+
var agentTask = JsonSerializer.Deserialize<AgentTask>(BodyContent.Result, A2AJsonUtilities.DefaultOptions);
99+
Assert.NotNull(agentTask);
100+
Assert.Equal(TaskState.Submitted, agentTask.Status.State);
101+
Assert.Empty(agentTask.History);
102+
}
103+
104+
[Fact]
105+
public async Task SingleResponse_TaskCancel_Works()
106+
{
107+
// Arrange
108+
var taskManager = new TaskManager();
109+
var newTask = await taskManager.CreateTaskAsync();
110+
var cancelParams = new TaskIdParams { Id = newTask.Id };
111+
112+
// Act
113+
var result = await A2AJsonRpcProcessor.SingleResponse(taskManager, "5", A2AMethods.TaskCancel, ToJsonElement(cancelParams));
114+
115+
// Assert
116+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
117+
118+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
119+
120+
Assert.Equal(StatusCodes.Status200OK, StatusCode);
121+
Assert.Equal("application/json", ContentType);
122+
Assert.NotNull(BodyContent);
123+
124+
var agentTask = JsonSerializer.Deserialize<AgentTask>(BodyContent.Result, A2AJsonUtilities.DefaultOptions);
125+
Assert.NotNull(agentTask);
126+
Assert.Equal(TaskState.Canceled, agentTask.Status.State);
127+
Assert.Empty(agentTask.History);
128+
}
129+
130+
[Fact]
131+
public async Task SingleResponse_TaskPushNotificationConfigSet_Works()
132+
{
133+
// Arrange
134+
var taskManager = new TaskManager();
135+
var config = new TaskPushNotificationConfig
136+
{
137+
Id = "test-task",
138+
PushNotificationConfig = new PushNotificationConfig()
139+
{
140+
Url = "https://example.com/notify",
141+
}
142+
};
143+
144+
// Act
145+
var result = await A2AJsonRpcProcessor.SingleResponse(taskManager, "6", A2AMethods.TaskPushNotificationConfigSet, ToJsonElement(config));
146+
147+
// Assert
148+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
149+
150+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
151+
152+
Assert.Equal(StatusCodes.Status200OK, StatusCode);
153+
Assert.Equal("application/json", ContentType);
154+
Assert.NotNull(BodyContent);
155+
156+
var notificationConfig = JsonSerializer.Deserialize<TaskPushNotificationConfig>(BodyContent.Result, A2AJsonUtilities.DefaultOptions);
157+
Assert.NotNull(notificationConfig);
158+
159+
Assert.Equal("test-task", notificationConfig.Id);
160+
Assert.Equal("https://example.com/notify", notificationConfig.PushNotificationConfig.Url);
161+
}
162+
163+
[Fact]
164+
public async Task SingleResponse_TaskPushNotificationConfigGet_Works()
165+
{
166+
// Arrange
167+
var taskManager = new TaskManager();
168+
var config = new TaskPushNotificationConfig {
169+
Id = "test-task",
170+
PushNotificationConfig = new PushNotificationConfig()
171+
{
172+
Url = "https://example.com/notify",
173+
}
174+
};
175+
await taskManager.SetPushNotificationAsync(config);
176+
var getParams = new TaskIdParams { Id = "test-task" };
177+
178+
// Act
179+
var result = await A2AJsonRpcProcessor.SingleResponse(taskManager, "7", A2AMethods.TaskPushNotificationConfigGet, ToJsonElement(getParams));
180+
181+
// Assert
182+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
183+
184+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
185+
186+
Assert.Equal(StatusCodes.Status200OK, StatusCode);
187+
Assert.Equal("application/json", ContentType);
188+
Assert.NotNull(BodyContent);
189+
190+
var notificationConfig = JsonSerializer.Deserialize<TaskPushNotificationConfig>(BodyContent.Result, A2AJsonUtilities.DefaultOptions);
191+
Assert.NotNull(notificationConfig);
192+
193+
Assert.Equal("test-task", notificationConfig.Id);
194+
Assert.Equal("https://example.com/notify", notificationConfig.PushNotificationConfig.Url);
195+
}
196+
197+
[Fact]
198+
public async Task StreamResponse_MessageStream_InvalidParams_ReturnsError()
199+
{
200+
// Arrange
201+
var taskManager = new TaskManager();
202+
203+
// Act
204+
var result = await A2AJsonRpcProcessor.StreamResponse(taskManager, "10", A2AMethods.MessageStream, null);
205+
206+
// Assert
207+
var responseResult = Assert.IsType<JsonRpcResponseResult>(result);
208+
209+
var (StatusCode, ContentType, BodyContent) = await GetJsonRpcResponseHttpDetails<JsonRpcResponse>(responseResult);
210+
211+
Assert.Equal(StatusCodes.Status400BadRequest, StatusCode);
212+
Assert.Equal("application/json", ContentType);
213+
214+
Assert.NotNull(BodyContent);
215+
Assert.Null(BodyContent.Result);
216+
217+
Assert.NotNull(BodyContent.Error);
218+
Assert.Equal(-32602, BodyContent.Error!.Code); // Invalid params
219+
Assert.Equal("Invalid parameters", BodyContent.Error.Message);
220+
}
221+
222+
private static JsonElement ToJsonElement<T>(T obj)
223+
{
224+
var json = JsonSerializer.Serialize(obj, A2AJsonUtilities.DefaultOptions);
225+
using var doc = JsonDocument.Parse(json);
226+
return doc.RootElement.Clone();
227+
}
228+
229+
private static async Task<(int StatusCode, string? ContentType, TBody BodyContent)> GetJsonRpcResponseHttpDetails<TBody>(JsonRpcResponseResult responseResult)
230+
{
231+
HttpContext context = new DefaultHttpContext();
232+
using var memoryStream = new MemoryStream();
233+
context.Response.Body = memoryStream;
234+
await responseResult.ExecuteAsync(context);
235+
236+
context.Response.Body.Position = 0;
237+
return (context.Response.StatusCode, context.Response.ContentType, JsonSerializer.Deserialize<TBody>(context.Response.Body, A2AJsonUtilities.DefaultOptions)!);
238+
}
239+
}

tests/A2A.UnitTests/JsonRpc/JsonRpcErrorResponseTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public void JsonRpcErrorResponse_Properties_SetAndGet()
1111
var error = new JsonRpcError { Code = 123, Message = "err" };
1212

1313
// Act
14-
var sut = new JsonRpcErrorResponse
14+
var sut = new JsonRpcResponse
1515
{
1616
Id = "id1",
1717
JsonRpc = "2.0",
@@ -31,7 +31,7 @@ public void JsonRpcErrorResponse_CanSetResult()
3131
var node = JsonValue.Create(42);
3232

3333
// Act
34-
var sut = new JsonRpcErrorResponse { Result = node };
34+
var sut = new JsonRpcResponse { Result = node };
3535

3636
// Assert
3737
Assert.Equal(42, sut.Result!.GetValue<int>());

0 commit comments

Comments
 (0)