@@ -2129,3 +2129,267 @@ func Test_ListSubIssues(t *testing.T) {
21292129 })
21302130 }
21312131}
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