-
Notifications
You must be signed in to change notification settings - Fork 0
feat(github): surface structured error identifiers on 4xx responses #512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package github | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestHTTPStatusErrorSurfacesSafeDetail(t *testing.T) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The four no-leak scenarios (structured triple, non-JSON, message-only, metadata-free JSON) are asserted sequentially with Reply inline to this comment. |
||
| body := []byte(`{"message":"Validation Failed","errors":[{"resource":"PullRequestReview","field":"path","code":"invalid"}],"documentation_url":"https://docs.github.com"}`) | ||
| err := httpStatusError(400, body) | ||
| want := "github: status 400 (PullRequestReview.path.invalid)" | ||
| if err == nil || err.Error() != want { | ||
| t.Fatalf("err = %v, want %q", err, want) | ||
| } | ||
|
|
||
| // Non-JSON bodies stay fully redacted — they can echo request content. | ||
| err = httpStatusError(400, []byte("<html>some raw error echoing content</html>")) | ||
| if err == nil || err.Error() != "github: status 400 (response body redacted)" { | ||
| t.Fatalf("non-JSON err = %v, want redacted", err) | ||
| } | ||
|
|
||
| // Free-text message alone is NOT surfaced — it can echo secrets/content. | ||
| err = httpStatusError(401, []byte(`{"message":"bad credentials ghp_canary"}`)) | ||
| if err == nil || err.Error() != "github: status 401 (response body redacted)" { | ||
| t.Fatalf("message-only err = %v, want redacted", err) | ||
| } | ||
|
|
||
| // JSON without errors metadata also stays redacted. | ||
| err = httpStatusError(422, []byte(`{"data":"user content"}`)) | ||
| if err == nil || err.Error() != "github: status 422 (response body redacted)" { | ||
| t.Fatalf("metadata-free err = %v, want redacted", err) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The multi-error join ("; ".join over resource.field.code triples) and the 200-char truncation are new, non-trivial logic paths but have no test coverage — the added test in http_test.go only exercises a single-error payload. Two risks go unverified: (1) the join behavior with >1 errors, and (2)
detail[:maxDetail]truncates by byte index, which can split a multi-byte rune mid-sequence if resource/field/code ever contain non-ASCII text, producing invalid UTF-8 in the surfaced error string. Add a test case with multipleerrorsentries to lock in the;-joined format, and either truncate on a rune boundary (e.g. via a small loop orutf8.RuneCountInString-aware trim) or document why byte truncation is acceptable given the enum-only inputs.Reply inline to this comment.