@@ -53,6 +53,12 @@ function createTestSarif() {
5353 { location : { physicalLocation : { artifactLocation : { uri : 'src/db.js' } , region : { startLine : 42 } } , message : { text : 'query(...)' } } } ,
5454 ] } ] } ] ,
5555 } ,
56+ {
57+ ruleId : 'js/sql-injection' ,
58+ ruleIndex : 0 ,
59+ message : { text : 'SQL injection from request body.' } ,
60+ locations : [ { physicalLocation : { artifactLocation : { uri : 'src/api.js' } , region : { startLine : 15 , startColumn : 3 , endColumn : 40 } } } ] ,
61+ } ,
5662 {
5763 ruleId : 'js/xss' ,
5864 ruleIndex : 1 ,
@@ -64,6 +70,25 @@ function createTestSarif() {
6470 } ;
6571}
6672
73+ /** SARIF with a defined rule but zero results — validates resultCount: 0 */
74+ function createZeroResultsSarif ( ) {
75+ return {
76+ version : '2.1.0' ,
77+ runs : [ {
78+ tool : {
79+ driver : {
80+ name : 'CodeQL' ,
81+ version : '2.25.1' ,
82+ rules : [
83+ { id : 'js/unused-variable' , shortDescription : { text : 'Unused variable' } } ,
84+ ] ,
85+ } ,
86+ } ,
87+ results : [ ] ,
88+ } ] ,
89+ } ;
90+ }
91+
6792// ---------------------------------------------------------------------------
6893// Tests
6994// ---------------------------------------------------------------------------
@@ -170,7 +195,7 @@ describe('SARIF Tools', () => {
170195 const parsed = JSON . parse ( result . content [ 0 ] . text ) ;
171196
172197 expect ( parsed . ruleId ) . toBe ( 'js/sql-injection' ) ;
173- expect ( parsed . resultCount ) . toBe ( 1 ) ;
198+ expect ( parsed . resultCount ) . toBe ( 2 ) ;
174199 expect ( parsed . extractedSarif . runs [ 0 ] . tool . driver . rules ) . toHaveLength ( 1 ) ;
175200 } ) ;
176201
@@ -212,15 +237,29 @@ describe('SARIF Tools', () => {
212237 } ) ;
213238
214239 describe ( 'sarif_list_rules' , ( ) => {
215- it ( 'should list all rules with result counts' , async ( ) => {
240+ it ( 'should list all rules with per-rule result counts' , async ( ) => {
216241 const result = await handlers . sarif_list_rules ( { sarifPath : testSarifPath } ) ;
217242 const parsed = JSON . parse ( result . content [ 0 ] . text ) ;
218243
219244 expect ( parsed . totalRules ) . toBe ( 2 ) ;
220- expect ( parsed . totalResults ) . toBe ( 2 ) ;
245+ expect ( parsed . totalResults ) . toBe ( 3 ) ;
221246 expect ( parsed . rules [ 0 ] . ruleId ) . toBe ( 'js/sql-injection' ) ;
222- expect ( parsed . rules [ 0 ] . resultCount ) . toBe ( 1 ) ;
247+ expect ( parsed . rules [ 0 ] . resultCount ) . toBe ( 2 ) ;
223248 expect ( parsed . rules [ 1 ] . ruleId ) . toBe ( 'js/xss' ) ;
249+ expect ( parsed . rules [ 1 ] . resultCount ) . toBe ( 1 ) ;
250+ } ) ;
251+
252+ it ( 'should return resultCount 0 for rules with no results' , async ( ) => {
253+ const noResultsPath = join ( testStorageDir , 'no-results.sarif' ) ;
254+ writeFileSync ( noResultsPath , JSON . stringify ( createZeroResultsSarif ( ) ) ) ;
255+
256+ const result = await handlers . sarif_list_rules ( { sarifPath : noResultsPath } ) ;
257+ const parsed = JSON . parse ( result . content [ 0 ] . text ) ;
258+
259+ expect ( parsed . totalRules ) . toBe ( 1 ) ;
260+ expect ( parsed . totalResults ) . toBe ( 0 ) ;
261+ expect ( parsed . rules [ 0 ] . ruleId ) . toBe ( 'js/unused-variable' ) ;
262+ expect ( parsed . rules [ 0 ] . resultCount ) . toBe ( 0 ) ;
224263 } ) ;
225264 } ) ;
226265
@@ -303,7 +342,7 @@ describe('SARIF Tools', () => {
303342
304343 it ( 'should detect changed result counts' , async ( ) => {
305344 const sarifB = createTestSarif ( ) ;
306- sarifB . runs [ 0 ] . results = [ sarifB . runs [ 0 ] . results [ 0 ] ] ; // remove XSS result
345+ sarifB . runs [ 0 ] . results = sarifB . runs [ 0 ] . results . filter ( r => r . ruleId !== 'js/xss' ) ;
307346 const pathB = join ( testStorageDir , 'modified.sarif' ) ;
308347 writeFileSync ( pathB , JSON . stringify ( sarifB ) ) ;
309348
0 commit comments