@@ -381,7 +381,6 @@ func Test_AddIssueComment(t *testing.T) {
381381 require .NoError (t , err )
382382 assert .Equal (t , fmt .Sprintf ("%d" , tc .expectedComment .GetID ()), minimalResponse .ID )
383383 assert .Equal (t , tc .expectedComment .GetHTMLURL (), minimalResponse .URL )
384-
385384 })
386385 }
387386}
@@ -693,6 +692,217 @@ func Test_SearchIssues(t *testing.T) {
693692 }
694693}
695694
695+ func Test_SearchIssues_IFC_InsidersMode (t * testing.T ) {
696+ t .Parallel ()
697+
698+ serverTool := SearchIssues (translations .NullTranslationHelper )
699+
700+ makeIssue := func (owner , repo string , number int ) * github.Issue {
701+ return & github.Issue {
702+ Number : github .Ptr (number ),
703+ Title : github .Ptr ("issue" ),
704+ State : github .Ptr ("open" ),
705+ RepositoryURL : github .Ptr ("https://api.github.com/repos/" + owner + "/" + repo ),
706+ User : & github.User {Login : github .Ptr ("u" )},
707+ }
708+ }
709+
710+ type repoFixture struct {
711+ owner string
712+ repo string
713+ isPrivate bool
714+ collaborators []string
715+ repoStatus int
716+ }
717+
718+ repoHandlers := func (repos []repoFixture ) map [string ]http.HandlerFunc {
719+ repoByPath := map [string ]repoFixture {}
720+ for _ , r := range repos {
721+ repoByPath ["/repos/" + r .owner + "/" + r .repo ] = r
722+ }
723+ collaboratorsByPath := map [string ]repoFixture {}
724+ for _ , r := range repos {
725+ collaboratorsByPath ["/repos/" + r .owner + "/" + r .repo + "/collaborators" ] = r
726+ }
727+ return map [string ]http.HandlerFunc {
728+ GetReposByOwnerByRepo : func (w http.ResponseWriter , req * http.Request ) {
729+ r , ok := repoByPath [req .URL .Path ]
730+ if ! ok {
731+ w .WriteHeader (http .StatusNotFound )
732+ return
733+ }
734+ if r .repoStatus != 0 && r .repoStatus != http .StatusOK {
735+ w .WriteHeader (r .repoStatus )
736+ return
737+ }
738+ body , _ := json .Marshal (map [string ]any {
739+ "name" : r .repo ,
740+ "private" : r .isPrivate ,
741+ })
742+ w .WriteHeader (http .StatusOK )
743+ _ , _ = w .Write (body )
744+ },
745+ GetReposCollaboratorsByOwnerByRepo : func (w http.ResponseWriter , req * http.Request ) {
746+ r , ok := collaboratorsByPath [req .URL .Path ]
747+ if ! ok {
748+ w .WriteHeader (http .StatusOK )
749+ _ , _ = w .Write ([]byte ("[]" ))
750+ return
751+ }
752+ users := make ([]* github.User , len (r .collaborators ))
753+ for i , login := range r .collaborators {
754+ users [i ] = & github.User {Login : github .Ptr (login )}
755+ }
756+ body , _ := json .Marshal (users )
757+ w .WriteHeader (http .StatusOK )
758+ _ , _ = w .Write (body )
759+ },
760+ }
761+ }
762+
763+ makeMockClient := func (searchResult * github.IssuesSearchResult , repos []repoFixture ) * http.Client {
764+ handlers := repoHandlers (repos )
765+ handlers [GetSearchIssues ] = mockResponse (t , http .StatusOK , searchResult )
766+ return MockHTTPClientWithHandlers (handlers )
767+ }
768+
769+ reqParams := map [string ]any {"query" : "bug" }
770+
771+ t .Run ("insiders mode disabled omits ifc label" , func (t * testing.T ) {
772+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {makeIssue ("octocat" , "public-repo" , 1 )}}
773+ deps := BaseDeps {
774+ Client : github .NewClient (makeMockClient (searchResult , []repoFixture {{owner : "octocat" , repo : "public-repo" }})),
775+ Flags : FeatureFlags {InsidersMode : false },
776+ }
777+ handler := serverTool .Handler (deps )
778+
779+ request := createMCPRequest (reqParams )
780+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
781+ require .NoError (t , err )
782+ require .False (t , result .IsError )
783+ assert .Nil (t , result .Meta )
784+ })
785+
786+ t .Run ("insiders mode enabled with single public repo emits public untrusted" , func (t * testing.T ) {
787+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {makeIssue ("octocat" , "public-repo" , 1 )}}
788+ deps := BaseDeps {
789+ Client : github .NewClient (makeMockClient (searchResult , []repoFixture {{owner : "octocat" , repo : "public-repo" }})),
790+ Flags : FeatureFlags {InsidersMode : true },
791+ }
792+ handler := serverTool .Handler (deps )
793+
794+ request := createMCPRequest (reqParams )
795+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
796+ require .NoError (t , err )
797+ require .False (t , result .IsError )
798+
799+ require .NotNil (t , result .Meta )
800+ ifcMap := unmarshalIFC (t , result .Meta ["ifc" ])
801+ assert .Equal (t , "untrusted" , ifcMap ["integrity" ])
802+ assert .Equal (t , []any {"public" }, ifcMap ["confidentiality" ])
803+ })
804+
805+ t .Run ("insiders mode mixed public and private collapses to public" , func (t * testing.T ) {
806+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {
807+ makeIssue ("octocat" , "private-repo" , 1 ),
808+ makeIssue ("octocat" , "public-repo" , 2 ),
809+ }}
810+ deps := BaseDeps {
811+ Client : github .NewClient (makeMockClient (searchResult , []repoFixture {
812+ {owner : "octocat" , repo : "private-repo" , isPrivate : true , collaborators : []string {"alice" }},
813+ {owner : "octocat" , repo : "public-repo" },
814+ })),
815+ Flags : FeatureFlags {InsidersMode : true },
816+ }
817+ handler := serverTool .Handler (deps )
818+
819+ request := createMCPRequest (reqParams )
820+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
821+ require .NoError (t , err )
822+ require .False (t , result .IsError )
823+
824+ require .NotNil (t , result .Meta )
825+ ifcMap := unmarshalIFC (t , result .Meta ["ifc" ])
826+ assert .Equal (t , "untrusted" , ifcMap ["integrity" ])
827+ assert .Equal (t , []any {"public" }, ifcMap ["confidentiality" ])
828+ })
829+
830+ t .Run ("insiders mode two private repos intersect collaborators" , func (t * testing.T ) {
831+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {
832+ makeIssue ("octocat" , "repo-a" , 1 ),
833+ makeIssue ("octocat" , "repo-b" , 2 ),
834+ }}
835+ deps := BaseDeps {
836+ Client : github .NewClient (makeMockClient (searchResult , []repoFixture {
837+ {owner : "octocat" , repo : "repo-a" , isPrivate : true , collaborators : []string {"alice" , "bob" , "carol" }},
838+ {owner : "octocat" , repo : "repo-b" , isPrivate : true , collaborators : []string {"bob" , "carol" , "dan" }},
839+ })),
840+ Flags : FeatureFlags {InsidersMode : true },
841+ }
842+ handler := serverTool .Handler (deps )
843+
844+ request := createMCPRequest (reqParams )
845+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
846+ require .NoError (t , err )
847+ require .False (t , result .IsError )
848+
849+ require .NotNil (t , result .Meta )
850+ ifcMap := unmarshalIFC (t , result .Meta ["ifc" ])
851+ assert .Equal (t , "untrusted" , ifcMap ["integrity" ])
852+ assert .Equal (t , []any {"bob" , "carol" }, ifcMap ["confidentiality" ])
853+ })
854+
855+ t .Run ("insiders mode skips ifc label when visibility lookup fails" , func (t * testing.T ) {
856+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {makeIssue ("octocat" , "broken" , 1 )}}
857+ deps := BaseDeps {
858+ Client : github .NewClient (makeMockClient (searchResult , []repoFixture {
859+ {owner : "octocat" , repo : "broken" , repoStatus : http .StatusInternalServerError },
860+ })),
861+ Flags : FeatureFlags {InsidersMode : true },
862+ }
863+ handler := serverTool .Handler (deps )
864+
865+ request := createMCPRequest (reqParams )
866+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
867+ require .NoError (t , err )
868+ require .False (t , result .IsError , "tool call should still succeed when visibility lookup fails" )
869+
870+ if result .Meta != nil {
871+ _ , hasIFC := result .Meta ["ifc" ]
872+ assert .False (t , hasIFC , "ifc label should be omitted when visibility lookup fails" )
873+ }
874+ })
875+
876+ t .Run ("insiders mode empty results emits public untrusted" , func (t * testing.T ) {
877+ searchResult := & github.IssuesSearchResult {Issues : []* github.Issue {}}
878+ deps := BaseDeps {
879+ Client : github .NewClient (makeMockClient (searchResult , nil )),
880+ Flags : FeatureFlags {InsidersMode : true },
881+ }
882+ handler := serverTool .Handler (deps )
883+
884+ request := createMCPRequest (reqParams )
885+ result , err := handler (ContextWithDeps (context .Background (), deps ), & request )
886+ require .NoError (t , err )
887+ require .False (t , result .IsError )
888+
889+ require .NotNil (t , result .Meta )
890+ ifcMap := unmarshalIFC (t , result .Meta ["ifc" ])
891+ assert .Equal (t , "untrusted" , ifcMap ["integrity" ])
892+ assert .Equal (t , []any {"public" }, ifcMap ["confidentiality" ])
893+ })
894+ }
895+
896+ func unmarshalIFC (t * testing.T , ifcLabel any ) map [string ]any {
897+ t .Helper ()
898+ require .NotNil (t , ifcLabel , "ifc label should be present" )
899+ ifcJSON , err := json .Marshal (ifcLabel )
900+ require .NoError (t , err )
901+ var ifcMap map [string ]any
902+ require .NoError (t , json .Unmarshal (ifcJSON , & ifcMap ))
903+ return ifcMap
904+ }
905+
696906func Test_CreateIssue (t * testing.T ) {
697907 // Verify tool definition once
698908 serverTool := IssueWrite (translations .NullTranslationHelper )
0 commit comments