@@ -341,7 +341,8 @@ def test_scan_sarif_output_format
341341 # Validate against SARIF 2.1.0 schema
342342 require "json_schemer"
343343 schema_path = File . join ( File . dirname ( __FILE__ ) , "../../fixtures/sarif-schema-2.1.0.json" )
344- schema = JSONSchemer . schema ( Pathname . new ( schema_path ) )
344+ schema_content = JSON . parse ( File . read ( schema_path ) )
345+ schema = JSONSchemer . schema ( schema_content , ref_resolver : proc { |uri | schema_content } )
345346 errors = schema . validate ( sarif ) . to_a
346347 assert_empty errors , "SARIF schema validation failed: #{ errors . map { |e | e [ "error" ] } . join ( ", " ) } "
347348 ensure
@@ -549,3 +550,140 @@ def test_lockfile_dependencies_filter
549550 assert_equal "7.0.0" , result . first [ :requirement ]
550551 end
551552end
553+
554+ class Git ::Pkgs ::TestVulnsHistory < Minitest ::Test
555+ include TestHelpers
556+
557+ def setup
558+ Git ::Pkgs ::Database . disconnect
559+ create_test_repo
560+
561+ add_file ( "package.json" , '{"dependencies": {"lodash": "4.17.0"}}' )
562+ commit ( "Add lodash" )
563+
564+ @git_dir = File . join ( @test_dir , ".git" )
565+ Git ::Pkgs ::Database . connect ( @git_dir )
566+ Git ::Pkgs ::Database . create_schema
567+
568+ Git ::Pkgs . git_dir = @git_dir
569+ capture_stdout { Git ::Pkgs ::Commands ::Init . new ( [ "--no-hooks" , "--force" ] ) . run }
570+
571+ # Mark package as synced to avoid OSV API calls
572+ Git ::Pkgs ::Models ::Package . create (
573+ purl : "pkg:npm/lodash" ,
574+ ecosystem : "npm" ,
575+ name : "lodash" ,
576+ vulns_synced_at : Time . now
577+ )
578+ end
579+
580+ def teardown
581+ Git ::Pkgs . git_dir = nil
582+ cleanup_test_repo
583+ end
584+
585+ def capture_stdout
586+ original = $stdout
587+ $stdout = StringIO . new
588+ yield
589+ $stdout. string
590+ ensure
591+ $stdout = original
592+ end
593+
594+ def test_history_shows_withdrawn_vulns_in_timeline
595+ # Create an active vulnerability
596+ active_vuln = Git ::Pkgs ::Models ::Vulnerability . create (
597+ id : "GHSA-active" ,
598+ summary : "Active vulnerability" ,
599+ severity : "high" ,
600+ published_at : Time . now - 86400 ,
601+ fetched_at : Time . now
602+ )
603+ Git ::Pkgs ::Models ::VulnerabilityPackage . create (
604+ vulnerability_id : active_vuln . id ,
605+ ecosystem : "npm" ,
606+ package_name : "lodash" ,
607+ vulnerable_range : "< 4.17.21"
608+ )
609+
610+ # Create a withdrawn vulnerability
611+ withdrawn_vuln = Git ::Pkgs ::Models ::Vulnerability . create (
612+ id : "GHSA-withdrawn" ,
613+ summary : "Withdrawn vulnerability" ,
614+ severity : "medium" ,
615+ published_at : Time . now - 172800 ,
616+ withdrawn_at : Time . now - 86400 ,
617+ fetched_at : Time . now
618+ )
619+ Git ::Pkgs ::Models ::VulnerabilityPackage . create (
620+ vulnerability_id : withdrawn_vuln . id ,
621+ ecosystem : "npm" ,
622+ package_name : "lodash" ,
623+ vulnerable_range : "< 4.17.21"
624+ )
625+
626+ output = capture_stdout do
627+ Git ::Pkgs ::Commands ::Vulns ::History . new ( [ "lodash" ] ) . run
628+ end
629+
630+ assert_includes output , "GHSA-active"
631+ assert_includes output , "GHSA-withdrawn"
632+ assert_includes output , "withdrawn"
633+ end
634+
635+ def test_history_json_includes_withdrawn_vulns
636+ withdrawn_vuln = Git ::Pkgs ::Models ::Vulnerability . create (
637+ id : "GHSA-withdrawn-json" ,
638+ summary : "Withdrawn vulnerability" ,
639+ severity : "low" ,
640+ published_at : Time . now - 172800 ,
641+ withdrawn_at : Time . now - 86400 ,
642+ fetched_at : Time . now
643+ )
644+ Git ::Pkgs ::Models ::VulnerabilityPackage . create (
645+ vulnerability_id : withdrawn_vuln . id ,
646+ ecosystem : "npm" ,
647+ package_name : "lodash" ,
648+ vulnerable_range : "< 4.17.21"
649+ )
650+
651+ output = capture_stdout do
652+ Git ::Pkgs ::Commands ::Vulns ::History . new ( [ "lodash" , "-f" , "json" ] ) . run
653+ end
654+
655+ json = JSON . parse ( output )
656+ assert_equal "lodash" , json [ "package" ]
657+
658+ events = json [ "timeline" ]
659+ withdrawn_events = events . select { |e | e [ "event_type" ] == "cve_withdrawn" }
660+ assert withdrawn_events . any? , "Expected withdrawn event in timeline"
661+
662+ published_events = events . select { |e | e [ "description" ] &.include? ( "[withdrawn]" ) }
663+ assert published_events . any? , "Expected [withdrawn] annotation on published event"
664+ end
665+
666+ def test_history_shows_withdrawn_event_with_date
667+ withdrawn_time = Time . now - 86400
668+ withdrawn_vuln = Git ::Pkgs ::Models ::Vulnerability . create (
669+ id : "GHSA-with-date" ,
670+ summary : "Withdrawn with date" ,
671+ severity : "high" ,
672+ published_at : Time . now - 172800 ,
673+ withdrawn_at : withdrawn_time ,
674+ fetched_at : Time . now
675+ )
676+ Git ::Pkgs ::Models ::VulnerabilityPackage . create (
677+ vulnerability_id : withdrawn_vuln . id ,
678+ ecosystem : "npm" ,
679+ package_name : "lodash" ,
680+ vulnerable_range : "< 4.17.21"
681+ )
682+
683+ output = capture_stdout do
684+ Git ::Pkgs ::Commands ::Vulns ::History . new ( [ "lodash" ] ) . run
685+ end
686+
687+ assert_includes output , "GHSA-with-date withdrawn"
688+ end
689+ end
0 commit comments