@@ -595,6 +595,174 @@ describe('dependency-analysis', () => {
595595 expect ( result . deprecatedPackages [ 2 ] ?. depth ) . toBe ( 'transitive' )
596596 } )
597597
598+ it ( 'extracts correct fixedIn version for the current version range' , async ( ) => {
599+ const mockResolved = new Map ( [
600+ [
601+ 'minimist@1.0.0' ,
602+ {
603+ name : 'minimist' ,
604+ version : '1.0.0' ,
605+ size : 1000 ,
606+ optional : false ,
607+ depth : 'root' as const ,
608+ path : [ 'minimist@1.0.0' ] ,
609+ } ,
610+ ] ,
611+ ] )
612+ vi . mocked ( resolveDependencyTree ) . mockResolvedValue ( mockResolved )
613+
614+ // Mock OSV response with multiple affected ranges (like minimist)
615+ // Range 1: 0 - 0.2.1, Range 2: 1.0.0 - 1.2.3
616+ // Version 1.0.0 should match Range 2, so fixedIn should be 1.2.3
617+ mockOsvApi (
618+ [ { vulns : [ { id : 'GHSA-vh95-rmgr-6w4m' , modified : '2024-01-01' } ] } ] ,
619+ new Map ( [
620+ [
621+ 'minimist@1.0.0' ,
622+ {
623+ vulns : [
624+ {
625+ id : 'GHSA-vh95-rmgr-6w4m' ,
626+ summary : 'Prototype Pollution in minimist' ,
627+ database_specific : { severity : 'MODERATE' } ,
628+ affected : [
629+ {
630+ package : { ecosystem : 'npm' , name : 'minimist' } ,
631+ ranges : [
632+ {
633+ type : 'SEMVER' ,
634+ events : [ { introduced : '0' } , { fixed : '0.2.1' } ] ,
635+ } ,
636+ ] ,
637+ } ,
638+ {
639+ package : { ecosystem : 'npm' , name : 'minimist' } ,
640+ ranges : [
641+ {
642+ type : 'SEMVER' ,
643+ events : [ { introduced : '1.0.0' } , { fixed : '1.2.3' } ] ,
644+ } ,
645+ ] ,
646+ } ,
647+ ] ,
648+ } ,
649+ ] ,
650+ } ,
651+ ] ,
652+ ] ) ,
653+ )
654+
655+ const result = await analyzeDependencyTree ( 'minimist' , '1.0.0' )
656+
657+ expect ( result . vulnerablePackages ) . toHaveLength ( 1 )
658+ expect ( result . vulnerablePackages [ 0 ] ?. vulnerabilities [ 0 ] ?. fixedIn ) . toBe ( '1.2.3' )
659+ } )
660+
661+ it ( 'extracts correct fixedIn for prerelease versions (e.g., 16.0.0-beta.0)' , async ( ) => {
662+ const mockResolved = new Map ( [
663+ [
664+ 'next@16.0.0-beta.0' ,
665+ {
666+ name : 'next' ,
667+ version : '16.0.0-beta.0' ,
668+ size : 1000 ,
669+ optional : false ,
670+ depth : 'root' as const ,
671+ path : [ 'next@16.0.0-beta.0' ] ,
672+ } ,
673+ ] ,
674+ ] )
675+ vi . mocked ( resolveDependencyTree ) . mockResolvedValue ( mockResolved )
676+
677+ // Mock OSV response with multiple ranges including prerelease
678+ // Version 16.0.0-beta.0 should NOT match 13.0.0-15.0.8, but SHOULD match 16.0.0-beta.0-16.0.11
679+ mockOsvApi (
680+ [ { vulns : [ { id : 'GHSA-test' , modified : '2024-01-01' } ] } ] ,
681+ new Map ( [
682+ [
683+ 'next@16.0.0-beta.0' ,
684+ {
685+ vulns : [
686+ {
687+ id : 'GHSA-test' ,
688+ summary : 'Test vulnerability' ,
689+ database_specific : { severity : 'HIGH' } ,
690+ affected : [
691+ {
692+ package : { ecosystem : 'npm' , name : 'next' } ,
693+ ranges : [
694+ {
695+ type : 'SEMVER' ,
696+ events : [ { introduced : '13.0.0' } , { fixed : '15.0.8' } ] ,
697+ } ,
698+ ] ,
699+ } ,
700+ {
701+ package : { ecosystem : 'npm' , name : 'next' } ,
702+ ranges : [
703+ {
704+ type : 'SEMVER' ,
705+ events : [ { introduced : '16.0.0-beta.0' } , { fixed : '16.0.11' } ] ,
706+ } ,
707+ ] ,
708+ } ,
709+ ] ,
710+ } ,
711+ ] ,
712+ } ,
713+ ] ,
714+ ] ) ,
715+ )
716+
717+ const result = await analyzeDependencyTree ( 'next' , '16.0.0-beta.0' )
718+
719+ expect ( result . vulnerablePackages ) . toHaveLength ( 1 )
720+ // Should match the 16.x range, not the 13-15 range
721+ expect ( result . vulnerablePackages [ 0 ] ?. vulnerabilities [ 0 ] ?. fixedIn ) . toBe ( '16.0.11' )
722+ } )
723+
724+ it ( 'returns undefined fixedIn when no matching range has a fixed version' , async ( ) => {
725+ const mockResolved = new Map ( [
726+ [
727+ 'pkg@1.0.0' ,
728+ {
729+ name : 'pkg' ,
730+ version : '1.0.0' ,
731+ size : 1000 ,
732+ optional : false ,
733+ depth : 'root' as const ,
734+ path : [ 'pkg@1.0.0' ] ,
735+ } ,
736+ ] ,
737+ ] )
738+ vi . mocked ( resolveDependencyTree ) . mockResolvedValue ( mockResolved )
739+
740+ // Mock OSV response without affected data
741+ mockOsvApi (
742+ [ { vulns : [ { id : 'GHSA-no-fix' , modified : '2024-01-01' } ] } ] ,
743+ new Map ( [
744+ [
745+ 'pkg@1.0.0' ,
746+ {
747+ vulns : [
748+ {
749+ id : 'GHSA-no-fix' ,
750+ summary : 'Vuln without fix info' ,
751+ database_specific : { severity : 'LOW' } ,
752+ // No affected field
753+ } ,
754+ ] ,
755+ } ,
756+ ] ,
757+ ] ) ,
758+ )
759+
760+ const result = await analyzeDependencyTree ( 'pkg' , '1.0.0' )
761+
762+ expect ( result . vulnerablePackages ) . toHaveLength ( 1 )
763+ expect ( result . vulnerablePackages [ 0 ] ?. vulnerabilities [ 0 ] ?. fixedIn ) . toBeUndefined ( )
764+ } )
765+
598766 it ( 'returns both vulnerabilities and deprecated packages together' , async ( ) => {
599767 const mockResolved = new Map ( [
600768 [
0 commit comments