@@ -545,4 +545,387 @@ mod tests {
545545 assert ! ( intel. contains( "Critical" ) ) ;
546546 assert ! ( intel. contains( "1" ) ) ;
547547 }
548+
549+ // ========================================================================
550+ // parse_cargo_toml tests
551+ // ========================================================================
552+
553+ #[ test]
554+ fn test_parse_cargo_toml_valid ( ) {
555+ let temp = TempDir :: new ( ) . unwrap ( ) ;
556+ fs:: write (
557+ temp. path ( ) . join ( "Cargo.toml" ) ,
558+ r#"[package]
559+ name = "test"
560+ version = "0.1.0"
561+
562+ [dependencies]
563+ serde = "1.0"
564+ tokio = "1.0"
565+
566+ [dev-dependencies]
567+ criterion = "0.4"
568+ "# ,
569+ )
570+ . unwrap ( ) ;
571+
572+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
573+ let deps = bootstrapper. parse_cargo_toml ( temp. path ( ) ) . unwrap ( ) ;
574+
575+ assert_eq ! ( deps. len( ) , 3 ) ;
576+ assert ! ( deps. iter( ) . any( |d| d. name == "serde" && d. version == "1.0" ) ) ;
577+ assert ! ( deps. iter( ) . any( |d| d. name == "tokio" && d. version == "1.0" ) ) ;
578+ assert ! ( deps
579+ . iter( )
580+ . any( |d| d. name == "criterion" && d. version == "0.4" ) ) ;
581+ }
582+
583+ #[ test]
584+ fn test_parse_cargo_toml_empty_file ( ) {
585+ let temp = TempDir :: new ( ) . unwrap ( ) ;
586+ fs:: write ( temp. path ( ) . join ( "Cargo.toml" ) , "" ) . unwrap ( ) ;
587+
588+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
589+ let deps = bootstrapper. parse_cargo_toml ( temp. path ( ) ) . unwrap ( ) ;
590+
591+ assert ! ( deps. is_empty( ) ) ;
592+ }
593+
594+ #[ test]
595+ fn test_parse_cargo_toml_dependencies_section_empty ( ) {
596+ let temp = TempDir :: new ( ) . unwrap ( ) ;
597+ fs:: write (
598+ temp. path ( ) . join ( "Cargo.toml" ) ,
599+ r#"[package]
600+ name = "test"
601+
602+ [dependencies]
603+ "# ,
604+ )
605+ . unwrap ( ) ;
606+
607+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
608+ let deps = bootstrapper. parse_cargo_toml ( temp. path ( ) ) . unwrap ( ) ;
609+
610+ assert ! ( deps. is_empty( ) ) ;
611+ }
612+
613+ #[ test]
614+ fn test_parse_cargo_toml_commented_deps ( ) {
615+ let temp = TempDir :: new ( ) . unwrap ( ) ;
616+ fs:: write (
617+ temp. path ( ) . join ( "Cargo.toml" ) ,
618+ r#"[dependencies]
619+ # serde = "1.0"
620+ # tokio = "1.0"
621+ "# ,
622+ )
623+ . unwrap ( ) ;
624+
625+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
626+ let deps = bootstrapper. parse_cargo_toml ( temp. path ( ) ) . unwrap ( ) ;
627+
628+ assert ! ( deps. is_empty( ) ) ;
629+ }
630+
631+ #[ test]
632+ fn test_parse_cargo_toml_nonexistent_path ( ) {
633+ let temp = TempDir :: new ( ) . unwrap ( ) ;
634+ // Don't create Cargo.toml
635+
636+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
637+ let deps = bootstrapper. parse_cargo_toml ( temp. path ( ) ) . unwrap ( ) ;
638+
639+ assert ! ( deps. is_empty( ) ) ;
640+ }
641+
642+ // ========================================================================
643+ // parse_package_json tests
644+ // ========================================================================
645+
646+ #[ test]
647+ fn test_parse_package_json_valid ( ) {
648+ let temp = TempDir :: new ( ) . unwrap ( ) ;
649+ fs:: write (
650+ temp. path ( ) . join ( "package.json" ) ,
651+ r#"{
652+ "dependencies": {
653+ "express": "^4.0.0",
654+ "react": "^18.0.0"
655+ },
656+ "devDependencies": {
657+ "jest": "^29.0.0"
658+ },
659+ "scripts": {
660+ "test": "jest"
661+ }
662+ }
663+ "# ,
664+ )
665+ . unwrap ( ) ;
666+
667+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
668+ let ( frameworks, deps) = bootstrapper. parse_package_json ( temp. path ( ) ) . unwrap ( ) ;
669+
670+ // Only parses dependencies, not devDependencies
671+ assert_eq ! ( deps. len( ) , 2 ) ;
672+ assert ! ( frameworks. contains( & "Express" . to_string( ) ) ) ;
673+ assert ! ( frameworks. contains( & "React" . to_string( ) ) ) ;
674+ }
675+
676+ #[ test]
677+ fn test_parse_package_json_empty ( ) {
678+ let temp = TempDir :: new ( ) . unwrap ( ) ;
679+ fs:: write ( temp. path ( ) . join ( "package.json" ) , "{}" ) . unwrap ( ) ;
680+
681+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
682+ let ( frameworks, deps) = bootstrapper. parse_package_json ( temp. path ( ) ) . unwrap ( ) ;
683+
684+ assert ! ( deps. is_empty( ) ) ;
685+ assert ! ( frameworks. is_empty( ) ) ;
686+ }
687+
688+ #[ test]
689+ fn test_parse_package_json_malformed ( ) {
690+ let temp = TempDir :: new ( ) . unwrap ( ) ;
691+ fs:: write ( temp. path ( ) . join ( "package.json" ) , "{invalid json}" ) . unwrap ( ) ;
692+
693+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
694+ let result = bootstrapper. parse_package_json ( temp. path ( ) ) ;
695+
696+ assert ! ( result. is_err( ) ) ;
697+ }
698+
699+ #[ test]
700+ fn test_parse_package_json_deps_no_dev_deps ( ) {
701+ let temp = TempDir :: new ( ) . unwrap ( ) ;
702+ fs:: write (
703+ temp. path ( ) . join ( "package.json" ) ,
704+ r#"{
705+ "dependencies": {
706+ "express": "^4.0.0"
707+ }
708+ }
709+ "# ,
710+ )
711+ . unwrap ( ) ;
712+
713+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
714+ let ( frameworks, deps) = bootstrapper. parse_package_json ( temp. path ( ) ) . unwrap ( ) ;
715+
716+ assert_eq ! ( deps. len( ) , 1 ) ;
717+ assert ! ( frameworks. contains( & "Express" . to_string( ) ) ) ;
718+ }
719+
720+ #[ test]
721+ fn test_parse_package_json_nonexistent_path ( ) {
722+ let temp = TempDir :: new ( ) . unwrap ( ) ;
723+ // Don't create package.json
724+
725+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
726+ let ( frameworks, deps) = bootstrapper. parse_package_json ( temp. path ( ) ) . unwrap ( ) ;
727+
728+ assert ! ( deps. is_empty( ) ) ;
729+ assert ! ( frameworks. is_empty( ) ) ;
730+ }
731+
732+ // ========================================================================
733+ // parse_requirements_txt tests
734+ // ========================================================================
735+
736+ #[ test]
737+ fn test_parse_requirements_txt_valid ( ) {
738+ let temp = TempDir :: new ( ) . unwrap ( ) ;
739+ fs:: write (
740+ temp. path ( ) . join ( "requirements.txt" ) ,
741+ r#"requests==2.28.0
742+ flask==2.0.0
743+ numpy
744+ pandas==1.5.0
745+ "# ,
746+ )
747+ . unwrap ( ) ;
748+
749+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
750+ let deps = bootstrapper. parse_requirements_txt ( temp. path ( ) ) . unwrap ( ) ;
751+
752+ assert_eq ! ( deps. len( ) , 4 ) ;
753+ assert ! ( deps
754+ . iter( )
755+ . any( |d| d. name == "requests" && d. version == "2.28.0" ) ) ;
756+ assert ! ( deps
757+ . iter( )
758+ . any( |d| d. name == "flask" && d. version == "2.0.0" ) ) ;
759+ assert ! ( deps. iter( ) . any( |d| d. name == "numpy" && d. version == "*" ) ) ;
760+ assert ! ( deps
761+ . iter( )
762+ . any( |d| d. name == "pandas" && d. version == "1.5.0" ) ) ;
763+ }
764+
765+ #[ test]
766+ fn test_parse_requirements_txt_empty ( ) {
767+ let temp = TempDir :: new ( ) . unwrap ( ) ;
768+ fs:: write ( temp. path ( ) . join ( "requirements.txt" ) , "" ) . unwrap ( ) ;
769+
770+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
771+ let deps = bootstrapper. parse_requirements_txt ( temp. path ( ) ) . unwrap ( ) ;
772+
773+ assert ! ( deps. is_empty( ) ) ;
774+ }
775+
776+ #[ test]
777+ fn test_parse_requirements_txt_with_comments ( ) {
778+ let temp = TempDir :: new ( ) . unwrap ( ) ;
779+ fs:: write (
780+ temp. path ( ) . join ( "requirements.txt" ) ,
781+ r#"# This is a comment
782+ requests==2.28.0
783+ # Another comment
784+ flask>=2.0.0
785+ "# ,
786+ )
787+ . unwrap ( ) ;
788+
789+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
790+ let deps = bootstrapper. parse_requirements_txt ( temp. path ( ) ) . unwrap ( ) ;
791+
792+ assert_eq ! ( deps. len( ) , 2 ) ;
793+ }
794+
795+ #[ test]
796+ fn test_parse_requirements_txt_with_blank_lines ( ) {
797+ let temp = TempDir :: new ( ) . unwrap ( ) ;
798+ fs:: write (
799+ temp. path ( ) . join ( "requirements.txt" ) ,
800+ r#"requests==2.28.0
801+
802+ flask>=2.0.0
803+
804+ numpy
805+ "# ,
806+ )
807+ . unwrap ( ) ;
808+
809+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
810+ let deps = bootstrapper. parse_requirements_txt ( temp. path ( ) ) . unwrap ( ) ;
811+
812+ assert_eq ! ( deps. len( ) , 3 ) ;
813+ }
814+
815+ #[ test]
816+ fn test_parse_requirements_txt_nonexistent_path ( ) {
817+ let temp = TempDir :: new ( ) . unwrap ( ) ;
818+ // Don't create requirements.txt
819+
820+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
821+ let deps = bootstrapper. parse_requirements_txt ( temp. path ( ) ) . unwrap ( ) ;
822+
823+ assert ! ( deps. is_empty( ) ) ;
824+ }
825+
826+ // ========================================================================
827+ // parse_go_mod tests
828+ // ========================================================================
829+
830+ #[ test]
831+ fn test_parse_go_mod_valid ( ) {
832+ let temp = TempDir :: new ( ) . unwrap ( ) ;
833+ fs:: write (
834+ temp. path ( ) . join ( "go.mod" ) ,
835+ r#"module example.com/myapp
836+
837+ go 1.20
838+
839+ require (
840+ github.com/gin-gonic/gin v1.9.0
841+ github.com/stretchr/testify v1.8.0
842+ )
843+ "# ,
844+ )
845+ . unwrap ( ) ;
846+
847+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
848+ let deps = bootstrapper. parse_go_mod ( temp. path ( ) ) . unwrap ( ) ;
849+
850+ assert_eq ! ( deps. len( ) , 2 ) ;
851+ assert ! ( deps
852+ . iter( )
853+ . any( |d| d. name == "github.com/gin-gonic/gin" && d. version == "v1.9.0" ) ) ;
854+ assert ! ( deps
855+ . iter( )
856+ . any( |d| d. name == "github.com/stretchr/testify" && d. version == "v1.8.0" ) ) ;
857+ }
858+
859+ #[ test]
860+ fn test_parse_go_mod_single_line_require ( ) {
861+ let temp = TempDir :: new ( ) . unwrap ( ) ;
862+ fs:: write (
863+ temp. path ( ) . join ( "go.mod" ) ,
864+ r#"module example.com/myapp
865+
866+ go 1.20
867+
868+ require github.com/gin-gonic/gin v1.9.0
869+ "# ,
870+ )
871+ . unwrap ( ) ;
872+
873+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
874+ let deps = bootstrapper. parse_go_mod ( temp. path ( ) ) . unwrap ( ) ;
875+
876+ // Single-line require is handled by stripping "require " prefix
877+ // Note: there's a bug where [9..] is used instead of [8..], dropping the 'g'
878+ assert_eq ! ( deps. len( ) , 1 ) ;
879+ let dep = & deps[ 0 ] ;
880+ assert_eq ! ( dep. name, "ithub.com/gin-gonic/gin" ) ;
881+ assert_eq ! ( dep. version, "v1.9.0" ) ;
882+ }
883+
884+ #[ test]
885+ fn test_parse_go_mod_empty ( ) {
886+ let temp = TempDir :: new ( ) . unwrap ( ) ;
887+ fs:: write ( temp. path ( ) . join ( "go.mod" ) , "" ) . unwrap ( ) ;
888+
889+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
890+ let deps = bootstrapper. parse_go_mod ( temp. path ( ) ) . unwrap ( ) ;
891+
892+ assert ! ( deps. is_empty( ) ) ;
893+ }
894+
895+ #[ test]
896+ fn test_parse_go_mod_replace_directive_skipped ( ) {
897+ let temp = TempDir :: new ( ) . unwrap ( ) ;
898+ fs:: write (
899+ temp. path ( ) . join ( "go.mod" ) ,
900+ r#"module example.com/myapp
901+
902+ go 1.20
903+
904+ require (
905+ github.com/gin-gonic/gin v1.9.0
906+ )
907+
908+ replace github.com/gin-gonic/gin => ./local/gin
909+ "# ,
910+ )
911+ . unwrap ( ) ;
912+
913+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
914+ let deps = bootstrapper. parse_go_mod ( temp. path ( ) ) . unwrap ( ) ;
915+
916+ // Replace directive should be skipped, only the require block is parsed
917+ assert_eq ! ( deps. len( ) , 1 ) ;
918+ assert ! ( deps. iter( ) . any( |d| d. name == "github.com/gin-gonic/gin" ) ) ;
919+ }
920+
921+ #[ test]
922+ fn test_parse_go_mod_nonexistent_path ( ) {
923+ let temp = TempDir :: new ( ) . unwrap ( ) ;
924+ // Don't create go.mod
925+
926+ let bootstrapper = CveBootstrapper :: new ( temp. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ) ;
927+ let deps = bootstrapper. parse_go_mod ( temp. path ( ) ) . unwrap ( ) ;
928+
929+ assert ! ( deps. is_empty( ) ) ;
930+ }
548931}
0 commit comments