github.com/google/osv-scalibr@v0.4.1/enricher/vulnmatch/osvdev/osvdev_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package osvdev_test
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"github.com/google/osv-scalibr/enricher"
    26  	"github.com/google/osv-scalibr/enricher/vulnmatch/osvdev"
    27  	"github.com/google/osv-scalibr/enricher/vulnmatch/osvdev/fakeclient"
    28  	"github.com/google/osv-scalibr/extractor"
    29  	"github.com/google/osv-scalibr/inventory"
    30  	"github.com/google/osv-scalibr/inventory/vex"
    31  	"github.com/google/osv-scalibr/purl"
    32  	osvpb "github.com/ossf/osv-schema/bindings/go/osvschema"
    33  	"google.golang.org/protobuf/testing/protocmp"
    34  	structpb "google.golang.org/protobuf/types/known/structpb"
    35  	"google.golang.org/protobuf/types/known/timestamppb"
    36  )
    37  
    38  func TestEnrich(t *testing.T) {
    39  	cancelledContext, cancel := context.WithCancel(context.Background())
    40  	cancel()
    41  
    42  	var (
    43  		jsPkg      = &extractor.Package{Name: "express", Version: "4.17.1", PURLType: purl.TypeNPM}
    44  		goPkg      = &extractor.Package{Name: "github.com/gin-gonic/gin", Version: "1.8.1", PURLType: purl.TypeGolang}
    45  		fzfPkg     = &extractor.Package{Name: "fzf", Version: "0.63.0", PURLType: purl.TypeBrew}
    46  		pyPkg      = &extractor.Package{Name: "requests", Version: "1.63.0", PURLType: purl.TypePyPi}
    47  		unknownPkg = &extractor.Package{Name: "unknown", PURLType: purl.TypeGolang}
    48  
    49  		goPkgWithSignals = &extractor.Package{
    50  			Name:     "github.com/gin-gonic/gin",
    51  			Version:  "1.8.1",
    52  			PURLType: purl.TypeGolang,
    53  			ExploitabilitySignals: []*vex.PackageExploitabilitySignal{
    54  				{
    55  					Plugin: "annotator/example", VulnIdentifiers: []string{"GHSA-2c4m-59x9-fr2g"},
    56  				},
    57  			}}
    58  	)
    59  
    60  	var (
    61  		goVuln1 = osvpb.Vulnerability{
    62  			SchemaVersion: "1.7.0",
    63  			Id:            "GHSA-2c4m-59x9-fr2g",
    64  			Modified:      timestamppb.New(time.Date(2023, 11, 8, 4, 12, 18, 674169000, time.UTC)),
    65  			Published:     timestamppb.New(time.Date(2023, 5, 12, 20, 19, 25, 0, time.UTC)),
    66  			Aliases:       []string{"CVE-2023-29401", "GO-2023-1737"},
    67  			Summary:       "Gin Web Framework does not properly sanitize filename parameter ...",
    68  			Details:       "The filename parameter of the Context.FileAttachment function is ...",
    69  			Severity:      []*osvpb.Severity{{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N"}},
    70  			References: []*osvpb.Reference{
    71  				{Type: osvpb.Reference_ADVISORY, Url: "https://nvd.nist.gov/vuln/detail/CVE-2023-29401"},
    72  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/issues/3555"},
    73  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/pull/3556"},
    74  				{Type: osvpb.Reference_PACKAGE, Url: "https://github.com/gin-gonic/gin"},
    75  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/releases/tag/v1.9.1"},
    76  				{Type: osvpb.Reference_WEB, Url: "https://pkg.go.dev/vuln/GO-2023-1737"},
    77  			},
    78  			DatabaseSpecific: &structpb.Struct{
    79  				Fields: map[string]*structpb.Value{
    80  					"cwe_ids": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
    81  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-494"}},
    82  					}}}},
    83  					"github_reviewed":    {Kind: &structpb.Value_BoolValue{BoolValue: true}},
    84  					"github_reviewed_at": {Kind: &structpb.Value_StringValue{StringValue: "2023-05-12T20:19:25Z"}},
    85  					"nvd_published_at":   {Kind: &structpb.Value_StringValue{StringValue: "2023-06-08T21:15:16Z"}},
    86  					"severity":           {Kind: &structpb.Value_StringValue{StringValue: "MODERATE"}},
    87  				},
    88  			},
    89  			Affected: []*osvpb.Affected{
    90  				{
    91  					Package: &osvpb.Package{
    92  						Ecosystem: goPkg.Ecosystem().String(),
    93  						Name:      goPkg.Name,
    94  						Purl:      "pkg:golang/github.com/gin-gonic/gin",
    95  					},
    96  					Ranges: []*osvpb.Range{
    97  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "1.3.1-0.20190301021747-ccb9e902956d"}, {Fixed: "1.9.1"}}},
    98  					},
    99  					DatabaseSpecific: &structpb.Struct{
   100  						Fields: map[string]*structpb.Value{
   101  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2023/05/GHSA-2c4m-59x9-fr2g/GHSA-2c4m-59x9-fr2g.json"}},
   102  						},
   103  					},
   104  				},
   105  			},
   106  		}
   107  
   108  		goVuln2 = osvpb.Vulnerability{
   109  			SchemaVersion: "1.7.0",
   110  			Id:            "GHSA-3vp4-m3rf-835h",
   111  			Aliases:       []string{"CVE-2023-26125"},
   112  			Summary:       "Improper input validation in github.com/gin-gonic/gin",
   113  			Details:       "Versions of the package github.com/gin-gonic/gin before version ...",
   114  			Severity:      []*osvpb.Severity{{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"}},
   115  			Modified:      timestamppb.New(time.Date(2023, 11, 8, 4, 11, 58, 943766000, time.UTC)),
   116  			Published:     timestamppb.New(time.Date(2023, 5, 4, 6, 30, 12, 0, time.UTC)),
   117  			Affected: []*osvpb.Affected{
   118  				{
   119  					Package: &osvpb.Package{
   120  						Ecosystem: goPkg.Ecosystem().String(),
   121  						Name:      goPkg.Name,
   122  						Purl:      "pkg:golang/github.com/gin-gonic/gin",
   123  					},
   124  					Ranges: []*osvpb.Range{
   125  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "0"}, {Fixed: "1.9.0"}}},
   126  					},
   127  					DatabaseSpecific: &structpb.Struct{
   128  						Fields: map[string]*structpb.Value{
   129  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2023/05/GHSA-3vp4-m3rf-835h/GHSA-3vp4-m3rf-835h.json"}},
   130  						},
   131  					},
   132  				},
   133  			},
   134  			References: []*osvpb.Reference{
   135  				{Type: osvpb.Reference_ADVISORY, Url: "https://nvd.nist.gov/vuln/detail/CVE-2023-26125"},
   136  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/pull/3500"},
   137  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/pull/3503"},
   138  				{Type: osvpb.Reference_WEB, Url: "https://github.com/t0rchwo0d/gin/commit/fd9f98e70fb4107ee68c783482d231d35e60507b"},
   139  				{Type: osvpb.Reference_PACKAGE, Url: "https://github.com/gin-gonic/gin"},
   140  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/releases/tag/v1.9.0"},
   141  				{Type: osvpb.Reference_WEB, Url: "https://security.snyk.io/vuln/SNYK-GOLANG-GITHUBCOMGINGONICGIN-3324285"},
   142  			},
   143  			DatabaseSpecific: &structpb.Struct{
   144  				Fields: map[string]*structpb.Value{
   145  					"cwe_ids": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
   146  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-20"}},
   147  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-77"}},
   148  					}}}},
   149  					"github_reviewed":    {Kind: &structpb.Value_BoolValue{BoolValue: true}},
   150  					"github_reviewed_at": {Kind: &structpb.Value_StringValue{StringValue: "2023-05-05T02:20:00Z"}},
   151  					"nvd_published_at":   {Kind: &structpb.Value_StringValue{StringValue: "2023-05-04T05:15:09Z"}},
   152  					"severity":           {Kind: &structpb.Value_StringValue{StringValue: "MODERATE"}},
   153  				},
   154  			},
   155  		}
   156  
   157  		goVuln3 = osvpb.Vulnerability{
   158  			SchemaVersion: "1.7.0",
   159  			Id:            "GO-2023-1737",
   160  			Aliases:       []string{"CVE-2023-29401", "GHSA-2c4m-59x9-fr2g"},
   161  			Summary:       "Improper handling of filenames in Content-Disposition HTTP heade...",
   162  			Details:       "The filename parameter of the Context.FileAttachment function is ...",
   163  			DatabaseSpecific: &structpb.Struct{
   164  				Fields: map[string]*structpb.Value{
   165  					"review_status": {Kind: &structpb.Value_StringValue{StringValue: "REVIEWED"}},
   166  					"url":           {Kind: &structpb.Value_StringValue{StringValue: "https://pkg.go.dev/vuln/GO-2023-1737"}},
   167  				},
   168  			},
   169  			Affected: []*osvpb.Affected{
   170  				{
   171  					Package: &osvpb.Package{
   172  						Ecosystem: goPkg.Ecosystem().String(),
   173  						Name:      goPkg.Name,
   174  						Purl:      "pkg:golang/github.com/gin-gonic/gin",
   175  					},
   176  					Ranges: []*osvpb.Range{
   177  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "1.3.1-0.20190301021747-ccb9e902956d"}, {Fixed: "1.9.1"}}},
   178  					},
   179  					DatabaseSpecific: &structpb.Struct{
   180  						Fields: map[string]*structpb.Value{
   181  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://vuln.go.dev/ID/GO-2023-1737.json"}},
   182  						},
   183  					},
   184  					EcosystemSpecific: &structpb.Struct{
   185  						Fields: map[string]*structpb.Value{
   186  							"imports": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
   187  								Values: []*structpb.Value{
   188  									{Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{
   189  										Fields: map[string]*structpb.Value{
   190  											"path": {Kind: &structpb.Value_StringValue{StringValue: "github.com/gin-gonic/gin"}},
   191  											"symbols": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{
   192  												Values: []*structpb.Value{
   193  													{Kind: &structpb.Value_StringValue{StringValue: "Context.FileAttachment"}},
   194  												},
   195  											}}},
   196  										},
   197  									}}},
   198  								},
   199  							}}},
   200  						},
   201  					},
   202  				},
   203  			},
   204  			Modified:  timestamppb.New(time.Date(2024, 5, 20, 16, 3, 47, 0, time.UTC)),
   205  			Published: timestamppb.New(time.Date(2023, 5, 11, 18, 59, 56, 0, time.UTC)),
   206  			Credits:   []*osvpb.Credit{{Name: "motoyasu-saburi"}},
   207  			References: []*osvpb.Reference{
   208  				{Type: osvpb.Reference_REPORT, Url: "https://github.com/gin-gonic/gin/issues/3555"},
   209  				{Type: osvpb.Reference_FIX, Url: "https://github.com/gin-gonic/gin/pull/3556"},
   210  				{Type: osvpb.Reference_WEB, Url: "https://github.com/gin-gonic/gin/releases/tag/v1.9.1"},
   211  			},
   212  		}
   213  
   214  		jsVuln1 = osvpb.Vulnerability{
   215  			SchemaVersion: "1.7.0",
   216  			Id:            "GHSA-qw6h-vgh9-j6wx",
   217  			Modified:      timestamppb.New(time.Date(2024, 11, 18, 16, 27, 11, 0, time.UTC)),
   218  			Published:     timestamppb.New(time.Date(2024, 9, 10, 19, 41, 4, 0, time.UTC)),
   219  			Aliases:       []string{"CVE-2024-43796"},
   220  			Related:       []string{"CGA-7rmh-796c-qmq8", "CGA-8w92-879x-f9wc", "CGA-jq8v-jx6x-3fpc"},
   221  			Summary:       "express vulnerable to XSS via response.redirect()",
   222  			Details:       "In express <4.20.0, passing untrusted user input ...",
   223  			Severity: []*osvpb.Severity{
   224  				{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"},
   225  				{Type: osvpb.Severity_CVSS_V4, Score: "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:L"},
   226  			},
   227  			Affected: []*osvpb.Affected{
   228  				{
   229  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   230  					Ranges: []*osvpb.Range{
   231  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "0"}, {Fixed: "4.20.0"}}},
   232  					},
   233  					DatabaseSpecific: &structpb.Struct{
   234  						Fields: map[string]*structpb.Value{
   235  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/09/GHSA-qw6h-vgh9-j6wx/GHSA-qw6h-vgh9-j6wx.json"}},
   236  						},
   237  					},
   238  				},
   239  				{
   240  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   241  					Ranges: []*osvpb.Range{
   242  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "5.0.0-alpha.1"}, {Fixed: "5.0.0"}}},
   243  					},
   244  					DatabaseSpecific: &structpb.Struct{
   245  						Fields: map[string]*structpb.Value{
   246  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/09/GHSA-qw6h-vgh9-j6wx/GHSA-qw6h-vgh9-j6wx.json"}},
   247  						},
   248  					},
   249  				},
   250  			},
   251  			References: []*osvpb.Reference{
   252  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/security/advisories/GHSA-qw6h-vgh9-j6wx"},
   253  				{Type: osvpb.Reference_ADVISORY, Url: "https://nvd.nist.gov/vuln/detail/CVE-2024-43796"},
   254  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/commit/54271f69b511fea198471e6ff3400ab805d6b553"},
   255  				{Type: osvpb.Reference_PACKAGE, Url: "https://github.com/expressjs/express"},
   256  			},
   257  			DatabaseSpecific: &structpb.Struct{
   258  				Fields: map[string]*structpb.Value{
   259  					"cwe_ids": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
   260  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-79"}},
   261  					}}}},
   262  					"github_reviewed":    {Kind: &structpb.Value_BoolValue{BoolValue: true}},
   263  					"github_reviewed_at": {Kind: &structpb.Value_StringValue{StringValue: "2024-09-10T19:41:04Z"}},
   264  					"nvd_published_at":   {Kind: &structpb.Value_StringValue{StringValue: "2024-09-10T15:15:17Z"}},
   265  					"severity":           {Kind: &structpb.Value_StringValue{StringValue: "LOW"}},
   266  				},
   267  			},
   268  		}
   269  
   270  		jsVuln1Local = osvpb.Vulnerability{
   271  			SchemaVersion: "1.7.0",
   272  			Id:            "GHSA-qw6h-vgh9-j6wx",
   273  			Modified:      timestamppb.New(time.Date(2024, 11, 18, 16, 27, 11, 0, time.UTC)),
   274  			Published:     timestamppb.New(time.Date(2024, 9, 10, 19, 41, 4, 0, time.UTC)),
   275  			Aliases:       []string{"CVE-2024-43796"},
   276  			Related:       []string{"CGA-7rmh-796c-qmq8", "CGA-8w92-879x-f9wc", "CGA-jq8v-jx6x-3fpc"},
   277  			Summary:       "express vulnerable to XSS via response.redirect()",
   278  			Details:       "In express <4.20.0, passing untrusted user input ...",
   279  			Severity: []*osvpb.Severity{
   280  				{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L"},
   281  			},
   282  			Affected: []*osvpb.Affected{
   283  				{
   284  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   285  					Ranges: []*osvpb.Range{
   286  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "0"}, {Fixed: "4.20.0"}}},
   287  					},
   288  					DatabaseSpecific: &structpb.Struct{
   289  						Fields: map[string]*structpb.Value{
   290  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/09/GHSA-qw6h-vgh9-j6wx/GHSA-qw6h-vgh9-j6wx.json"}},
   291  						},
   292  					},
   293  				},
   294  				{
   295  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   296  					Ranges: []*osvpb.Range{
   297  						{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "5.0.0-alpha.1"}, {Fixed: "5.0.0"}}},
   298  					},
   299  					DatabaseSpecific: &structpb.Struct{
   300  						Fields: map[string]*structpb.Value{
   301  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/09/GHSA-qw6h-vgh9-j6wx/GHSA-qw6h-vgh9-j6wx.json"}},
   302  						},
   303  					},
   304  				},
   305  			},
   306  		}
   307  
   308  		jsVuln2 = osvpb.Vulnerability{
   309  			SchemaVersion: "1.7.0",
   310  			Id:            "GHSA-rv95-896h-c2vc",
   311  			Modified:      timestamppb.New(time.Date(2025, 7, 21, 16, 57, 31, 0, time.UTC)),
   312  			Published:     timestamppb.New(time.Date(2024, 3, 25, 19, 40, 26, 0, time.UTC)),
   313  			Withdrawn:     timestamppb.New(time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)),
   314  			Aliases:       []string{"CVE-2024-29041"},
   315  			Related:       []string{"CGA-5389-98xc-vr78", "CGA-qg2p-wmx3-mx9q", "CGA-rjrm-49wc-v48x", "CGA-w26h-h47r-f6rx", "CVE-2024-29041"},
   316  			Summary:       "Express.js Open Redirect in malformed URLs",
   317  			Details:       "Versions of Express.js prior to 4.19.2 ...",
   318  			Severity:      []*osvpb.Severity{{Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}},
   319  			Affected: []*osvpb.Affected{
   320  				{
   321  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   322  					Ranges:  []*osvpb.Range{{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "0"}, {Fixed: "4.19.2"}}}},
   323  					DatabaseSpecific: &structpb.Struct{
   324  						Fields: map[string]*structpb.Value{
   325  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/03/GHSA-rv95-896h-c2vc/GHSA-rv95-896h-c2vc.json"}},
   326  						},
   327  					},
   328  				},
   329  				{
   330  					Package: &osvpb.Package{Ecosystem: "npm", Name: "express", Purl: "pkg:npm/express"},
   331  					Ranges:  []*osvpb.Range{{Type: osvpb.Range_SEMVER, Events: []*osvpb.Event{{Introduced: "5.0.0-alpha.1"}, {Fixed: "5.0.0-beta.3"}}}},
   332  					DatabaseSpecific: &structpb.Struct{
   333  						Fields: map[string]*structpb.Value{
   334  							"source": {Kind: &structpb.Value_StringValue{StringValue: "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/03/GHSA-rv95-896h-c2vc/GHSA-rv95-896h-c2vc.json"}},
   335  						},
   336  					}},
   337  			},
   338  			References: []*osvpb.Reference{
   339  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc"},
   340  				{Type: osvpb.Reference_ADVISORY, Url: "https://nvd.nist.gov/vuln/detail/CVE-2024-29041"},
   341  				{Type: osvpb.Reference_WEB, Url: "https://github.com/koajs/koa/issues/1800"},
   342  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/pull/5539"},
   343  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd"},
   344  				{Type: osvpb.Reference_WEB, Url: "https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94"},
   345  				{Type: osvpb.Reference_WEB, Url: "https://expressjs.com/en/4x/api.html#res.location"},
   346  				{Type: osvpb.Reference_PACKAGE, Url: "https://github.com/expressjs/express"},
   347  			},
   348  			DatabaseSpecific: &structpb.Struct{
   349  				Fields: map[string]*structpb.Value{
   350  					"cwe_ids": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
   351  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-1286"}},
   352  						{Kind: &structpb.Value_StringValue{StringValue: "CWE-601"}},
   353  					}}}},
   354  					"github_reviewed":    {Kind: &structpb.Value_BoolValue{BoolValue: true}},
   355  					"github_reviewed_at": {Kind: &structpb.Value_StringValue{StringValue: "2024-03-25T19:40:26Z"}},
   356  					"nvd_published_at":   {Kind: &structpb.Value_StringValue{StringValue: "2024-03-25T21:15:46Z"}},
   357  					"severity":           {Kind: &structpb.Value_StringValue{StringValue: "MODERATE"}},
   358  				},
   359  			},
   360  		}
   361  
   362  		fzfVulnLocal = osvpb.Vulnerability{
   363  			Id: "mockID",
   364  			Affected: inventory.PackageToAffected(fzfPkg, "3002.1", &osvpb.Severity{
   365  				Type:  osvpb.Severity_CVSS_V3,
   366  				Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
   367  			}),
   368  		}
   369  
   370  		pyPkgSameVulnAsFzf = osvpb.Vulnerability{
   371  			Id: "mockID",
   372  			Affected: inventory.PackageToAffected(pyPkg, "3.002.1", &osvpb.Severity{
   373  				Type:  osvpb.Severity_CVSS_V3,
   374  				Score: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
   375  			}),
   376  		}
   377  	)
   378  
   379  	client := fakeclient.New(map[string][]*osvpb.Vulnerability{
   380  		fmt.Sprintf("%s:%s:", goPkg.Name, goPkg.Version): {&goVuln1, &goVuln2, &goVuln3},
   381  		fmt.Sprintf("%s:%s:", jsPkg.Name, jsPkg.Version): {&jsVuln1, &jsVuln2},
   382  		fmt.Sprintf("%s:%s:", pyPkg.Name, pyPkg.Version): {&pyPkgSameVulnAsFzf},
   383  	})
   384  
   385  	tests := []struct {
   386  		name         string
   387  		packageVulns []*inventory.PackageVuln
   388  		packages     []*extractor.Package
   389  		//nolint:containedctx
   390  		ctx                 context.Context
   391  		wantErr             error
   392  		initialQueryTimeout time.Duration
   393  		wantPackageVulns    []*inventory.PackageVuln
   394  	}{
   395  		{
   396  			name:             "ctx_cancelled",
   397  			ctx:              cancelledContext,
   398  			packages:         []*extractor.Package{jsPkg, goPkg},
   399  			wantPackageVulns: []*inventory.PackageVuln{},
   400  			wantErr:          cmpopts.AnyError,
   401  		},
   402  		{
   403  			name:                "initial_query_timeout",
   404  			initialQueryTimeout: -1 * time.Second,
   405  			packages:            []*extractor.Package{jsPkg, goPkg},
   406  			wantPackageVulns:    []*inventory.PackageVuln{},
   407  			wantErr:             osvdev.ErrInitialQueryTimeout,
   408  		},
   409  		{
   410  			name:     "simple_test",
   411  			packages: []*extractor.Package{goPkg},
   412  			wantPackageVulns: []*inventory.PackageVuln{
   413  				{Vulnerability: &goVuln1, Package: goPkg, Plugins: []string{osvdev.Name}},
   414  				{Vulnerability: &goVuln2, Package: goPkg, Plugins: []string{osvdev.Name}},
   415  				{Vulnerability: &goVuln3, Package: goPkg, Plugins: []string{osvdev.Name}},
   416  			},
   417  		},
   418  		{
   419  			name:             "not_covered_purl_type",
   420  			packages:         []*extractor.Package{fzfPkg},
   421  			wantPackageVulns: []*inventory.PackageVuln{},
   422  		},
   423  		{
   424  			name:             "unknown_package",
   425  			packages:         []*extractor.Package{unknownPkg},
   426  			wantPackageVulns: []*inventory.PackageVuln{},
   427  		},
   428  		{
   429  			name:     "interleaving_covered_not_covered",
   430  			packages: []*extractor.Package{goPkg, fzfPkg, jsPkg},
   431  			wantPackageVulns: []*inventory.PackageVuln{
   432  				{Vulnerability: &goVuln1, Package: goPkg, Plugins: []string{osvdev.Name}},
   433  				{Vulnerability: &goVuln2, Package: goPkg, Plugins: []string{osvdev.Name}},
   434  				{Vulnerability: &goVuln3, Package: goPkg, Plugins: []string{osvdev.Name}},
   435  				{Vulnerability: &jsVuln1, Package: jsPkg, Plugins: []string{osvdev.Name}},
   436  				{Vulnerability: &jsVuln2, Package: jsPkg, Plugins: []string{osvdev.Name}},
   437  			},
   438  		},
   439  		{
   440  			name: "not_empty_local_inventory_vulns",
   441  			packageVulns: []*inventory.PackageVuln{
   442  				{Vulnerability: &fzfVulnLocal, Package: fzfPkg, Plugins: []string{"mock/plugin"}},
   443  			},
   444  			packages: []*extractor.Package{fzfPkg, jsPkg},
   445  			wantPackageVulns: []*inventory.PackageVuln{
   446  				{Vulnerability: &fzfVulnLocal, Package: fzfPkg, Plugins: []string{"mock/plugin"}},
   447  				{Vulnerability: &jsVuln1, Package: jsPkg, Plugins: []string{osvdev.Name}},
   448  				{Vulnerability: &jsVuln2, Package: jsPkg, Plugins: []string{osvdev.Name}},
   449  			},
   450  		},
   451  		{
   452  			name: "one_local_one_remote__same_pkg_same_cve",
   453  			packageVulns: []*inventory.PackageVuln{
   454  				{Vulnerability: &jsVuln1Local, Package: jsPkg, Plugins: []string{"mock/plugin"}},
   455  			},
   456  			packages: []*extractor.Package{jsPkg},
   457  			wantPackageVulns: []*inventory.PackageVuln{
   458  				{Vulnerability: &jsVuln1, Package: jsPkg, Plugins: []string{osvdev.Name, "mock/plugin"}},
   459  				{Vulnerability: &jsVuln2, Package: jsPkg, Plugins: []string{osvdev.Name}},
   460  			},
   461  		},
   462  		{
   463  			name: "one_local_one_remote__different_pkg_same_cve",
   464  			packageVulns: []*inventory.PackageVuln{
   465  				{Vulnerability: &fzfVulnLocal, Package: fzfPkg, Plugins: []string{"mock/plugin"}},
   466  			},
   467  			packages: []*extractor.Package{fzfPkg, pyPkg},
   468  			wantPackageVulns: []*inventory.PackageVuln{
   469  				{Vulnerability: &fzfVulnLocal, Package: fzfPkg, Plugins: []string{"mock/plugin"}},
   470  				{Vulnerability: &pyPkgSameVulnAsFzf, Package: pyPkg, Plugins: []string{osvdev.Name}},
   471  			},
   472  		},
   473  		{
   474  			name:     "exploitability_signals",
   475  			packages: []*extractor.Package{goPkgWithSignals},
   476  			wantPackageVulns: []*inventory.PackageVuln{
   477  				{
   478  					Vulnerability:         &goVuln1,
   479  					Package:               goPkgWithSignals,
   480  					Plugins:               []string{osvdev.Name},
   481  					ExploitabilitySignals: []*vex.FindingExploitabilitySignal{{Plugin: "annotator/example", Justification: vex.Unspecified}},
   482  				},
   483  				{Vulnerability: &goVuln2, Package: goPkgWithSignals, Plugins: []string{osvdev.Name}},
   484  				{Vulnerability: &goVuln3, Package: goPkgWithSignals, Plugins: []string{osvdev.Name}},
   485  			}},
   486  	}
   487  
   488  	for _, tt := range tests {
   489  		t.Run(tt.name, func(t *testing.T) {
   490  			if tt.ctx == nil {
   491  				tt.ctx = context.Background()
   492  			}
   493  
   494  			e := osvdev.NewWithClient(client, tt.initialQueryTimeout)
   495  
   496  			var input *enricher.ScanInput
   497  
   498  			if tt.packageVulns == nil {
   499  				tt.packageVulns = []*inventory.PackageVuln{}
   500  			}
   501  
   502  			inv := &inventory.Inventory{
   503  				PackageVulns: tt.packageVulns,
   504  				Packages:     tt.packages,
   505  			}
   506  
   507  			err := e.Enrich(tt.ctx, input, inv)
   508  			if !cmp.Equal(tt.wantErr, err, cmpopts.EquateErrors()) {
   509  				t.Fatalf("Enrich(%v) error: %v, want %v", tt.packages, err, tt.wantErr)
   510  			}
   511  
   512  			want := &inventory.Inventory{
   513  				PackageVulns: tt.wantPackageVulns,
   514  				Packages:     tt.packages,
   515  			}
   516  
   517  			sortPkgVulns := cmpopts.SortSlices(func(a, b *inventory.PackageVuln) bool {
   518  				if a.Vulnerability.Id != b.Vulnerability.Id {
   519  					return a.Vulnerability.Id < b.Vulnerability.Id
   520  				}
   521  				return a.Package.Name < b.Package.Name
   522  			})
   523  
   524  			diff := cmp.Diff(
   525  				want, inv,
   526  				sortPkgVulns,
   527  				protocmp.Transform(),
   528  				cmpopts.SortSlices(func(a, b string) bool { return a < b }),
   529  			)
   530  
   531  			if diff != "" {
   532  				t.Errorf("Enrich(%v): unexpected diff (-want +got): %v", tt.packages, diff)
   533  			}
   534  		})
   535  	}
   536  }