@@ -2129,3 +2129,267 @@ func Test_ListSubIssues(t *testing.T) {
2129
2129
})
2130
2130
}
2131
2131
}
2132
+
2133
+ func Test_RemoveSubIssue (t * testing.T ) {
2134
+ // Verify tool definition once
2135
+ mockClient := github .NewClient (nil )
2136
+ tool , _ := RemoveSubIssue (stubGetClientFn (mockClient ), translations .NullTranslationHelper )
2137
+
2138
+ assert .Equal (t , "remove_sub_issue" , tool .Name )
2139
+ assert .NotEmpty (t , tool .Description )
2140
+ assert .Contains (t , tool .InputSchema .Properties , "owner" )
2141
+ assert .Contains (t , tool .InputSchema .Properties , "repo" )
2142
+ assert .Contains (t , tool .InputSchema .Properties , "issue_number" )
2143
+ assert .Contains (t , tool .InputSchema .Properties , "sub_issue_id" )
2144
+ assert .ElementsMatch (t , tool .InputSchema .Required , []string {"owner" , "repo" , "issue_number" , "sub_issue_id" })
2145
+
2146
+ // Setup mock issue for success case (matches GitHub API response format - the updated parent issue)
2147
+ mockIssue := & github.Issue {
2148
+ Number : github .Ptr (42 ),
2149
+ Title : github .Ptr ("Parent Issue" ),
2150
+ Body : github .Ptr ("This is the parent issue after sub-issue removal" ),
2151
+ State : github .Ptr ("open" ),
2152
+ HTMLURL : github .Ptr ("https://github.com/owner/repo/issues/42" ),
2153
+ User : & github.User {
2154
+ Login : github .Ptr ("testuser" ),
2155
+ },
2156
+ Labels : []* github.Label {
2157
+ {
2158
+ Name : github .Ptr ("enhancement" ),
2159
+ Color : github .Ptr ("84b6eb" ),
2160
+ Description : github .Ptr ("New feature or request" ),
2161
+ },
2162
+ },
2163
+ }
2164
+
2165
+ tests := []struct {
2166
+ name string
2167
+ mockedClient * http.Client
2168
+ requestArgs map [string ]interface {}
2169
+ expectError bool
2170
+ expectedIssue * github.Issue
2171
+ expectedErrMsg string
2172
+ }{
2173
+ {
2174
+ name : "successful sub-issue removal" ,
2175
+ mockedClient : mock .NewMockedHTTPClient (
2176
+ mock .WithRequestMatchHandler (
2177
+ mock.EndpointPattern {
2178
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2179
+ Method : "DELETE" ,
2180
+ },
2181
+ expectRequestBody (t , map [string ]interface {}{
2182
+ "sub_issue_id" : float64 (123 ),
2183
+ }).andThen (
2184
+ mockResponse (t , http .StatusOK , mockIssue ),
2185
+ ),
2186
+ ),
2187
+ ),
2188
+ requestArgs : map [string ]interface {}{
2189
+ "owner" : "owner" ,
2190
+ "repo" : "repo" ,
2191
+ "issue_number" : float64 (42 ),
2192
+ "sub_issue_id" : float64 (123 ),
2193
+ },
2194
+ expectError : false ,
2195
+ expectedIssue : mockIssue ,
2196
+ },
2197
+ {
2198
+ name : "parent issue not found" ,
2199
+ mockedClient : mock .NewMockedHTTPClient (
2200
+ mock .WithRequestMatchHandler (
2201
+ mock.EndpointPattern {
2202
+ Pattern : "/repos/owner/repo/issues/999/sub_issue" ,
2203
+ Method : "DELETE" ,
2204
+ },
2205
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
2206
+ w .WriteHeader (http .StatusNotFound )
2207
+ _ , _ = w .Write ([]byte (`{"message": "Not Found"}` ))
2208
+ }),
2209
+ ),
2210
+ ),
2211
+ requestArgs : map [string ]interface {}{
2212
+ "owner" : "owner" ,
2213
+ "repo" : "repo" ,
2214
+ "issue_number" : float64 (999 ),
2215
+ "sub_issue_id" : float64 (123 ),
2216
+ },
2217
+ expectError : false ,
2218
+ expectedErrMsg : "failed to remove sub-issue" ,
2219
+ },
2220
+ {
2221
+ name : "sub-issue not found" ,
2222
+ mockedClient : mock .NewMockedHTTPClient (
2223
+ mock .WithRequestMatchHandler (
2224
+ mock.EndpointPattern {
2225
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2226
+ Method : "DELETE" ,
2227
+ },
2228
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
2229
+ w .WriteHeader (http .StatusNotFound )
2230
+ _ , _ = w .Write ([]byte (`{"message": "Sub-issue not found"}` ))
2231
+ }),
2232
+ ),
2233
+ ),
2234
+ requestArgs : map [string ]interface {}{
2235
+ "owner" : "owner" ,
2236
+ "repo" : "repo" ,
2237
+ "issue_number" : float64 (42 ),
2238
+ "sub_issue_id" : float64 (999 ),
2239
+ },
2240
+ expectError : false ,
2241
+ expectedErrMsg : "failed to remove sub-issue" ,
2242
+ },
2243
+ {
2244
+ name : "bad request - invalid sub_issue_id" ,
2245
+ mockedClient : mock .NewMockedHTTPClient (
2246
+ mock .WithRequestMatchHandler (
2247
+ mock.EndpointPattern {
2248
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2249
+ Method : "DELETE" ,
2250
+ },
2251
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
2252
+ w .WriteHeader (http .StatusBadRequest )
2253
+ _ , _ = w .Write ([]byte (`{"message": "Invalid sub_issue_id"}` ))
2254
+ }),
2255
+ ),
2256
+ ),
2257
+ requestArgs : map [string ]interface {}{
2258
+ "owner" : "owner" ,
2259
+ "repo" : "repo" ,
2260
+ "issue_number" : float64 (42 ),
2261
+ "sub_issue_id" : float64 (- 1 ),
2262
+ },
2263
+ expectError : false ,
2264
+ expectedErrMsg : "failed to remove sub-issue" ,
2265
+ },
2266
+ {
2267
+ name : "repository not found" ,
2268
+ mockedClient : mock .NewMockedHTTPClient (
2269
+ mock .WithRequestMatchHandler (
2270
+ mock.EndpointPattern {
2271
+ Pattern : "/repos/nonexistent/repo/issues/42/sub_issue" ,
2272
+ Method : "DELETE" ,
2273
+ },
2274
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
2275
+ w .WriteHeader (http .StatusNotFound )
2276
+ _ , _ = w .Write ([]byte (`{"message": "Not Found"}` ))
2277
+ }),
2278
+ ),
2279
+ ),
2280
+ requestArgs : map [string ]interface {}{
2281
+ "owner" : "nonexistent" ,
2282
+ "repo" : "repo" ,
2283
+ "issue_number" : float64 (42 ),
2284
+ "sub_issue_id" : float64 (123 ),
2285
+ },
2286
+ expectError : false ,
2287
+ expectedErrMsg : "failed to remove sub-issue" ,
2288
+ },
2289
+ {
2290
+ name : "insufficient permissions" ,
2291
+ mockedClient : mock .NewMockedHTTPClient (
2292
+ mock .WithRequestMatchHandler (
2293
+ mock.EndpointPattern {
2294
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2295
+ Method : "DELETE" ,
2296
+ },
2297
+ http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
2298
+ w .WriteHeader (http .StatusForbidden )
2299
+ _ , _ = w .Write ([]byte (`{"message": "Must have write access to repository"}` ))
2300
+ }),
2301
+ ),
2302
+ ),
2303
+ requestArgs : map [string ]interface {}{
2304
+ "owner" : "owner" ,
2305
+ "repo" : "repo" ,
2306
+ "issue_number" : float64 (42 ),
2307
+ "sub_issue_id" : float64 (123 ),
2308
+ },
2309
+ expectError : false ,
2310
+ expectedErrMsg : "failed to remove sub-issue" ,
2311
+ },
2312
+ {
2313
+ name : "missing required parameter owner" ,
2314
+ mockedClient : mock .NewMockedHTTPClient (
2315
+ mock .WithRequestMatchHandler (
2316
+ mock.EndpointPattern {
2317
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2318
+ Method : "DELETE" ,
2319
+ },
2320
+ mockResponse (t , http .StatusOK , mockIssue ),
2321
+ ),
2322
+ ),
2323
+ requestArgs : map [string ]interface {}{
2324
+ "repo" : "repo" ,
2325
+ "issue_number" : float64 (42 ),
2326
+ "sub_issue_id" : float64 (123 ),
2327
+ },
2328
+ expectError : false ,
2329
+ expectedErrMsg : "missing required parameter: owner" ,
2330
+ },
2331
+ {
2332
+ name : "missing required parameter sub_issue_id" ,
2333
+ mockedClient : mock .NewMockedHTTPClient (
2334
+ mock .WithRequestMatchHandler (
2335
+ mock.EndpointPattern {
2336
+ Pattern : "/repos/owner/repo/issues/42/sub_issue" ,
2337
+ Method : "DELETE" ,
2338
+ },
2339
+ mockResponse (t , http .StatusOK , mockIssue ),
2340
+ ),
2341
+ ),
2342
+ requestArgs : map [string ]interface {}{
2343
+ "owner" : "owner" ,
2344
+ "repo" : "repo" ,
2345
+ "issue_number" : float64 (42 ),
2346
+ },
2347
+ expectError : false ,
2348
+ expectedErrMsg : "missing required parameter: sub_issue_id" ,
2349
+ },
2350
+ }
2351
+
2352
+ for _ , tc := range tests {
2353
+ t .Run (tc .name , func (t * testing.T ) {
2354
+ // Setup client with mock
2355
+ client := github .NewClient (tc .mockedClient )
2356
+ _ , handler := RemoveSubIssue (stubGetClientFn (client ), translations .NullTranslationHelper )
2357
+
2358
+ // Create call request
2359
+ request := createMCPRequest (tc .requestArgs )
2360
+
2361
+ // Call handler
2362
+ result , err := handler (context .Background (), request )
2363
+
2364
+ // Verify results
2365
+ if tc .expectError {
2366
+ require .Error (t , err )
2367
+ assert .Contains (t , err .Error (), tc .expectedErrMsg )
2368
+ return
2369
+ }
2370
+
2371
+ if tc .expectedErrMsg != "" {
2372
+ require .NotNil (t , result )
2373
+ textContent := getTextResult (t , result )
2374
+ assert .Contains (t , textContent .Text , tc .expectedErrMsg )
2375
+ return
2376
+ }
2377
+
2378
+ require .NoError (t , err )
2379
+
2380
+ // Parse the result and get the text content if no error
2381
+ textContent := getTextResult (t , result )
2382
+
2383
+ // Unmarshal and verify the result
2384
+ var returnedIssue github.Issue
2385
+ err = json .Unmarshal ([]byte (textContent .Text ), & returnedIssue )
2386
+ require .NoError (t , err )
2387
+ assert .Equal (t , * tc .expectedIssue .Number , * returnedIssue .Number )
2388
+ assert .Equal (t , * tc .expectedIssue .Title , * returnedIssue .Title )
2389
+ assert .Equal (t , * tc .expectedIssue .Body , * returnedIssue .Body )
2390
+ assert .Equal (t , * tc .expectedIssue .State , * returnedIssue .State )
2391
+ assert .Equal (t , * tc .expectedIssue .HTMLURL , * returnedIssue .HTMLURL )
2392
+ assert .Equal (t , * tc .expectedIssue .User .Login , * returnedIssue .User .Login )
2393
+ })
2394
+ }
2395
+ }
0 commit comments