@@ -3716,3 +3716,67 @@ func TestStreamableHTTP_E2E_DiscoverSuccess(t *testing.T) {
37163716 t .Errorf ("CallTool result[0] = %+v, want TextContent{Text:\" hello\" }" , res .Content [0 ])
37173717 }
37183718}
3719+
3720+ // A POST whose call ID is already in flight on the same session must be rejected
3721+ // with a JSON-RPC error, must not overwrite the existing request-to-stream
3722+ // mapping, and must not publish the duplicate message to the session's incoming
3723+ // channel.
3724+ func TestStreamableServerRejectsDuplicateInFlightRequestID (t * testing.T ) {
3725+ id := jsonrpc2 .Int64ID (1 )
3726+ conn := & streamableServerConn {
3727+ logger : ensureLogger (nil ),
3728+ incoming : make (chan jsonrpc.Message , 1 ),
3729+ done : make (chan struct {}),
3730+ streams : map [string ]* stream {
3731+ "existing" : {
3732+ id : "existing" ,
3733+ logger : ensureLogger (nil ),
3734+ requests : map [jsonrpc.ID ]struct {}{id : {}},
3735+ },
3736+ },
3737+ requestStreams : map [jsonrpc.ID ]string {id : "existing" },
3738+ }
3739+
3740+ data , err := jsonrpc2 .EncodeMessage (req (1 , methodPing , & PingParams {}))
3741+ if err != nil {
3742+ t .Fatalf ("EncodeMessage() error = %v" , err )
3743+ }
3744+ ctx , cancel := context .WithTimeout (t .Context (), 20 * time .Millisecond )
3745+ defer cancel ()
3746+ httpReq := httptest .NewRequestWithContext (ctx , http .MethodPost , "/" , bytes .NewReader (data ))
3747+ httpReq .Header .Set ("Content-Type" , "application/json" )
3748+ httpReq .Header .Set ("Accept" , "application/json, text/event-stream" )
3749+
3750+ rec := httptest .NewRecorder ()
3751+ conn .servePOST (rec , httpReq )
3752+
3753+ if rec .Code != http .StatusBadRequest {
3754+ t .Fatalf ("status = %d, want %d; body = %s" , rec .Code , http .StatusBadRequest , rec .Body .String ())
3755+ }
3756+ msg , err := jsonrpc2 .DecodeMessage (rec .Body .Bytes ())
3757+ if err != nil {
3758+ t .Fatalf ("DecodeMessage() error = %v; body = %s" , err , rec .Body .String ())
3759+ }
3760+ resp , ok := msg .(* jsonrpc.Response )
3761+ if ! ok {
3762+ t .Fatalf ("response type = %T, want *jsonrpc.Response" , msg )
3763+ }
3764+ if got := resp .ID .Raw (); got != int64 (1 ) {
3765+ t .Fatalf ("response ID = %v, want 1" , got )
3766+ }
3767+ var jerr * jsonrpc.Error
3768+ if ! errors .As (resp .Error , & jerr ) {
3769+ t .Fatalf ("response error = %v, want *jsonrpc.Error" , resp .Error )
3770+ }
3771+ if jerr .Code != jsonrpc .CodeInvalidRequest {
3772+ t .Fatalf ("error code = %d, want %d" , jerr .Code , jsonrpc .CodeInvalidRequest )
3773+ }
3774+ if got := conn .requestStreams [id ]; got != "existing" {
3775+ t .Fatalf ("requestStreams[%v] = %q, want %q" , id , got , "existing" )
3776+ }
3777+ select {
3778+ case msg := <- conn .incoming :
3779+ t .Fatalf ("duplicate request was published to incoming: %v" , msg )
3780+ default :
3781+ }
3782+ }
0 commit comments