diff --git a/internal/mcp/http_transport_test.go b/internal/mcp/http_transport_test.go index eac6ffb87..9a1563dd0 100644 --- a/internal/mcp/http_transport_test.go +++ b/internal/mcp/http_transport_test.go @@ -1545,4 +1545,55 @@ func TestParseHTTPResult(t *testing.T) { assert.Equal(t, -32601, resp.Error.Code) assert.Equal(t, "Method not found", resp.Error.Message) }) + + t.Run("non-200 status with JSON-RPC body synthesises HTTP error", func(t *testing.T) { + // parseJSONRPCResponseWithSSE synthesises a synthetic HTTP error for non-200 responses. + // In the current implementation this means the JSON-RPC error already present in the body + // is overridden by the synthetic error, so the -32603 code is what callers observe. + // This test documents that current behaviour; if parseJSONRPCResponseWithSSE is changed + // to pass through the body-level error, the assertions below will need updating. + result := &httpRequestResult{ + StatusCode: http.StatusInternalServerError, + ResponseBody: []byte(`{"jsonrpc":"2.0","id":4,"error":{"code":-32000,"message":"Server overloaded"}}`), + } + resp, err := parseHTTPResult(result) + require.NoError(t, err) + require.NotNil(t, resp) + require.NotNil(t, resp.Error, "non-200 response should have an error set") + // Synthetic HTTP error is produced by parseJSONRPCResponseWithSSE for non-200 statuses. + assert.Equal(t, -32603, resp.Error.Code, "synthetic HTTP error code should be -32603") + assert.Contains(t, resp.Error.Message, "500", "synthetic error should include HTTP status") + }) +} + +// TestBuildHTTPClientWithHeaders_NilTransport verifies that when the base client has +// a nil Transport, buildHTTPClientWithHeaders falls back to http.DefaultTransport as +// the inner transport for the injecting round-tripper. +func TestBuildHTTPClientWithHeaders_NilTransport(t *testing.T) { + // Use a buffered channel to safely pass the observed header value from the + // handler goroutine to the test goroutine without a data race. + receivedHeader := make(chan string, 1) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedHeader <- r.Header.Get("X-Test-Header") + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + + // base client has no Transport set (nil) — the wrapper must fall back to + // http.DefaultTransport so real network requests still work. + base := &http.Client{} + injected := buildHTTPClientWithHeaders(base, map[string]string{ + "X-Test-Header": "nil-transport-value", + }) + assert.NotSame(t, base, injected, "non-empty headers should return a new client") + + req, err := http.NewRequestWithContext(context.Background(), "GET", srv.URL, nil) + require.NoError(t, err) + + resp, err := injected.Do(req) + require.NoError(t, err) + resp.Body.Close() + + assert.Equal(t, "nil-transport-value", <-receivedHeader, + "header should be injected even when base client Transport is nil") }