@@ -2,8 +2,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
22import { Command } from "commander" ;
33import { registerIssuesCommand } from "./issues" ;
44import { AnalysisService } from "../api/client/services/AnalysisService" ;
5+ import { ToolsService } from "../api/client/services/ToolsService" ;
56
67vi . mock ( "../api/client/services/AnalysisService" ) ;
8+ vi . mock ( "../api/client/services/ToolsService" ) ;
79vi . mock ( "../utils/credentials" , ( ) => ( { loadCredentials : vi . fn ( ( ) => null ) } ) ) ;
810vi . spyOn ( console , "log" ) . mockImplementation ( ( ) => { } ) ;
911
@@ -612,6 +614,177 @@ describe("issues command", () => {
612614 ) ;
613615 } ) ;
614616
617+ describe ( "--tools filter" , ( ) => {
618+ const mockToolList = {
619+ data : [
620+ { uuid : "uuid-eslint" , name : "ESLint" , shortName : "eslint" , prefix : "ESLint_" } ,
621+ { uuid : "uuid-eslint9" , name : "ESLint 9" , shortName : "eslint9" , prefix : "ESLint9_" } ,
622+ { uuid : "uuid-semgrep" , name : "Semgrep" , shortName : "semgrep" , prefix : "Semgrep_" } ,
623+ { uuid : "uuid-markdownlint" , name : "Markdownlint" , shortName : "markdownlint" , prefix : "Markdownlint_" } ,
624+ { uuid : "uuid-remarklint" , name : "Remarklint" , shortName : "remarklint" , prefix : "Remarklint_" } ,
625+ ] ,
626+ pagination : undefined ,
627+ } ;
628+
629+ it ( "should pass a UUID directly to body.toolUuids" , async ( ) => {
630+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
631+ data : [ ] ,
632+ } as any ) ;
633+
634+ const program = createProgram ( ) ;
635+ await program . parseAsync ( [
636+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
637+ "--tools" , "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ,
638+ ] ) ;
639+
640+ expect ( ToolsService . listTools ) . not . toHaveBeenCalled ( ) ;
641+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
642+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
643+ { toolUuids : [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ] } ,
644+ ) ;
645+ } ) ;
646+
647+ it ( "should resolve an exact tool name to its UUID" , async ( ) => {
648+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
649+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
650+ data : [ ] ,
651+ } as any ) ;
652+
653+ const program = createProgram ( ) ;
654+ await program . parseAsync ( [
655+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
656+ "--tools" , "eslint" ,
657+ ] ) ;
658+
659+ expect ( ToolsService . listTools ) . toHaveBeenCalled ( ) ;
660+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
661+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
662+ { toolUuids : [ "uuid-eslint" ] } ,
663+ ) ;
664+ } ) ;
665+
666+ it ( "should resolve a shortName match to its UUID" , async ( ) => {
667+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
668+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
669+ data : [ ] ,
670+ } as any ) ;
671+
672+ const program = createProgram ( ) ;
673+ await program . parseAsync ( [
674+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
675+ "--tools" , "semgrep" ,
676+ ] ) ;
677+
678+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
679+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
680+ { toolUuids : [ "uuid-semgrep" ] } ,
681+ ) ;
682+ } ) ;
683+
684+ it ( "should resolve an exact shortName match (eslint9)" , async ( ) => {
685+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
686+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
687+ data : [ ] ,
688+ } as any ) ;
689+
690+ const program = createProgram ( ) ;
691+ await program . parseAsync ( [
692+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
693+ "--tools" , "eslint9" ,
694+ ] ) ;
695+
696+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
697+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
698+ { toolUuids : [ "uuid-eslint9" ] } ,
699+ ) ;
700+ } ) ;
701+
702+ it ( "should resolve a unique substring match via prefix" , async ( ) => {
703+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
704+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
705+ data : [ ] ,
706+ } as any ) ;
707+
708+ const program = createProgram ( ) ;
709+ // "semgr" is not an exact name or shortName, but substring-matches only Semgrep
710+ await program . parseAsync ( [
711+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
712+ "--tools" , "semgr" ,
713+ ] ) ;
714+
715+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
716+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
717+ { toolUuids : [ "uuid-semgrep" ] } ,
718+ ) ;
719+ } ) ;
720+
721+ it ( "should error when tool name is ambiguous" , async ( ) => {
722+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
723+
724+ const mockExit = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
725+ throw new Error ( "process.exit called" ) ;
726+ } ) ;
727+ const mockStderr = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
728+
729+ const program = createProgram ( ) ;
730+ await expect (
731+ program . parseAsync ( [
732+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
733+ "--tools" , "mark" ,
734+ ] ) ,
735+ ) . rejects . toThrow ( "process.exit called" ) ;
736+
737+ expect ( mockStderr ) . toHaveBeenCalledWith (
738+ expect . stringContaining ( "ambiguous" ) ,
739+ ) ;
740+
741+ mockExit . mockRestore ( ) ;
742+ mockStderr . mockRestore ( ) ;
743+ } ) ;
744+
745+ it ( "should error when tool name is not found" , async ( ) => {
746+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
747+
748+ const mockExit = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
749+ throw new Error ( "process.exit called" ) ;
750+ } ) ;
751+ const mockStderr = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
752+
753+ const program = createProgram ( ) ;
754+ await expect (
755+ program . parseAsync ( [
756+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
757+ "--tools" , "nonexistent" ,
758+ ] ) ,
759+ ) . rejects . toThrow ( "process.exit called" ) ;
760+
761+ expect ( mockStderr ) . toHaveBeenCalledWith (
762+ expect . stringContaining ( "not found" ) ,
763+ ) ;
764+
765+ mockExit . mockRestore ( ) ;
766+ mockStderr . mockRestore ( ) ;
767+ } ) ;
768+
769+ it ( "should handle mixed UUIDs and tool names" , async ( ) => {
770+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
771+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
772+ data : [ ] ,
773+ } as any ) ;
774+
775+ const program = createProgram ( ) ;
776+ await program . parseAsync ( [
777+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
778+ "--tools" , "a1b2c3d4-e5f6-7890-abcd-ef1234567890,semgrep" ,
779+ ] ) ;
780+
781+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
782+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
783+ { toolUuids : [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890" , "uuid-semgrep" ] } ,
784+ ) ;
785+ } ) ;
786+ } ) ;
787+
615788 it ( "should fail when CODACY_API_TOKEN is not set" , async ( ) => {
616789 delete process . env . CODACY_API_TOKEN ;
617790
0 commit comments