From 4da5cf41f56f5770bb287980b1867ddddfe508dc Mon Sep 17 00:00:00 2001 From: Tmwakalasya Date: Wed, 7 Oct 2026 15:58:06 -0400 Subject: [PATCH] fix(changeprovider): preserve GitHub HTTP status errors --- .../changeprovider/github/BUILD.bazel | 3 ++ .../changeprovider/github/graphql.go | 4 +- .../changeprovider/github/provider_test.go | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/submitqueue/extension/changeprovider/github/BUILD.bazel b/submitqueue/extension/changeprovider/github/BUILD.bazel index 2120b691b..fd60c787b 100644 --- a/submitqueue/extension/changeprovider/github/BUILD.bazel +++ b/submitqueue/extension/changeprovider/github/BUILD.bazel @@ -12,6 +12,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//platform/base/change/github:go_default_library", + "//platform/http:go_default_library", "//platform/metrics:go_default_library", "//submitqueue/entity:go_default_library", "//submitqueue/extension/changeprovider:go_default_library", @@ -31,6 +32,8 @@ go_test( deps = [ "//platform/base/change:go_default_library", "//platform/base/change/github:go_default_library", + "//platform/errs:go_default_library", + "//platform/errs/http:go_default_library", "//platform/http:go_default_library", "//submitqueue/entity:go_default_library", "//submitqueue/extension/changeprovider:go_default_library", diff --git a/submitqueue/extension/changeprovider/github/graphql.go b/submitqueue/extension/changeprovider/github/graphql.go index fc24ee5b4..fc1d26957 100644 --- a/submitqueue/extension/changeprovider/github/graphql.go +++ b/submitqueue/extension/changeprovider/github/graphql.go @@ -7,6 +7,8 @@ import ( "fmt" "io" "net/http" + + phttp "github.com/uber/submitqueue/platform/http" ) // pullRequestQuery is the GraphQL query to fetch pull request information including files, author, and head SHA. @@ -140,7 +142,7 @@ func parseGraphQLResponse( ) (*pullRequestData, error) { if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body)) + return nil, fmt.Errorf("GitHub API request failed: %w", phttp.NewStatusError(resp.StatusCode, body)) } var gqlResp graphqlResponse diff --git a/submitqueue/extension/changeprovider/github/provider_test.go b/submitqueue/extension/changeprovider/github/provider_test.go index f114f9ebd..e7e968e8b 100644 --- a/submitqueue/extension/changeprovider/github/provider_test.go +++ b/submitqueue/extension/changeprovider/github/provider_test.go @@ -13,6 +13,8 @@ import ( "go.uber.org/zap/zaptest" "github.com/uber/submitqueue/platform/base/change" + "github.com/uber/submitqueue/platform/errs" + httperrs "github.com/uber/submitqueue/platform/errs/http" phttp "github.com/uber/submitqueue/platform/http" "github.com/uber/submitqueue/submitqueue/entity" "github.com/uber/submitqueue/submitqueue/extension/changeprovider" @@ -213,3 +215,39 @@ func TestProvider_Get_FetchError_StopsOnFirstFailure(t *testing.T) { require.Error(t, err) assert.Equal(t, 2, callCount) } + +func TestProvider_Get_HTTPStatusError(t *testing.T) { + processor := errs.NewClassifierProcessor(httperrs.Classifier) + for _, tt := range []struct { + name string + status int + retryable bool + }{ + {name: "service unavailable", status: http.StatusServiceUnavailable, retryable: true}, + {name: "rate limited", status: http.StatusTooManyRequests, retryable: true}, + {name: "bad request", status: http.StatusBadRequest}, + {name: "unauthorized", status: http.StatusUnauthorized}, + } { + t.Run(tt.name, func(t *testing.T) { + const body = `{"message":"upstream failure"}` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(tt.status) + _, _ = w.Write([]byte(body)) + })) + t.Cleanup(server.Close) + + p := newTestProvider(t, server.URL) + _, err := p.Get(context.Background(), entity.Request{Change: change.Change{ + URIs: []string{"github://github.example.com/uber/submitqueue/pull/123/" + shaA}, + }}) + var statusErr *phttp.StatusError + require.ErrorAs(t, err, &statusErr) + assert.Equal(t, tt.status, statusErr.StatusCode) + assert.Equal(t, body, statusErr.Body) + + classified := processor.Process(err) + assert.Equal(t, tt.retryable, errs.IsRetryable(classified)) + assert.True(t, errs.IsDependencyError(classified)) + }) + } +}