11package io .a2a .server .apps .quarkus ;
22
3- import static io .restassured .RestAssured .given ;
4- import static org .junit .jupiter .api .Assertions .assertEquals ;
5- import static org .junit .jupiter .api .Assertions .assertNull ;
6- import static org .junit .jupiter .api .Assertions .fail ;
7- import static org .wildfly .common .Assert .assertNotNull ;
8- import jakarta .ws .rs .core .MediaType ;
9-
10- import java .net .URI ;
11- import java .net .http .HttpClient ;
12- import java .net .http .HttpRequest ;
13- import java .net .http .HttpResponse ;
14- import java .util .concurrent .CompletableFuture ;
15- import java .util .concurrent .CountDownLatch ;
16- import java .util .concurrent .TimeUnit ;
17- import java .util .concurrent .atomic .AtomicReference ;
18- import java .util .stream .Stream ;
19-
20- import com .fasterxml .jackson .core .JsonProcessingException ;
21-
223import io .a2a .server .apps .common .AbstractA2AServerTest ;
23- import io .a2a .spec .A2AClientException ;
24- import io .a2a .spec .InvalidParamsError ;
25- import io .a2a .spec .InvalidRequestError ;
26- import io .a2a .spec .JSONParseError ;
27- import io .a2a .spec .JSONRPCErrorResponse ;
28- import io .a2a .spec .Message ;
29- import io .a2a .spec .MessageSendParams ;
30- import io .a2a .spec .MethodNotFoundError ;
31- import io .a2a .spec .Part ;
32- import io .a2a .spec .SendStreamingMessageRequest ;
33- import io .a2a .spec .SendStreamingMessageResponse ;
34- import io .a2a .spec .StreamingJSONRPCRequest ;
35- import io .a2a .spec .Task ;
36- import io .a2a .spec .TaskQueryParams ;
37- import io .a2a .spec .TaskState ;
38- import io .a2a .spec .TextPart ;
394import io .a2a .spec .TransportProtocol ;
40- import io .a2a .util .Utils ;
415import io .quarkus .test .junit .QuarkusTest ;
426
43- import org .junit .jupiter .api .Assertions ;
44- import org .junit .jupiter .api .Test ;
45-
467@ QuarkusTest
478public class QuarkusA2AJSONRPCTest extends AbstractA2AServerTest {
489
@@ -59,242 +20,4 @@ protected String getTransportProtocol() {
5920 protected String getTransportUrl () {
6021 return "http://localhost:8081" ;
6122 }
62-
63- @ Test
64- public void testMalformedJSONRPCRequest () {
65- // missing closing bracket
66- String malformedRequest = "{\" jsonrpc\" : \" 2.0\" , \" method\" : \" message/send\" , \" params\" : {\" foo\" : \" bar\" }" ;
67- JSONRPCErrorResponse response = given ()
68- .contentType (MediaType .APPLICATION_JSON )
69- .body (malformedRequest )
70- .when ()
71- .post ("/" )
72- .then ()
73- .statusCode (200 )
74- .extract ()
75- .as (JSONRPCErrorResponse .class );
76- assertNotNull (response .getError ());
77- assertEquals (new JSONParseError ().getCode (), response .getError ().getCode ());
78- }
79-
80- @ Test
81- public void testInvalidParamsJSONRPCRequest () {
82- String invalidParamsRequest = """
83- {"jsonrpc": "2.0", "method": "message/send", "params": "not_a_dict", "id": "1"}
84- """ ;
85- testInvalidParams (invalidParamsRequest );
86-
87- invalidParamsRequest = """
88- {"jsonrpc": "2.0", "method": "message/send", "params": {"message": {"parts": "invalid"}}, "id": "1"}
89- """ ;
90- testInvalidParams (invalidParamsRequest );
91- }
92-
93- private void testInvalidParams (String invalidParamsRequest ) {
94- JSONRPCErrorResponse response = given ()
95- .contentType (MediaType .APPLICATION_JSON )
96- .body (invalidParamsRequest )
97- .when ()
98- .post ("/" )
99- .then ()
100- .statusCode (200 )
101- .extract ()
102- .as (JSONRPCErrorResponse .class );
103- assertNotNull (response .getError ());
104- assertEquals (new InvalidParamsError ().getCode (), response .getError ().getCode ());
105- assertEquals ("1" , response .getId ());
106- }
107-
108- @ Test
109- public void testInvalidJSONRPCRequestMissingJsonrpc () {
110- String invalidRequest = """
111- {
112- "method": "message/send",
113- "params": {}
114- }
115- """ ;
116- JSONRPCErrorResponse response = given ()
117- .contentType (MediaType .APPLICATION_JSON )
118- .body (invalidRequest )
119- .when ()
120- .post ("/" )
121- .then ()
122- .statusCode (200 )
123- .extract ()
124- .as (JSONRPCErrorResponse .class );
125- assertNotNull (response .getError ());
126- assertEquals (new InvalidRequestError ().getCode (), response .getError ().getCode ());
127- }
128-
129- @ Test
130- public void testInvalidJSONRPCRequestMissingMethod () {
131- String invalidRequest = """
132- {"jsonrpc": "2.0", "params": {}}
133- """ ;
134- JSONRPCErrorResponse response = given ()
135- .contentType (MediaType .APPLICATION_JSON )
136- .body (invalidRequest )
137- .when ()
138- .post ("/" )
139- .then ()
140- .statusCode (200 )
141- .extract ()
142- .as (JSONRPCErrorResponse .class );
143- assertNotNull (response .getError ());
144- assertEquals (new InvalidRequestError ().getCode (), response .getError ().getCode ());
145- }
146-
147- @ Test
148- public void testInvalidJSONRPCRequestInvalidId () {
149- String invalidRequest = """
150- {"jsonrpc": "2.0", "method": "message/send", "params": {}, "id": {"bad": "type"}}
151- """ ;
152- JSONRPCErrorResponse response = given ()
153- .contentType (MediaType .APPLICATION_JSON )
154- .body (invalidRequest )
155- .when ()
156- .post ("/" )
157- .then ()
158- .statusCode (200 )
159- .extract ()
160- .as (JSONRPCErrorResponse .class );
161- assertNotNull (response .getError ());
162- assertEquals (new InvalidRequestError ().getCode (), response .getError ().getCode ());
163- }
164-
165- @ Test
166- public void testInvalidJSONRPCRequestNonExistentMethod () {
167- String invalidRequest = """
168- {"jsonrpc": "2.0", "method" : "nonexistent/method", "params": {}}
169- """ ;
170- JSONRPCErrorResponse response = given ()
171- .contentType (MediaType .APPLICATION_JSON )
172- .body (invalidRequest )
173- .when ()
174- .post ("/" )
175- .then ()
176- .statusCode (200 )
177- .extract ()
178- .as (JSONRPCErrorResponse .class );
179- assertNotNull (response .getError ());
180- assertEquals (new MethodNotFoundError ().getCode (), response .getError ().getCode ());
181- }
182-
183- @ Test
184- public void testNonStreamingMethodWithAcceptHeader () throws Exception {
185- testGetTask (MediaType .APPLICATION_JSON );
186- }
187-
188- private void testGetTask (String mediaType ) throws Exception {
189- saveTaskInTaskStore (MINIMAL_TASK );
190- try {
191- Task response = getClient ().getTask (new TaskQueryParams (MINIMAL_TASK .getId ()), null );
192- assertEquals ("task-123" , response .getId ());
193- assertEquals ("session-xyz" , response .getContextId ());
194- assertEquals (TaskState .SUBMITTED , response .getStatus ().state ());
195- } catch (A2AClientException e ) {
196- fail ("Unexpected exception during getTask: " + e .getMessage (), e );
197- } finally {
198- deleteTaskInTaskStore (MINIMAL_TASK .getId ());
199- }
200- }
201-
202- @ Test
203- public void testStreamingMethodWithAcceptHeader () throws Exception {
204- testSendStreamingMessage (MediaType .SERVER_SENT_EVENTS );
205- }
206-
207- @ Test
208- public void testSendMessageStreamNewMessageSuccess () throws Exception {
209- testSendStreamingMessage (null );
210- }
211-
212- private void testSendStreamingMessage (String mediaType ) throws Exception {
213- Message message = new Message .Builder (MESSAGE )
214- .taskId (MINIMAL_TASK .getId ())
215- .contextId (MINIMAL_TASK .getContextId ())
216- .build ();
217- SendStreamingMessageRequest request = new SendStreamingMessageRequest (
218- "1" , new MessageSendParams (message , null , null ));
219-
220- CompletableFuture <HttpResponse <Stream <String >>> responseFuture = initialiseStreamingRequest (request , mediaType );
221-
222- CountDownLatch latch = new CountDownLatch (1 );
223- AtomicReference <Throwable > errorRef = new AtomicReference <>();
224-
225- responseFuture .thenAccept (response -> {
226- if (response .statusCode () != 200 ) {
227- //errorRef.set(new IllegalStateException("Status code was " + response.statusCode()));
228- throw new IllegalStateException ("Status code was " + response .statusCode ());
229- }
230- response .body ().forEach (line -> {
231- try {
232- SendStreamingMessageResponse jsonResponse = extractJsonResponseFromSseLine (line );
233- if (jsonResponse != null ) {
234- assertNull (jsonResponse .getError ());
235- Message messageResponse = (Message ) jsonResponse .getResult ();
236- assertEquals (MESSAGE .getMessageId (), messageResponse .getMessageId ());
237- assertEquals (MESSAGE .getRole (), messageResponse .getRole ());
238- Part <?> part = messageResponse .getParts ().get (0 );
239- assertEquals (Part .Kind .TEXT , part .getKind ());
240- assertEquals ("test message" , ((TextPart ) part ).getText ());
241- latch .countDown ();
242- }
243- } catch (JsonProcessingException e ) {
244- throw new RuntimeException (e );
245- }
246- });
247- }).exceptionally (t -> {
248- if (!isStreamClosedError (t )) {
249- errorRef .set (t );
250- }
251- latch .countDown ();
252- return null ;
253- });
254-
255-
256- boolean dataRead = latch .await (20 , TimeUnit .SECONDS );
257- Assertions .assertTrue (dataRead );
258- Assertions .assertNull (errorRef .get ());
259-
260- }
261-
262- private CompletableFuture <HttpResponse <Stream <String >>> initialiseStreamingRequest (
263- StreamingJSONRPCRequest <?> request , String mediaType ) throws Exception {
264-
265- // Create the client
266- HttpClient client = HttpClient .newBuilder ()
267- .version (HttpClient .Version .HTTP_2 )
268- .build ();
269-
270- // Create the request
271- HttpRequest .Builder builder = HttpRequest .newBuilder ()
272- .uri (URI .create ("http://localhost:" + serverPort + "/" ))
273- .POST (HttpRequest .BodyPublishers .ofString (Utils .OBJECT_MAPPER .writeValueAsString (request )))
274- .header ("Content-Type" , APPLICATION_JSON );
275- if (mediaType != null ) {
276- builder .header ("Accept" , mediaType );
277- }
278- HttpRequest httpRequest = builder .build ();
279-
280-
281- // Send request async and return the CompletableFuture
282- return client .sendAsync (httpRequest , HttpResponse .BodyHandlers .ofLines ());
283- }
284-
285- private SendStreamingMessageResponse extractJsonResponseFromSseLine (String line ) throws JsonProcessingException {
286- line = extractSseData (line );
287- if (line != null ) {
288- return Utils .OBJECT_MAPPER .readValue (line , SendStreamingMessageResponse .class );
289- }
290- return null ;
291- }
292-
293- private static String extractSseData (String line ) {
294- if (line .startsWith ("data:" )) {
295- line = line .substring (5 ).trim ();
296- return line ;
297- }
298- return null ;
299- }
30023}
0 commit comments