@@ -36,6 +36,8 @@ func TestGitHubErrorContext(t *testing.T) {
3636
3737 apiError := apiErrors [0 ]
3838 assert .Equal (t , "failed to fetch resource" , apiError .Message )
39+ assert .Empty (t , apiError .Code )
40+ assert .Nil (t , apiError .RetryAfterSeconds )
3941 assert .Equal (t , resp , apiError .Response )
4042 assert .Equal (t , originalErr , apiError .Err )
4143 assert .Equal (t , "failed to fetch resource: resource not found" , apiError .Error ())
@@ -86,6 +88,8 @@ func TestGitHubErrorContext(t *testing.T) {
8688
8789 rawError := rawErrors [0 ]
8890 assert .Equal (t , "failed to fetch raw content" , rawError .Message )
91+ assert .Empty (t , rawError .Code )
92+ assert .Nil (t , rawError .RetryAfterSeconds )
8993 assert .Equal (t , resp , rawError .Response )
9094 assert .Equal (t , originalErr , rawError .Err )
9195 })
@@ -260,6 +264,7 @@ func TestGitHubErrorContext(t *testing.T) {
260264
261265 apiError := apiErrors [0 ]
262266 assert .Equal (t , "API call failed" , apiError .Message )
267+ assert .Empty (t , apiError .Code )
263268 assert .Equal (t , resp , apiError .Response )
264269 assert .Equal (t , originalErr , apiError .Err )
265270 })
@@ -308,6 +313,7 @@ func TestGitHubErrorContext(t *testing.T) {
308313
309314 apiError := apiErrors [0 ]
310315 assert .Equal (t , "failed to create issue" , apiError .Message )
316+ assert .Empty (t , apiError .Code )
311317 assert .Equal (t , resp , apiError .Response )
312318 // The synthetic error should contain the status code and body
313319 assert .Contains (t , apiError .Err .Error (), "unexpected status 422" )
@@ -384,6 +390,71 @@ func TestGitHubErrorTypes(t *testing.T) {
384390 var err error = gqlErr
385391 assert .Equal (t , "test message: query failed" , err .Error ())
386392 })
393+
394+ t .Run ("GitHubAPIError classifies rate limit variants and preserves retry metadata" , func (t * testing.T ) {
395+ t .Run ("secondary rate limit" , func (t * testing.T ) {
396+ resp := & github.Response {
397+ Response : & http.Response {
398+ StatusCode : http .StatusForbidden ,
399+ Status : "403 Forbidden" ,
400+ Header : http.Header {
401+ "Retry-After" : []string {"60" },
402+ },
403+ },
404+ }
405+
406+ apiErr := newGitHubAPIError ("failed to search" , resp , fmt .Errorf ("secondary rate limit exceeded" ))
407+
408+ require .NotNil (t , apiErr .RetryAfterSeconds )
409+ assert .Equal (t , "secondary_rate_limited" , apiErr .Code )
410+ assert .Equal (t , 60 , * apiErr .RetryAfterSeconds )
411+ })
412+
413+ t .Run ("abuse rate limit" , func (t * testing.T ) {
414+ resp := & github.Response {
415+ Response : & http.Response {
416+ StatusCode : http .StatusForbidden ,
417+ Status : "403 Forbidden" ,
418+ Header : http.Header {},
419+ },
420+ }
421+
422+ apiErr := newGitHubAPIError ("failed to create issue" , resp , fmt .Errorf ("You have triggered an abuse detection mechanism" ))
423+
424+ assert .Equal (t , "abuse_rate_limited" , apiErr .Code )
425+ assert .Nil (t , apiErr .RetryAfterSeconds )
426+ })
427+
428+ t .Run ("primary rate limit" , func (t * testing.T ) {
429+ resp := & github.Response {
430+ Response : & http.Response {
431+ StatusCode : http .StatusForbidden ,
432+ Status : "403 Forbidden" ,
433+ Header : http.Header {
434+ "X-RateLimit-Remaining" : []string {"0" },
435+ },
436+ },
437+ }
438+
439+ apiErr := newGitHubAPIError ("failed to list issues" , resp , fmt .Errorf ("API rate limit exceeded" ))
440+
441+ assert .Equal (t , "rate_limited" , apiErr .Code )
442+ assert .Nil (t , apiErr .RetryAfterSeconds )
443+ })
444+
445+ t .Run ("invalid token still classified" , func (t * testing.T ) {
446+ resp := & github.Response {
447+ Response : & http.Response {
448+ StatusCode : http .StatusUnauthorized ,
449+ Status : "401 Unauthorized" ,
450+ },
451+ }
452+
453+ apiErr := newGitHubAPIError ("failed to authenticate" , resp , fmt .Errorf ("bad credentials" ))
454+
455+ assert .Equal (t , "invalid_token" , apiErr .Code )
456+ })
457+ })
387458}
388459
389460// TestMiddlewareScenario demonstrates a realistic middleware scenario
0 commit comments