@@ -693,6 +693,204 @@ func Test_ListIssues(t *testing.T) {
693
693
}
694
694
}
695
695
696
+ func Test_UpdateIssue (t * testing.T ) {
697
+ // Verify tool definition
698
+ mockClient := github .NewClient (nil )
699
+ tool , _ := updateIssue (mockClient , translations .NullTranslationHelper )
700
+
701
+ assert .Equal (t , "update_issue" , tool .Name )
702
+ assert .NotEmpty (t , tool .Description )
703
+ assert .Contains (t , tool .InputSchema .Properties , "owner" )
704
+ assert .Contains (t , tool .InputSchema .Properties , "repo" )
705
+ assert .Contains (t , tool .InputSchema .Properties , "issue_number" )
706
+ assert .Contains (t , tool .InputSchema .Properties , "title" )
707
+ assert .Contains (t , tool .InputSchema .Properties , "body" )
708
+ assert .Contains (t , tool .InputSchema .Properties , "state" )
709
+ assert .Contains (t , tool .InputSchema .Properties , "labels" )
710
+ assert .Contains (t , tool .InputSchema .Properties , "assignees" )
711
+ assert .Contains (t , tool .InputSchema .Properties , "milestone" )
712
+ assert .ElementsMatch (t , tool .InputSchema .Required , []string {"owner" , "repo" , "issue_number" })
713
+
714
+ // Setup mock issue for success case
715
+ mockIssue := & github.Issue {
716
+ Number : github .Ptr (123 ),
717
+ Title : github .Ptr ("Updated Issue Title" ),
718
+ Body : github .Ptr ("Updated issue description" ),
719
+ State : github .Ptr ("closed" ),
720
+ HTMLURL : github .Ptr ("https://github.com/owner/repo/issues/123" ),
721
+ Assignees : []* github.User {{Login : github .Ptr ("assignee1" )}, {Login : github .Ptr ("assignee2" )}},
722
+ Labels : []* github.Label {{Name : github .Ptr ("bug" )}, {Name : github .Ptr ("priority" )}},
723
+ Milestone : & github.Milestone {Number : github .Ptr (5 )},
724
+ }
725
+
726
+ tests := []struct {
727
+ name string
728
+ mockedClient * http.Client
729
+ requestArgs map [string ]interface {}
730
+ expectError bool
731
+ expectedIssue * github.Issue
732
+ expectedErrMsg string
733
+ }{
734
+ {
735
+ name : "update issue with all fields" ,
736
+ mockedClient : mock .NewMockedHTTPClient (
737
+ mock .WithRequestMatchHandler (
738
+ mock .PatchReposIssuesByOwnerByRepoByIssueNumber ,
739
+ mockResponse (t , http .StatusOK , mockIssue ),
740
+ ),
741
+ ),
742
+ requestArgs : map [string ]interface {}{
743
+ "owner" : "owner" ,
744
+ "repo" : "repo" ,
745
+ "issue_number" : float64 (123 ),
746
+ "title" : "Updated Issue Title" ,
747
+ "body" : "Updated issue description" ,
748
+ "state" : "closed" ,
749
+ "labels" : "bug,priority" ,
750
+ "assignees" : "assignee1,assignee2" ,
751
+ "milestone" : float64 (5 ),
752
+ },
753
+ expectError : false ,
754
+ expectedIssue : mockIssue ,
755
+ },
756
+ {
757
+ name : "update issue with minimal fields" ,
758
+ mockedClient : mock .NewMockedHTTPClient (
759
+ mock .WithRequestMatchHandler (
760
+ mock .PatchReposIssuesByOwnerByRepoByIssueNumber ,
761
+ mockResponse (t , http .StatusOK , & github.Issue {
762
+ Number : github .Ptr (123 ),
763
+ Title : github .Ptr ("Only Title Updated" ),
764
+ HTMLURL : github .Ptr ("https://github.com/owner/repo/issues/123" ),
765
+ State : github .Ptr ("open" ),
766
+ }),
767
+ ),
768
+ ),
769
+ requestArgs : map [string ]interface {}{
770
+ "owner" : "owner" ,
771
+ "repo" : "repo" ,
772
+ "issue_number" : float64 (123 ),
773
+ "title" : "Only Title Updated" ,
774
+ },
775
+ expectError : false ,
776
+ expectedIssue : & github.Issue {
777
+ Number : github .Ptr (123 ),
778
+ Title : github .Ptr ("Only Title Updated" ),
779
+ HTMLURL : github .Ptr ("https://github.com/owner/repo/issues/123" ),
780
+ State : github .Ptr ("open" ),
781
+ },
782
+ },
783
+ {
784
+ name : "update issue fails with not found" ,
785
+ mockedClient : mock .NewMockedHTTPClient (
786
+ mock .WithRequestMatchHandler (
787
+ mock .PatchReposIssuesByOwnerByRepoByIssueNumber ,
788
+ http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
789
+ w .WriteHeader (http .StatusNotFound )
790
+ _ , _ = w .Write ([]byte (`{"message": "Issue not found"}` ))
791
+ }),
792
+ ),
793
+ ),
794
+ requestArgs : map [string ]interface {}{
795
+ "owner" : "owner" ,
796
+ "repo" : "repo" ,
797
+ "issue_number" : float64 (999 ),
798
+ "title" : "This issue doesn't exist" ,
799
+ },
800
+ expectError : true ,
801
+ expectedErrMsg : "failed to update issue" ,
802
+ },
803
+ {
804
+ name : "update issue fails with validation error" ,
805
+ mockedClient : mock .NewMockedHTTPClient (
806
+ mock .WithRequestMatchHandler (
807
+ mock .PatchReposIssuesByOwnerByRepoByIssueNumber ,
808
+ http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
809
+ w .WriteHeader (http .StatusUnprocessableEntity )
810
+ _ , _ = w .Write ([]byte (`{"message": "Invalid state value"}` ))
811
+ }),
812
+ ),
813
+ ),
814
+ requestArgs : map [string ]interface {}{
815
+ "owner" : "owner" ,
816
+ "repo" : "repo" ,
817
+ "issue_number" : float64 (123 ),
818
+ "state" : "invalid_state" ,
819
+ },
820
+ expectError : true ,
821
+ expectedErrMsg : "failed to update issue" ,
822
+ },
823
+ }
824
+
825
+ for _ , tc := range tests {
826
+ t .Run (tc .name , func (t * testing.T ) {
827
+ // Setup client with mock
828
+ client := github .NewClient (tc .mockedClient )
829
+ _ , handler := updateIssue (client , translations .NullTranslationHelper )
830
+
831
+ // Create call request
832
+ request := createMCPRequest (tc .requestArgs )
833
+
834
+ // Call handler
835
+ result , err := handler (context .Background (), request )
836
+
837
+ // Verify results
838
+ if tc .expectError {
839
+ if err != nil {
840
+ assert .Contains (t , err .Error (), tc .expectedErrMsg )
841
+ } else {
842
+ // For errors returned as part of the result, not as an error
843
+ require .NotNil (t , result )
844
+ textContent := getTextResult (t , result )
845
+ assert .Contains (t , textContent .Text , tc .expectedErrMsg )
846
+ }
847
+ return
848
+ }
849
+
850
+ require .NoError (t , err )
851
+
852
+ // Parse the result and get the text content if no error
853
+ textContent := getTextResult (t , result )
854
+
855
+ // Unmarshal and verify the result
856
+ var returnedIssue github.Issue
857
+ err = json .Unmarshal ([]byte (textContent .Text ), & returnedIssue )
858
+ require .NoError (t , err )
859
+
860
+ assert .Equal (t , * tc .expectedIssue .Number , * returnedIssue .Number )
861
+ assert .Equal (t , * tc .expectedIssue .Title , * returnedIssue .Title )
862
+ assert .Equal (t , * tc .expectedIssue .State , * returnedIssue .State )
863
+ assert .Equal (t , * tc .expectedIssue .HTMLURL , * returnedIssue .HTMLURL )
864
+
865
+ if tc .expectedIssue .Body != nil {
866
+ assert .Equal (t , * tc .expectedIssue .Body , * returnedIssue .Body )
867
+ }
868
+
869
+ // Check assignees if expected
870
+ if tc .expectedIssue .Assignees != nil && len (tc .expectedIssue .Assignees ) > 0 {
871
+ assert .Len (t , returnedIssue .Assignees , len (tc .expectedIssue .Assignees ))
872
+ for i , assignee := range returnedIssue .Assignees {
873
+ assert .Equal (t , * tc .expectedIssue .Assignees [i ].Login , * assignee .Login )
874
+ }
875
+ }
876
+
877
+ // Check labels if expected
878
+ if tc .expectedIssue .Labels != nil && len (tc .expectedIssue .Labels ) > 0 {
879
+ assert .Len (t , returnedIssue .Labels , len (tc .expectedIssue .Labels ))
880
+ for i , label := range returnedIssue .Labels {
881
+ assert .Equal (t , * tc .expectedIssue .Labels [i ].Name , * label .Name )
882
+ }
883
+ }
884
+
885
+ // Check milestone if expected
886
+ if tc .expectedIssue .Milestone != nil {
887
+ assert .NotNil (t , returnedIssue .Milestone )
888
+ assert .Equal (t , * tc .expectedIssue .Milestone .Number , * returnedIssue .Milestone .Number )
889
+ }
890
+ })
891
+ }
892
+ }
893
+
696
894
func Test_ParseISOTimestamp (t * testing.T ) {
697
895
tests := []struct {
698
896
name string
0 commit comments