@@ -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,159 @@ 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 a substring match via prefix when only one tool matches" , 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+ // "eslint9" matches shortName "eslint9" exactly
692+ await program . parseAsync ( [
693+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
694+ "--tools" , "eslint9" ,
695+ ] ) ;
696+
697+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
698+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
699+ { toolUuids : [ "uuid-eslint9" ] } ,
700+ ) ;
701+ } ) ;
702+
703+ it ( "should error when tool name is ambiguous" , async ( ) => {
704+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
705+
706+ const mockExit = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
707+ throw new Error ( "process.exit called" ) ;
708+ } ) ;
709+ const mockStderr = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
710+
711+ const program = createProgram ( ) ;
712+ await expect (
713+ program . parseAsync ( [
714+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
715+ "--tools" , "mark" ,
716+ ] ) ,
717+ ) . rejects . toThrow ( "process.exit called" ) ;
718+
719+ expect ( mockStderr ) . toHaveBeenCalledWith (
720+ expect . stringContaining ( "ambiguous" ) ,
721+ ) ;
722+
723+ mockExit . mockRestore ( ) ;
724+ mockStderr . mockRestore ( ) ;
725+ } ) ;
726+
727+ it ( "should error when tool name is not found" , async ( ) => {
728+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
729+
730+ const mockExit = vi . spyOn ( process , "exit" ) . mockImplementation ( ( ) => {
731+ throw new Error ( "process.exit called" ) ;
732+ } ) ;
733+ const mockStderr = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
734+
735+ const program = createProgram ( ) ;
736+ await expect (
737+ program . parseAsync ( [
738+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
739+ "--tools" , "nonexistent" ,
740+ ] ) ,
741+ ) . rejects . toThrow ( "process.exit called" ) ;
742+
743+ expect ( mockStderr ) . toHaveBeenCalledWith (
744+ expect . stringContaining ( 'not found' ) ,
745+ ) ;
746+
747+ mockExit . mockRestore ( ) ;
748+ mockStderr . mockRestore ( ) ;
749+ } ) ;
750+
751+ it ( "should handle mixed UUIDs and tool names" , async ( ) => {
752+ vi . mocked ( ToolsService . listTools ) . mockResolvedValue ( mockToolList as any ) ;
753+ vi . mocked ( AnalysisService . searchRepositoryIssues ) . mockResolvedValue ( {
754+ data : [ ] ,
755+ } as any ) ;
756+
757+ const program = createProgram ( ) ;
758+ await program . parseAsync ( [
759+ "node" , "test" , "issues" , "gh" , "test-org" , "test-repo" ,
760+ "--tools" , "a1b2c3d4-e5f6-7890-abcd-ef1234567890,semgrep" ,
761+ ] ) ;
762+
763+ expect ( AnalysisService . searchRepositoryIssues ) . toHaveBeenCalledWith (
764+ "gh" , "test-org" , "test-repo" , undefined , 100 ,
765+ { toolUuids : [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890" , "uuid-semgrep" ] } ,
766+ ) ;
767+ } ) ;
768+ } ) ;
769+
615770 it ( "should fail when CODACY_API_TOKEN is not set" , async ( ) => {
616771 delete process . env . CODACY_API_TOKEN ;
617772
0 commit comments