@@ -18,24 +18,38 @@ import (
1818 "github.com/docker/cagent/pkg/config"
1919)
2020
21- type Response struct {
22- Jsonrpc string `json:"jsonrpc"`
23- ID string `json:"id"`
24- Result * Result `json:"result,omitempty"`
25- Error any `json:"error,omitempty"`
21+ // a2aResponse is a simplified representation of a JSON-RPC response
22+ // that only captures the fields we care about in tests.
23+ type a2aResponse struct {
24+ Jsonrpc string `json:"jsonrpc"`
25+ ID string `json:"id"`
26+ Error any `json:"error,omitempty"`
27+
28+ // Result holds the task with its artifacts.
29+ Result * struct {
30+ Artifacts []struct {
31+ Parts []struct {
32+ Kind string `json:"kind"`
33+ Text string `json:"text"`
34+ } `json:"parts"`
35+ } `json:"artifacts"`
36+ } `json:"result,omitempty"`
2637}
2738
28- type Result struct {
29- Artifacts []Artifact `json:"artifacts"`
30- }
31-
32- type Artifact struct {
33- Parts []Part `json:"parts"`
34- }
35-
36- type Part struct {
37- Kind string `json:"kind"`
38- Text string `json:"text"`
39+ // textParts returns all text parts across every artifact in the response.
40+ func (r * a2aResponse ) textParts () []string {
41+ if r .Result == nil {
42+ return nil
43+ }
44+ var texts []string
45+ for _ , a := range r .Result .Artifacts {
46+ for _ , p := range a .Parts {
47+ if p .Kind == "text" {
48+ texts = append (texts , p .Text )
49+ }
50+ }
51+ }
52+ return texts
3953}
4054
4155func TestA2AServer_AgentCard (t * testing.T ) {
@@ -58,55 +72,18 @@ func TestA2AServer_Invoke(t *testing.T) {
5872 _ , runConfig := startRecordingAIProxy (t )
5973 agentCard := startA2AServer (t , "testdata/basic.yaml" , runConfig )
6074
61- requestID := "test-request-1"
62- jsonRPCRequest := map [string ]any {
63- "jsonrpc" : "2.0" ,
64- "id" : requestID ,
65- "method" : "message/send" ,
66- "params" : map [string ]any {
67- "message" : map [string ]any {
68- "messageId" : "msg-1" ,
69- "role" : "user" ,
70- "parts" : []map [string ]any {
71- {
72- "kind" : "text" ,
73- "text" : "What is 2+2? Answer with just the number." ,
74- },
75- },
76- },
77- },
78- }
79-
80- requestBody , err := json .Marshal (jsonRPCRequest )
81- require .NoError (t , err )
82-
83- req , err := http .NewRequestWithContext (t .Context (), http .MethodPost , agentCard .URL , bytes .NewReader (requestBody ))
84- require .NoError (t , err )
85- req .Header .Set ("Content-Type" , "application/json" )
86-
87- resp , err := http .DefaultClient .Do (req )
88- require .NoError (t , err )
89- defer resp .Body .Close ()
90-
91- assert .Equal (t , http .StatusOK , resp .StatusCode )
92-
93- responseBody , err := io .ReadAll (resp .Body )
94- require .NoError (t , err )
75+ resp := sendA2AMessage (t , agentCard .URL , "test-request-1" , "msg-1" , "What is 2+2? Answer with just the number." )
9576
96- var jsonRPCResponse Response
97- err = json .Unmarshal (responseBody , & jsonRPCResponse )
98- require .NoError (t , err )
77+ assert .Equal (t , "2.0" , resp .Jsonrpc )
78+ assert .Equal (t , "test-request-1" , resp .ID )
79+ assert .Nil (t , resp .Error )
80+ require .NotNil (t , resp .Result )
9981
100- assert .Equal (t , "2.0" , jsonRPCResponse .Jsonrpc )
101- assert .Equal (t , requestID , jsonRPCResponse .ID )
102- assert .Nil (t , jsonRPCResponse .Error )
103- require .NotNil (t , jsonRPCResponse .Result )
104- assert .Len (t , jsonRPCResponse .Result .Artifacts , 1 )
105- assert .Len (t , jsonRPCResponse .Result .Artifacts [0 ].Parts , 2 )
106- assert .Equal (t , "text" , jsonRPCResponse .Result .Artifacts [0 ].Parts [0 ].Kind )
107- assert .Equal (t , "4" , jsonRPCResponse .Result .Artifacts [0 ].Parts [0 ].Text )
108- assert .Equal (t , "text" , jsonRPCResponse .Result .Artifacts [0 ].Parts [1 ].Kind )
109- assert .Equal (t , "4" , jsonRPCResponse .Result .Artifacts [0 ].Parts [1 ].Text )
82+ texts := resp .textParts ()
83+ require .NotEmpty (t , texts )
84+ for _ , text := range texts {
85+ assert .Equal (t , "4" , text )
86+ }
11087}
11188
11289func TestA2AServer_MultipleRequests (t * testing.T ) {
@@ -123,47 +100,13 @@ func TestA2AServer_MultipleRequests(t *testing.T) {
123100 for i , message := range messages {
124101 t .Run (fmt .Sprintf ("request_%d" , i ), func (t * testing.T ) {
125102 requestID := fmt .Sprintf ("test-request-%d" , i )
126- jsonRPCRequest := map [string ]any {
127- "jsonrpc" : "2.0" ,
128- "id" : requestID ,
129- "method" : "message/send" ,
130- "params" : map [string ]any {
131- "message" : map [string ]any {
132- "messageId" : fmt .Sprintf ("msg-%d" , i ),
133- "role" : "user" ,
134- "parts" : []map [string ]any {
135- {
136- "kind" : "text" ,
137- "text" : message ,
138- },
139- },
140- },
141- },
142- }
143-
144- requestBody , err := json .Marshal (jsonRPCRequest )
145- require .NoError (t , err )
103+ msgID := fmt .Sprintf ("msg-%d" , i )
146104
147- req , err := http .NewRequestWithContext (t .Context (), http .MethodPost , agentCard .URL , bytes .NewReader (requestBody ))
148- require .NoError (t , err )
149- req .Header .Set ("Content-Type" , "application/json" )
105+ resp := sendA2AMessage (t , agentCard .URL , requestID , msgID , message )
150106
151- resp , err := http .DefaultClient .Do (req )
152- require .NoError (t , err )
153- defer resp .Body .Close ()
154-
155- assert .Equal (t , http .StatusOK , resp .StatusCode )
156-
157- responseBody , err := io .ReadAll (resp .Body )
158- require .NoError (t , err )
159-
160- var jsonRPCResponse Response
161- err = json .Unmarshal (responseBody , & jsonRPCResponse )
162- require .NoError (t , err )
163-
164- assert .Equal (t , requestID , jsonRPCResponse .ID )
165- assert .Nil (t , jsonRPCResponse .Error )
166- assert .NotNil (t , jsonRPCResponse .Result )
107+ assert .Equal (t , requestID , resp .ID )
108+ assert .Nil (t , resp .Error )
109+ assert .NotNil (t , resp .Result )
167110 })
168111 }
169112}
@@ -174,63 +117,59 @@ func TestA2AServer_MultiAgent(t *testing.T) {
174117 _ , runConfig := startRecordingAIProxy (t )
175118 agentCard := startA2AServer (t , "testdata/multi.yaml" , runConfig )
176119
177- requestID := "test-multi-1"
178- jsonRPCRequest := map [string ]any {
120+ resp := sendA2AMessage (t , agentCard .URL , "test-multi-1" , "msg-multi-1" , "Say hello." )
121+
122+ assert .Equal (t , "test-multi-1" , resp .ID )
123+ assert .Nil (t , resp .Error )
124+ require .NotNil (t , resp .Result )
125+
126+ texts := resp .textParts ()
127+ require .NotEmpty (t , texts )
128+ assert .Contains (t , texts [len (texts )- 1 ], "Hello" )
129+ }
130+
131+ // sendA2AMessage sends a message/send JSON-RPC request and returns the parsed response.
132+ func sendA2AMessage (t * testing.T , url , requestID , messageID , text string ) a2aResponse {
133+ t .Helper ()
134+
135+ body , err := json .Marshal (map [string ]any {
179136 "jsonrpc" : "2.0" ,
180137 "id" : requestID ,
181138 "method" : "message/send" ,
182139 "params" : map [string ]any {
183140 "message" : map [string ]any {
184- "messageId" : "msg-multi-1" ,
141+ "messageId" : messageID ,
185142 "role" : "user" ,
186- "parts" : []map [string ]any {
187- {
188- "kind" : "text" ,
189- "text" : "Say hello." ,
190- },
191- },
143+ "parts" : []map [string ]any {{"kind" : "text" , "text" : text }},
192144 },
193145 },
194- }
195-
196- requestBody , err := json .Marshal (jsonRPCRequest )
146+ })
197147 require .NoError (t , err )
198148
199- req , err := http .NewRequestWithContext (t .Context (), http .MethodPost , agentCard . URL , bytes .NewReader (requestBody ))
149+ req , err := http .NewRequestWithContext (t .Context (), http .MethodPost , url , bytes .NewReader (body ))
200150 require .NoError (t , err )
201151 req .Header .Set ("Content-Type" , "application/json" )
202152
203153 resp , err := http .DefaultClient .Do (req )
204154 require .NoError (t , err )
205155 defer resp .Body .Close ()
206156
207- assert .Equal (t , http .StatusOK , resp .StatusCode )
208-
209- responseBody , err := io .ReadAll (resp .Body )
210- require .NoError (t , err )
157+ require .Equal (t , http .StatusOK , resp .StatusCode )
211158
212- var jsonRPCResponse Response
213- err = json .Unmarshal (responseBody , & jsonRPCResponse )
159+ data , err := io .ReadAll (resp .Body )
214160 require .NoError (t , err )
215161
216- assert .Equal (t , requestID , jsonRPCResponse .ID )
217- assert .Nil (t , jsonRPCResponse .Error )
218- require .NotNil (t , jsonRPCResponse .Result )
219- require .Len (t , jsonRPCResponse .Result .Artifacts , 1 )
220- require .NotEmpty (t , jsonRPCResponse .Result .Artifacts [0 ].Parts )
162+ var parsed a2aResponse
163+ require .NoError (t , json .Unmarshal (data , & parsed ))
221164
222- // The last part contains the complete response text
223- lastPart := jsonRPCResponse .Result .Artifacts [0 ].Parts [len (jsonRPCResponse .Result .Artifacts [0 ].Parts )- 1 ]
224- assert .Equal (t , "text" , lastPart .Kind )
225- assert .Contains (t , lastPart .Text , "Hello" )
165+ return parsed
226166}
227167
228168func startA2AServer (t * testing.T , agentFile string , runConfig * config.RuntimeConfig ) a2a.AgentCard {
229169 t .Helper ()
230170
231171 var lc net.ListenConfig
232172 ln , err := lc .Listen (t .Context (), "tcp" , ":0" )
233-
234173 require .NoError (t , err )
235174
236175 go func () {
@@ -240,7 +179,10 @@ func startA2AServer(t *testing.T, agentFile string, runConfig *config.RuntimeCon
240179 port := ln .Addr ().(* net.TCPAddr ).Port
241180 serverURL := fmt .Sprintf ("http://localhost:%d" , port )
242181
243- resp , err := http .Get (serverURL + a2asrv .WellKnownAgentCardPath )
182+ cardReq , err := http .NewRequestWithContext (t .Context (), http .MethodGet , serverURL + a2asrv .WellKnownAgentCardPath , http .NoBody )
183+ require .NoError (t , err )
184+
185+ resp , err := http .DefaultClient .Do (cardReq )
244186 require .NoError (t , err )
245187 defer resp .Body .Close ()
246188
0 commit comments