@@ -463,6 +463,28 @@ func TestMiddlewareScenario(t *testing.T) {
463463 })
464464}
465465
466+ // requireErrorText asserts that result is a non-nil MCP tool error and returns its text content.
467+ func requireErrorText (t * testing.T , result * mcp.CallToolResult ) string {
468+ t .Helper ()
469+ require .NotNil (t , result )
470+ require .True (t , result .IsError )
471+ require .NotEmpty (t , result .Content )
472+ text , ok := result .Content [0 ].(* mcp.TextContent )
473+ require .True (t , ok , "expected *mcp.TextContent, got %T" , result .Content [0 ])
474+ return text .Text
475+ }
476+
477+ // assertContextHasError asserts that exactly one error is stored in ctx and it matches expectedErr.
478+ //
479+ //nolint:revive // t must be first for test helpers; context-as-argument doesn't apply here
480+ func assertContextHasError (t * testing.T , ctx context.Context , expectedErr error ) {
481+ t .Helper ()
482+ apiErrors , err := GetGitHubAPIErrors (ctx )
483+ require .NoError (t , err )
484+ require .Len (t , apiErrors , 1 )
485+ assert .Equal (t , expectedErr , apiErrors [0 ].Err )
486+ }
487+
466488func TestNewGitHubAPIErrorResponse_RateLimits (t * testing.T ) {
467489 t .Run ("RateLimitError produces clean message with retry time" , func (t * testing.T ) {
468490 // Given a context with GitHub error tracking enabled
@@ -482,21 +504,14 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
482504 // When we create an API error response for a rate limit error
483505 result := NewGitHubAPIErrorResponse (ctx , "search code" , resp , rateLimitErr )
484506
485- // Then it should return an MCP error result
486- require .NotNil (t , result )
487- assert .True (t , result .IsError )
488-
489- // And the message should be clean and actionable (no raw URLs or status codes)
490- textContent := result .Content [0 ].(* mcp.TextContent )
491- assert .Contains (t , textContent .Text , fmt .Sprintf ("GitHub API rate limit exceeded. Retry after %v." , expectedRetryIn ))
492- assert .NotContains (t , textContent .Text , "https://" )
493- assert .NotContains (t , textContent .Text , "403" )
507+ // Then the message should be clean and actionable (no raw URLs or status codes)
508+ text := requireErrorText (t , result )
509+ assert .Contains (t , text , fmt .Sprintf ("GitHub API rate limit exceeded. Retry after %v." , expectedRetryIn ))
510+ assert .NotContains (t , text , "https://" )
511+ assert .NotContains (t , text , "403" )
494512
495513 // And the original error should still be stored in context for middleware
496- apiErrors , err := GetGitHubAPIErrors (ctx )
497- require .NoError (t , err )
498- require .Len (t , apiErrors , 1 )
499- assert .Equal (t , rateLimitErr , apiErrors [0 ].Err )
514+ assertContextHasError (t , ctx , rateLimitErr )
500515 })
501516
502517 t .Run ("AbuseRateLimitError with RetryAfter produces clean message with wait time" , func (t * testing.T ) {
@@ -514,21 +529,14 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
514529 // When we create an API error response for a secondary rate limit error
515530 result := NewGitHubAPIErrorResponse (ctx , "create issue" , resp , abuseErr )
516531
517- // Then it should return an MCP error result
518- require .NotNil (t , result )
519- assert .True (t , result .IsError )
520-
521532 // And the message should include the specific retry duration
522- textContent := result . Content [ 0 ].( * mcp. TextContent )
523- assert .Contains (t , textContent . Text , "GitHub secondary rate limit exceeded. Retry after 47s." )
524- assert .NotContains (t , textContent . Text , "https://" )
525- assert .NotContains (t , textContent . Text , "403" )
533+ text := requireErrorText ( t , result )
534+ assert .Contains (t , text , "GitHub secondary rate limit exceeded. Retry after 47s." )
535+ assert .NotContains (t , text , "https://" )
536+ assert .NotContains (t , text , "403" )
526537
527538 // And the original error should still be stored in context for middleware
528- apiErrors , err := GetGitHubAPIErrors (ctx )
529- require .NoError (t , err )
530- require .Len (t , apiErrors , 1 )
531- assert .Equal (t , abuseErr , apiErrors [0 ].Err )
539+ assertContextHasError (t , ctx , abuseErr )
532540 })
533541
534542 t .Run ("AbuseRateLimitError without RetryAfter produces clean message without wait time" , func (t * testing.T ) {
@@ -545,21 +553,14 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
545553 // When we create an API error response for a secondary rate limit error without retry info
546554 result := NewGitHubAPIErrorResponse (ctx , "create issue" , resp , abuseErr )
547555
548- // Then it should return an MCP error result
549- require .NotNil (t , result )
550- assert .True (t , result .IsError )
551-
552556 // And the message should be clean and actionable
553- textContent := result . Content [ 0 ].( * mcp. TextContent )
554- assert .Contains (t , textContent . Text , "GitHub secondary rate limit exceeded. Wait before retrying." )
555- assert .NotContains (t , textContent . Text , "https://" )
556- assert .NotContains (t , textContent . Text , "403" )
557+ text := requireErrorText ( t , result )
558+ assert .Contains (t , text , "GitHub secondary rate limit exceeded. Wait before retrying." )
559+ assert .NotContains (t , text , "https://" )
560+ assert .NotContains (t , text , "403" )
557561
558562 // And the original error should still be stored in context for middleware
559- apiErrors , err := GetGitHubAPIErrors (ctx )
560- require .NoError (t , err )
561- require .Len (t , apiErrors , 1 )
562- assert .Equal (t , abuseErr , apiErrors [0 ].Err )
563+ assertContextHasError (t , ctx , abuseErr )
563564 })
564565
565566 t .Run ("RateLimitError with reset time in the past falls back to wait message" , func (t * testing.T ) {
@@ -575,10 +576,8 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
575576
576577 result := NewGitHubAPIErrorResponse (ctx , "search code" , resp , rateLimitErr )
577578
578- require .NotNil (t , result )
579- assert .True (t , result .IsError )
580- textContent := result .Content [0 ].(* mcp.TextContent )
581- assert .Contains (t , textContent .Text , "GitHub API rate limit exceeded. Wait before retrying." )
579+ text := requireErrorText (t , result )
580+ assert .Contains (t , text , "GitHub API rate limit exceeded. Wait before retrying." )
582581 })
583582
584583 t .Run ("RateLimitError with zero reset time falls back to wait message" , func (t * testing.T ) {
@@ -593,10 +592,8 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
593592
594593 result := NewGitHubAPIErrorResponse (ctx , "search code" , resp , rateLimitErr )
595594
596- require .NotNil (t , result )
597- assert .True (t , result .IsError )
598- textContent := result .Content [0 ].(* mcp.TextContent )
599- assert .Contains (t , textContent .Text , "GitHub API rate limit exceeded. Wait before retrying." )
595+ text := requireErrorText (t , result )
596+ assert .Contains (t , text , "GitHub API rate limit exceeded. Wait before retrying." )
600597 })
601598
602599 t .Run ("wrapped RateLimitError is handled via errors.As" , func (t * testing.T ) {
@@ -616,11 +613,9 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
616613
617614 result := NewGitHubAPIErrorResponse (ctx , "search code" , resp , wrappedErr )
618615
619- require .NotNil (t , result )
620- assert .True (t , result .IsError )
621- textContent := result .Content [0 ].(* mcp.TextContent )
622- assert .Contains (t , textContent .Text , fmt .Sprintf ("GitHub API rate limit exceeded. Retry after %v." , expectedRetryIn ))
623- assert .NotContains (t , textContent .Text , "https://" )
616+ text := requireErrorText (t , result )
617+ assert .Contains (t , text , fmt .Sprintf ("GitHub API rate limit exceeded. Retry after %v." , expectedRetryIn ))
618+ assert .NotContains (t , text , "https://" )
624619 })
625620
626621 t .Run ("wrapped AbuseRateLimitError is handled via errors.As" , func (t * testing.T ) {
@@ -637,11 +632,9 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
637632
638633 result := NewGitHubAPIErrorResponse (ctx , "create issue" , resp , wrappedErr )
639634
640- require .NotNil (t , result )
641- assert .True (t , result .IsError )
642- textContent := result .Content [0 ].(* mcp.TextContent )
643- assert .Contains (t , textContent .Text , "GitHub secondary rate limit exceeded. Retry after 30s." )
644- assert .NotContains (t , textContent .Text , "https://" )
635+ text := requireErrorText (t , result )
636+ assert .Contains (t , text , "GitHub secondary rate limit exceeded. Retry after 30s." )
637+ assert .NotContains (t , text , "https://" )
645638 })
646639
647640 t .Run ("non-rate-limit GitHub API error passes through the original error message" , func (t * testing.T ) {
@@ -655,10 +648,7 @@ func TestNewGitHubAPIErrorResponse_RateLimits(t *testing.T) {
655648 result := NewGitHubAPIErrorResponse (ctx , "API call failed" , resp , originalErr )
656649
657650 // Then the message should contain the original error text unchanged
658- require .NotNil (t , result )
659- assert .True (t , result .IsError )
660-
661- textContent := result .Content [0 ].(* mcp.TextContent )
662- assert .Contains (t , textContent .Text , "validation failed" )
651+ text := requireErrorText (t , result )
652+ assert .Contains (t , text , "validation failed" )
663653 })
664654}
0 commit comments