@@ -50,18 +50,20 @@ type fakeCDP struct {
5050 server * httptest.Server
5151 url string
5252
53- mu sync.Mutex
54- connections int
55- relatedAttaches int
56- enabledSessions map [string ]int
57- omitResponse bool
58- closeOnInvoke bool
59- detachAfterResponse bool
60- navigateBeforeResult bool
61- invokeResponseDelay time.Duration
62- toolCount int
63- popupOpen bool
64- write func (any )
53+ mu sync.Mutex
54+ connections int
55+ relatedAttaches int
56+ invocationCount int
57+ enabledSessions map [string ]int
58+ omitResponse bool
59+ closeOnInvoke bool
60+ detachAfterResponse bool
61+ navigateBeforeResult bool
62+ navigationDoesNotCommit bool
63+ invokeResponseDelay time.Duration
64+ toolCount int
65+ popupOpen bool
66+ write func (any )
6567}
6668
6769func newFakeCDP (t * testing.T , omitResponse bool ) * fakeCDP {
@@ -210,44 +212,56 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) {
210212 "params" : map [string ]any {"tools" : tools },
211213 })
212214 case "WebMCP.invokeTool" :
213- if f .closeOnInvoke {
215+ f .mu .Lock ()
216+ f .invocationCount ++
217+ invocationID := fmt .Sprintf ("invocation-%d" , f .invocationCount )
218+ closeOnInvoke := f .closeOnInvoke
219+ responseDelay := f .invokeResponseDelay
220+ omitResponse := f .omitResponse
221+ navigateBeforeResult := f .navigateBeforeResult
222+ navigationDoesNotCommit := f .navigationDoesNotCommit
223+ detachAfterResponse := f .detachAfterResponse
224+ f .mu .Unlock ()
225+ if closeOnInvoke {
214226 conn .CloseNow ()
215227 return
216228 }
217- if f . invokeResponseDelay > 0 {
218- time .Sleep (f . invokeResponseDelay )
229+ if responseDelay > 0 {
230+ time .Sleep (responseDelay )
219231 }
220- respond (map [string ]any {"invocationId" : "invocation-1" })
221- if ! f . omitResponse {
222- if f . navigateBeforeResult {
232+ respond (map [string ]any {"invocationId" : invocationID })
233+ if ! omitResponse {
234+ if navigateBeforeResult {
223235 write (map [string ]any {
224236 "method" : "Page.frameStartedLoading" , "sessionId" : request .SessionID ,
225237 "params" : map [string ]any {"frameId" : "iframe-frame" },
226238 })
227239 }
228240 output := any (map [string ]any {"content" : []map [string ]any {{"type" : "text" , "text" : request .SessionID }}})
229- if f . navigateBeforeResult {
241+ if navigateBeforeResult {
230242 output = []any {}
231243 }
232244 write (map [string ]any {
233245 "method" : "WebMCP.toolResponded" , "sessionId" : request .SessionID ,
234246 "params" : map [string ]any {
235- "invocationId" : "invocation-1" , "status" : "Completed" , "output" : output ,
247+ "invocationId" : invocationID , "status" : "Completed" , "output" : output ,
236248 },
237249 })
238- if ! f . navigateBeforeResult {
250+ if ! navigateBeforeResult {
239251 write (map [string ]any {
240252 "method" : "Page.frameStartedLoading" , "sessionId" : request .SessionID ,
241253 "params" : map [string ]any {"frameId" : "iframe-frame" },
242254 })
243255 }
244- write (map [string ]any {
245- "method" : "Page.frameNavigated" , "sessionId" : request .SessionID ,
246- "params" : map [string ]any {"frame" : map [string ]any {
247- "id" : "iframe-frame" , "loaderId" : "next-loader" , "url" : "https://payments.example/success" ,
248- }},
249- })
250- if f .detachAfterResponse {
256+ if ! navigationDoesNotCommit {
257+ write (map [string ]any {
258+ "method" : "Page.frameNavigated" , "sessionId" : request .SessionID ,
259+ "params" : map [string ]any {"frame" : map [string ]any {
260+ "id" : "iframe-frame" , "loaderId" : "next-loader" , "url" : "https://payments.example/success" ,
261+ }},
262+ })
263+ }
264+ if detachAfterResponse {
251265 write (map [string ]any {
252266 "method" : "Target.detachedFromTarget" ,
253267 "params" : map [string ]any {"sessionId" : request .SessionID },
@@ -420,12 +434,33 @@ func TestInvocationPreservesResponseObservedBeforeFrameNavigation(t *testing.T)
420434func TestInvocationNavigationBeforeResponseHasUnknownOutcome (t * testing.T ) {
421435 fake := newFakeCDP (t , false )
422436 fake .navigateBeforeResult = true
437+ fake .navigationDoesNotCommit = true
423438 manager := NewManager (staticUpstream {url : fake .url })
424439 t .Cleanup (func () { _ = manager .Close () })
440+ toolRef := paymentToolRef (t , manager )
425441
426- result , err := manager .Invoke (context .Background (), paymentToolRef ( t , manager ) , map [string ]any {})
442+ result , err := manager .Invoke (context .Background (), toolRef , map [string ]any {})
427443 require .ErrorIs (t , err , ErrOutcomeUnknown )
428444 require .Equal (t , "invocation-1" , result .InvocationID )
445+
446+ tools , err := manager .Tools (context .Background ())
447+ require .NoError (t , err )
448+ toolStillRegistered := false
449+ for _ , tool := range tools {
450+ if tool .Ref == toolRef {
451+ toolStillRegistered = true
452+ break
453+ }
454+ }
455+ require .True (t , toolStillRegistered )
456+
457+ fake .mu .Lock ()
458+ fake .navigateBeforeResult = false
459+ fake .navigationDoesNotCommit = false
460+ fake .mu .Unlock ()
461+ result , err = manager .Invoke (context .Background (), toolRef , map [string ]any {})
462+ require .NoError (t , err )
463+ require .Equal (t , "Completed" , result .Status )
429464}
430465
431466func TestInvocationReturnsCompletedResponseBeforeTargetDetach (t * testing.T ) {
0 commit comments