@@ -2,13 +2,16 @@ package cmd
22
33import (
44 "bytes"
5+ "fmt"
56 "io"
67 "os"
78 "testing"
89
910 "github.com/shivasurya/code-pathfinder/sast-engine/dsl"
1011 "github.com/shivasurya/code-pathfinder/sast-engine/graph"
1112 "github.com/shivasurya/code-pathfinder/sast-engine/graph/callgraph/core"
13+ "github.com/shivasurya/code-pathfinder/sast-engine/output"
14+ "github.com/shivasurya/code-pathfinder/sast-engine/ruleset"
1215 "github.com/stretchr/testify/assert"
1316 "github.com/stretchr/testify/require"
1417)
@@ -407,3 +410,195 @@ line 7`
407410 assert .Equal (t , 0 , len (snippet .Lines ))
408411 })
409412}
413+
414+ // mockManifestProvider is a mock implementation of ruleset.ManifestProvider for testing.
415+ type mockManifestProvider struct {
416+ manifests map [string ]* ruleset.Manifest
417+ errors map [string ]error
418+ }
419+
420+ func newMockManifestProvider () * mockManifestProvider {
421+ return & mockManifestProvider {
422+ manifests : make (map [string ]* ruleset.Manifest ),
423+ errors : make (map [string ]error ),
424+ }
425+ }
426+
427+ func (m * mockManifestProvider ) LoadCategoryManifest (category string ) (* ruleset.Manifest , error ) {
428+ if err , exists := m .errors [category ]; exists {
429+ return nil , err
430+ }
431+ if manifest , exists := m .manifests [category ]; exists {
432+ return manifest , nil
433+ }
434+ return nil , fmt .Errorf ("category not found: %s" , category )
435+ }
436+
437+ func (m * mockManifestProvider ) addManifest (category string , bundleNames []string ) {
438+ manifest := & ruleset.Manifest {
439+ Category : category ,
440+ Bundles : make (map [string ]* ruleset.Bundle ),
441+ }
442+ for _ , name := range bundleNames {
443+ manifest .Bundles [name ] = & ruleset.Bundle {Name : name }
444+ }
445+ m .manifests [category ] = manifest
446+ }
447+
448+ func (m * mockManifestProvider ) addError (category string , err error ) {
449+ m .errors [category ] = err
450+ }
451+
452+ func TestExpandBundleSpecs (t * testing.T ) {
453+ t .Run ("expands docker/all to multiple bundles" , func (t * testing.T ) {
454+ mock := newMockManifestProvider ()
455+ mock .addManifest ("docker" , []string {"security" , "best-practice" , "performance" })
456+ logger := output .NewLogger (output .VerbosityDefault )
457+
458+ specs := []string {"docker/all" }
459+ expanded , err := expandBundleSpecs (specs , mock , logger )
460+
461+ require .NoError (t , err )
462+ assert .Equal (t , 3 , len (expanded ))
463+ assert .Contains (t , expanded , "docker/best-practice" )
464+ assert .Contains (t , expanded , "docker/performance" )
465+ assert .Contains (t , expanded , "docker/security" )
466+ })
467+
468+ t .Run ("expands python/all to multiple bundles" , func (t * testing.T ) {
469+ mock := newMockManifestProvider ()
470+ mock .addManifest ("python" , []string {"deserialization" , "django" , "flask" })
471+ logger := output .NewLogger (output .VerbosityDefault )
472+
473+ specs := []string {"python/all" }
474+ expanded , err := expandBundleSpecs (specs , mock , logger )
475+
476+ require .NoError (t , err )
477+ assert .Equal (t , 3 , len (expanded ))
478+ assert .Contains (t , expanded , "python/deserialization" )
479+ assert .Contains (t , expanded , "python/django" )
480+ assert .Contains (t , expanded , "python/flask" )
481+ })
482+
483+ t .Run ("keeps regular bundle specs unchanged" , func (t * testing.T ) {
484+ mock := newMockManifestProvider ()
485+ logger := output .NewLogger (output .VerbosityDefault )
486+
487+ specs := []string {"docker/security" , "python/django" }
488+ expanded , err := expandBundleSpecs (specs , mock , logger )
489+
490+ require .NoError (t , err )
491+ assert .Equal (t , 2 , len (expanded ))
492+ assert .Equal (t , "docker/security" , expanded [0 ])
493+ assert .Equal (t , "python/django" , expanded [1 ])
494+ })
495+
496+ t .Run ("mixes category expansion with regular specs" , func (t * testing.T ) {
497+ mock := newMockManifestProvider ()
498+ mock .addManifest ("docker" , []string {"security" , "best-practice" })
499+ logger := output .NewLogger (output .VerbosityDefault )
500+
501+ specs := []string {"docker/all" , "python/django" }
502+ expanded , err := expandBundleSpecs (specs , mock , logger )
503+
504+ require .NoError (t , err )
505+ assert .Equal (t , 3 , len (expanded ))
506+ assert .Contains (t , expanded , "docker/best-practice" )
507+ assert .Contains (t , expanded , "docker/security" )
508+ assert .Contains (t , expanded , "python/django" )
509+ })
510+
511+ t .Run ("handles category with single bundle" , func (t * testing.T ) {
512+ mock := newMockManifestProvider ()
513+ mock .addManifest ("docker" , []string {"security" })
514+ logger := output .NewLogger (output .VerbosityDefault )
515+
516+ specs := []string {"docker/all" }
517+ expanded , err := expandBundleSpecs (specs , mock , logger )
518+
519+ require .NoError (t , err )
520+ assert .Equal (t , 1 , len (expanded ))
521+ assert .Equal (t , "docker/security" , expanded [0 ])
522+ })
523+
524+ t .Run ("handles category with no bundles" , func (t * testing.T ) {
525+ mock := newMockManifestProvider ()
526+ mock .addManifest ("docker" , []string {}) // Empty bundles
527+ logger := output .NewLogger (output .VerbosityDefault )
528+
529+ specs := []string {"docker/all" }
530+ expanded , err := expandBundleSpecs (specs , mock , logger )
531+
532+ require .NoError (t , err )
533+ assert .Equal (t , 0 , len (expanded ))
534+ })
535+
536+ t .Run ("returns error when category manifest fails to load" , func (t * testing.T ) {
537+ mock := newMockManifestProvider ()
538+ mock .addError ("nonexistent" , fmt .Errorf ("HTTP 404: not found" ))
539+ logger := output .NewLogger (output .VerbosityDefault )
540+
541+ specs := []string {"nonexistent/all" }
542+ expanded , err := expandBundleSpecs (specs , mock , logger )
543+
544+ require .Error (t , err )
545+ assert .Nil (t , expanded )
546+ assert .Contains (t , err .Error (), "failed to load manifest for category nonexistent" )
547+ })
548+
549+ t .Run ("returns error for invalid spec format" , func (t * testing.T ) {
550+ mock := newMockManifestProvider ()
551+ logger := output .NewLogger (output .VerbosityDefault )
552+
553+ specs := []string {"invalid-spec-no-slash" }
554+ expanded , err := expandBundleSpecs (specs , mock , logger )
555+
556+ require .Error (t , err )
557+ assert .Nil (t , expanded )
558+ assert .Contains (t , err .Error (), "invalid ruleset spec" )
559+ })
560+
561+ t .Run ("handles multiple category expansions" , func (t * testing.T ) {
562+ mock := newMockManifestProvider ()
563+ mock .addManifest ("docker" , []string {"security" , "best-practice" })
564+ mock .addManifest ("python" , []string {"django" , "flask" })
565+ logger := output .NewLogger (output .VerbosityDefault )
566+
567+ specs := []string {"docker/all" , "python/all" }
568+ expanded , err := expandBundleSpecs (specs , mock , logger )
569+
570+ require .NoError (t , err )
571+ assert .Equal (t , 4 , len (expanded ))
572+ assert .Contains (t , expanded , "docker/best-practice" )
573+ assert .Contains (t , expanded , "docker/security" )
574+ assert .Contains (t , expanded , "python/django" )
575+ assert .Contains (t , expanded , "python/flask" )
576+ })
577+
578+ t .Run ("handles empty input specs" , func (t * testing.T ) {
579+ mock := newMockManifestProvider ()
580+ logger := output .NewLogger (output .VerbosityDefault )
581+
582+ specs := []string {}
583+ expanded , err := expandBundleSpecs (specs , mock , logger )
584+
585+ require .NoError (t , err )
586+ assert .Equal (t , 0 , len (expanded ))
587+ })
588+
589+ t .Run ("preserves order for mixed specs" , func (t * testing.T ) {
590+ mock := newMockManifestProvider ()
591+ mock .addManifest ("docker" , []string {"security" })
592+ logger := output .NewLogger (output .VerbosityDefault )
593+
594+ specs := []string {"python/django" , "docker/all" , "java/security" }
595+ expanded , err := expandBundleSpecs (specs , mock , logger )
596+
597+ require .NoError (t , err )
598+ assert .Equal (t , 3 , len (expanded ))
599+ // Order should be: python/django, docker/security (expanded), java/security
600+ assert .Equal (t , "python/django" , expanded [0 ])
601+ assert .Equal (t , "docker/security" , expanded [1 ])
602+ assert .Equal (t , "java/security" , expanded [2 ])
603+ })
604+ }
0 commit comments