@@ -31,6 +31,8 @@ const printFinalStatusMock = jest.fn<any>();
3131const printCompactOutputMock = jest . fn < any > ( ) ;
3232const buildSuggestedFixCommandPlanMock = jest . fn < any > ( ) ;
3333const spawnMock = jest . fn < any > ( ) ;
34+ const buildReportDataMock = jest . fn < any > ( ) ;
35+ const writeHtmlReportMock = jest . fn < any > ( ) ;
3436
3537jest . unstable_mockModule ( "../src/cli/help.js" , ( ) => ( {
3638 printBanner : printBannerMock ,
@@ -104,6 +106,11 @@ jest.unstable_mockModule("node:child_process", () => ({
104106 spawn : spawnMock ,
105107} ) ) ;
106108
109+ jest . unstable_mockModule ( "../src/output/html-reporter.js" , ( ) => ( {
110+ buildReportData : buildReportDataMock ,
111+ writeHtmlReport : writeHtmlReportMock ,
112+ } ) ) ;
113+
107114function createScanInput ( overrides ?: Partial < ScanInput > ) : ScanInput {
108115 return {
109116 mode : "manifest-fallback" ,
@@ -202,6 +209,8 @@ describe("CLI integration", () => {
202209 package : finding . pkg . name ,
203210 severity : finding . severity ,
204211 } ) ) ;
212+ buildReportDataMock . mockReturnValue ( { cliVersion : "1.8.0" , findings : [ ] } ) ;
213+ writeHtmlReportMock . mockResolvedValue ( { reportPath : "/tmp/cve-report/index.html" } ) ;
205214 } ) ;
206215
207216 it ( "returns a json payload and exits successfully when no findings are present" , async ( ) => {
@@ -549,4 +558,81 @@ describe("CLI integration", () => {
549558 expect ( result . exitCode ) . toBe ( 1 ) ;
550559 expect ( result . stderr . join ( "\n" ) ) . toContain ( "--fix cannot be used with --json" ) ;
551560 } ) ;
561+
562+ describe ( "--report flag" , ( ) => {
563+ it ( "calls writeHtmlReport and prints the report path" , async ( ) => {
564+ const packages = [
565+ { name : "lodash" , version : "4.17.21" , ecosystem : "npm" , paths : [ [ "project" , "lodash" ] ] } ,
566+ ] ;
567+ loadPackagesMock . mockReturnValue ( createScanInput ( { packages } ) ) ;
568+ scanPackagesMock . mockResolvedValue ( [ ] ) ;
569+ parseArgsMock . mockReturnValue ( {
570+ command : "scan" ,
571+ options : {
572+ failOn : "critical" ,
573+ batchSize : "100" ,
574+ searchDepth : "4" ,
575+ minSeverity : "medium" ,
576+ report : "./my-report" ,
577+ noOpen : true ,
578+ } ,
579+ projectArg : "." ,
580+ } ) ;
581+
582+ const result = await runIndexModule ( ) ;
583+
584+ expect ( writeHtmlReportMock ) . toHaveBeenCalledWith (
585+ expect . objectContaining ( {
586+ outputDir : expect . stringContaining ( "my-report" ) ,
587+ autoOpen : false ,
588+ } )
589+ ) ;
590+ const output = result . stdout . join ( "\n" ) ;
591+ expect ( output ) . toContain ( "/tmp/cve-report/index.html" ) ;
592+ } ) ;
593+
594+ it ( "throws when --report and --json are both set" , async ( ) => {
595+ parseArgsMock . mockReturnValue ( {
596+ command : "scan" ,
597+ options : {
598+ failOn : "critical" ,
599+ batchSize : "100" ,
600+ searchDepth : "4" ,
601+ minSeverity : "medium" ,
602+ report : true ,
603+ json : true ,
604+ } ,
605+ projectArg : "." ,
606+ } ) ;
607+
608+ const result = await runIndexModule ( ) ;
609+
610+ expect ( result . stderr . join ( "\n" ) ) . toContain ( "--report cannot be used with --json" ) ;
611+ } ) ;
612+
613+ it ( "uses ./cve-report as default output dir when --report is true (boolean)" , async ( ) => {
614+ const packages = [
615+ { name : "lodash" , version : "4.17.21" , ecosystem : "npm" , paths : [ [ "project" , "lodash" ] ] } ,
616+ ] ;
617+ loadPackagesMock . mockReturnValue ( createScanInput ( { packages } ) ) ;
618+ scanPackagesMock . mockResolvedValue ( [ ] ) ;
619+ parseArgsMock . mockReturnValue ( {
620+ command : "scan" ,
621+ options : {
622+ failOn : "critical" ,
623+ batchSize : "100" ,
624+ searchDepth : "4" ,
625+ minSeverity : "medium" ,
626+ report : true ,
627+ noOpen : true ,
628+ } ,
629+ projectArg : "." ,
630+ } ) ;
631+
632+ await runIndexModule ( ) ;
633+
634+ const callArgs = writeHtmlReportMock . mock . calls [ 0 ] [ 0 ] ;
635+ expect ( callArgs . outputDir ) . toContain ( "cve-report" ) ;
636+ } ) ;
637+ } ) ;
552638} ) ;
0 commit comments