From 1e799c9e4ce1d2ab3ce285d49859c987f13e3a11 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 18 May 2026 17:42:55 +0200 Subject: [PATCH] testproxy: forward raw error body and headers from upstream The reverse proxy in libs/testproxy re-marshalled apierr.APIError into a {error_code, message} envelope, dropping details[] and any other fields the workspace returned. As a result, acceptance tests run against the cloud could not observe error metadata that real CLI/TF invocations rely on. Forward apiErr.ResponseWrapper.DebugBytes verbatim with the original status code so callers see exactly what the workspace sent. Also pass through response headers in includeResponseHeaders on the error path; WithResponseHeader visitors are not invoked when apiClient.Do returns an error. ResponseWrapper has been populated on every APIError since databricks/databricks-sdk-go#1261 (v0.100.0); the CLI is on v0.132.0. A panic guards the invariant in case the SDK ever changes shape. Co-authored-by: Isaac --- libs/testproxy/server.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/libs/testproxy/server.go b/libs/testproxy/server.go index f3510d7adb0..fd6038c8ed5 100644 --- a/libs/testproxy/server.go +++ b/libs/testproxy/server.go @@ -2,7 +2,6 @@ package testproxy import ( "bytes" - "encoding/json" "errors" "net/http" "net/http/httptest" @@ -129,22 +128,31 @@ func (s *ProxyServer) proxyToCloud(w http.ResponseWriter, r *http.Request) { var encodedResponse *testserver.EncodedResponse - // API errors from the SDK are expected to be of the type [apierr.APIError]. If we - // get an API error then parse the error and forward it back to the client - // in an appropriate format. + // API errors from the SDK are of type [apierr.APIError]. Forward the raw + // response bytes verbatim — including any error details — so callers see + // exactly what the workspace returned. Re-marshalling from the parsed + // APIError would drop fields the SDK doesn't surface (e.g. metadata in + // details[]) and silently break callers that inspect them. apiErr := &apierr.APIError{} if errors.As(err, &apiErr) { - body := map[string]string{ - "error_code": apiErr.ErrorCode, - "message": apiErr.Message, + rw := apiErr.ResponseWrapper + if rw == nil { + // The SDK populates ResponseWrapper for every APIError produced + // from a real HTTP response. If this ever fires the SDK changed + // shape and we need to revisit how we forward error bodies. + panic("apierr.APIError has no ResponseWrapper") } - - b, err := json.Marshal(body) - assert.NoError(s.t, err) - encodedResponse = &testserver.EncodedResponse{ - StatusCode: apiErr.StatusCode, - Body: b, + StatusCode: rw.Response.StatusCode, + Body: rw.DebugBytes, + } + // Visitors registered via WithResponseHeader are not invoked when + // the SDK returns an error, so populate the include list directly + // from the original response headers. + for _, header := range includeResponseHeaders { + if v := rw.Response.Header.Get(header); v != "" { + *responseHeaders[header] = v + } } }