@@ -3020,6 +3020,149 @@ func Test_AddSubIssue(t *testing.T) {
30203020 }
30213021}
30223022
3023+ func Test_AddSubIssue_RetryLogic (t * testing.T ) {
3024+ // Setup mock issue for success case
3025+ mockIssue := & github.Issue {
3026+ Number : github .Ptr (42 ),
3027+ Title : github .Ptr ("Parent Issue" ),
3028+ Body : github .Ptr ("This is the parent issue with a sub-issue" ),
3029+ State : github .Ptr ("open" ),
3030+ HTMLURL : github .Ptr ("https://github.com/owner/repo/issues/42" ),
3031+ User : & github.User {
3032+ Login : github .Ptr ("testuser" ),
3033+ },
3034+ }
3035+
3036+ tests := []struct {
3037+ name string
3038+ mockedClient * http.Client
3039+ requestArgs map [string ]interface {}
3040+ expectSuccess bool
3041+ expectedErrMsg string
3042+ }{
3043+ {
3044+ name : "successful retry after priority conflict" ,
3045+ mockedClient : func () * http.Client {
3046+ // Create a handler that fails twice with priority conflict, then succeeds
3047+ callCount := 0
3048+ handler := func (w http.ResponseWriter , _ * http.Request ) {
3049+ callCount ++
3050+ if callCount <= 2 {
3051+ // First two calls fail with priority conflict
3052+ w .WriteHeader (http .StatusUnprocessableEntity )
3053+ _ , _ = w .Write ([]byte (`{"message": "An error occurred while adding the sub-issue to the parent issue. Priority has already been taken"}` ))
3054+ } else {
3055+ // Third call succeeds
3056+ w .WriteHeader (http .StatusCreated )
3057+ responseJSON , _ := json .Marshal (mockIssue )
3058+ _ , _ = w .Write (responseJSON )
3059+ }
3060+ }
3061+ return MockHTTPClientWithHandlers (map [string ]http.HandlerFunc {
3062+ PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber : handler ,
3063+ })
3064+ }(),
3065+ requestArgs : map [string ]interface {}{
3066+ "method" : "add" ,
3067+ "owner" : "owner" ,
3068+ "repo" : "repo" ,
3069+ "issue_number" : float64 (42 ),
3070+ "sub_issue_id" : float64 (123 ),
3071+ },
3072+ expectSuccess : true ,
3073+ },
3074+ {
3075+ name : "exhausted retries with priority conflict" ,
3076+ mockedClient : MockHTTPClientWithHandlers (map [string ]http.HandlerFunc {
3077+ PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber : func (w http.ResponseWriter , _ * http.Request ) {
3078+ // Always fail with priority conflict
3079+ w .WriteHeader (http .StatusUnprocessableEntity )
3080+ _ , _ = w .Write ([]byte (`{"message": "An error occurred while adding the sub-issue to the parent issue. Priority has already been taken"}` ))
3081+ },
3082+ }),
3083+ requestArgs : map [string ]interface {}{
3084+ "method" : "add" ,
3085+ "owner" : "owner" ,
3086+ "repo" : "repo" ,
3087+ "issue_number" : float64 (42 ),
3088+ "sub_issue_id" : float64 (123 ),
3089+ },
3090+ expectSuccess : false ,
3091+ expectedErrMsg : "Priority has already been taken" ,
3092+ },
3093+ {
3094+ name : "non-retryable 422 error - sub-issue cannot be parent of itself" ,
3095+ mockedClient : MockHTTPClientWithHandlers (map [string ]http.HandlerFunc {
3096+ PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber : func (w http.ResponseWriter , _ * http.Request ) {
3097+ // Non-retryable 422 error
3098+ w .WriteHeader (http .StatusUnprocessableEntity )
3099+ _ , _ = w .Write ([]byte (`{"message": "Validation failed", "errors": [{"message": "Sub-issue cannot be a parent of itself"}]}` ))
3100+ },
3101+ }),
3102+ requestArgs : map [string ]interface {}{
3103+ "method" : "add" ,
3104+ "owner" : "owner" ,
3105+ "repo" : "repo" ,
3106+ "issue_number" : float64 (42 ),
3107+ "sub_issue_id" : float64 (42 ),
3108+ },
3109+ expectSuccess : false ,
3110+ expectedErrMsg : "failed to add sub-issue" ,
3111+ },
3112+ {
3113+ name : "first attempt succeeds - no retry needed" ,
3114+ mockedClient : MockHTTPClientWithHandlers (map [string ]http.HandlerFunc {
3115+ PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber : mockResponse (t , http .StatusCreated , mockIssue ),
3116+ }),
3117+ requestArgs : map [string ]interface {}{
3118+ "method" : "add" ,
3119+ "owner" : "owner" ,
3120+ "repo" : "repo" ,
3121+ "issue_number" : float64 (42 ),
3122+ "sub_issue_id" : float64 (123 ),
3123+ },
3124+ expectSuccess : true ,
3125+ },
3126+ }
3127+
3128+ serverTool := SubIssueWrite (translations .NullTranslationHelper )
3129+
3130+ for _ , tc := range tests {
3131+ t .Run (tc .name , func (t * testing.T ) {
3132+ // Setup client with mock
3133+ client := github .NewClient (tc .mockedClient )
3134+ deps := BaseDeps {
3135+ Client : client ,
3136+ }
3137+ handler := serverTool .Handler (deps )
3138+
3139+ // Create call request
3140+ request := createMCPRequest (tc .requestArgs )
3141+
3142+ // Call handler
3143+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
3144+
3145+ if tc .expectSuccess {
3146+ require .NoError (t , err )
3147+ require .NotNil (t , result )
3148+
3149+ // Parse and verify the result
3150+ textContent := getTextResult (t , result )
3151+ var returnedIssue github.Issue
3152+ err = json .Unmarshal ([]byte (textContent .Text ), & returnedIssue )
3153+ require .NoError (t , err )
3154+ assert .Equal (t , * mockIssue .Number , * returnedIssue .Number )
3155+ assert .Equal (t , * mockIssue .Title , * returnedIssue .Title )
3156+ } else {
3157+ // Expect error in result content
3158+ require .NotNil (t , result )
3159+ textContent := getTextResult (t , result )
3160+ assert .Contains (t , textContent .Text , tc .expectedErrMsg )
3161+ }
3162+ })
3163+ }
3164+ }
3165+
30233166func Test_GetSubIssues (t * testing.T ) {
30243167 // Verify tool definition once
30253168 serverTool := IssueRead (translations .NullTranslationHelper )
0 commit comments