Repository: google/osv-scanner
Pull request #504 (2023-08-25 04:47:04)

Update renovate.json
Update renovate json to match new renovate config values. See https://github.com/google/osv.dev/pull/1586


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 286e316..b4e7b3d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -40,7 +40,7 @@ jobs:
 
     steps:
       - name: Checkout repository
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 014e8de..e2a3b34 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -21,7 +21,7 @@ jobs:
       DOCKER_CLI_EXPERIMENTAL: "enabled"
     steps:
       - name: Checkout
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           fetch-depth: 0
       - name: Set up Go
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index cf3723c..b511c4d 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -32,7 +32,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           persist-credentials: false
           fetch-depth: 0
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index e241782..9f38ec6 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -32,7 +32,7 @@ jobs:
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           persist-credentials: false
 
diff --git a/go.mod b/go.mod
index 909fbe7..0800cda 100644
--- a/go.mod
+++ b/go.mod
@@ -4,12 +4,12 @@ go 1.19
 
 require (
 	github.com/BurntSushi/toml v1.3.2
-	github.com/CycloneDX/cyclonedx-go v0.7.1
+	github.com/CycloneDX/cyclonedx-go v0.7.2
 	github.com/go-git/go-billy/v5 v5.4.1
 	github.com/go-git/go-git/v5 v5.8.1
 	github.com/goark/go-cvss v1.6.6
 	github.com/google/go-cmp v0.5.9
-	github.com/jedib0t/go-pretty/v6 v6.4.6
+	github.com/jedib0t/go-pretty/v6 v6.4.7
 	github.com/kr/pretty v0.3.1
 	github.com/owenrumney/go-sarif/v2 v2.2.0
 	github.com/package-url/packageurl-go v0.1.1
diff --git a/go.sum b/go.sum
index fdaaffe..b2764ad 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/CycloneDX/cyclonedx-go v0.7.1 h1:5w1SxjGm9MTMNTuRbEPyw21ObdbaagTWF/KfF0qHTRE=
-github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps=
+github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ=
+github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk=
 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
@@ -52,8 +52,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe
 github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw=
-github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
+github.com/jedib0t/go-pretty/v6 v6.4.7 h1:lwiTJr1DEkAgzljsUsORmWsVn5MQjt1BPJdPCtJ6KXE=
+github.com/jedib0t/go-pretty/v6 v6.4.7/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -109,12 +109,16 @@ github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
 github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
 github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
 github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
 github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
diff --git a/internal/local/zip.go b/internal/local/zip.go
index 6b19120..cdd5027 100644
--- a/internal/local/zip.go
+++ b/internal/local/zip.go
@@ -16,6 +16,7 @@ import (
 	"path"
 	"strings"
 
+	"github.com/google/osv-scanner/internal/utility/vulns"
 	"github.com/google/osv-scanner/pkg/lockfile"
 	"github.com/google/osv-scanner/pkg/models"
 	"github.com/google/osv-scanner/pkg/osv"
@@ -235,7 +236,7 @@ func (db *ZipDB) VulnerabilitiesAffectingPackage(pkg lockfile.PackageDetails) mo
 	var vulnerabilities models.Vulnerabilities
 
 	for _, vulnerability := range db.Vulnerabilities(false) {
-		if vulnerability.IsAffected(pkg) && !vulnerabilities.Includes(vulnerability) {
+		if vulns.IsAffected(vulnerability, pkg) && !vulns.Include(vulnerabilities, vulnerability) {
 			vulnerabilities = append(vulnerabilities, vulnerability)
 		}
 	}
diff --git a/internal/output/table.go b/internal/output/table.go
index 871f9d6..69030bc 100644
--- a/internal/output/table.go
+++ b/internal/output/table.go
@@ -13,12 +13,15 @@ import (
 	v3_metric "github.com/goark/go-cvss/v3/metric"
 
 	"github.com/google/osv-scanner/pkg/models"
-	"github.com/google/osv-scanner/pkg/osv"
 
 	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/jedib0t/go-pretty/v6/text"
 )
 
+// OSVBaseVulnerabilityURL is the base URL for detailed vulnerability views.
+// Copied in from osv package to avoid referencing the osv package unnecessarily
+const OSVBaseVulnerabilityURL = "https://osv.dev/"
+
 // PrintTableResults prints the osv scan results into a human friendly table.
 func PrintTableResults(vulnResult *models.VulnerabilityResults, outputWriter io.Writer, terminalWidth int) {
 	outputTable := table.NewWriter()
@@ -96,9 +99,9 @@ func tableBuilderInner(vulnResult *models.VulnerabilityResults, addStyling bool,
 
 				for _, vuln := range group.IDs {
 					if addStyling {
-						links = append(links, osv.BaseVulnerabilityURL+text.Bold.EscapeSeq()+vuln+text.Reset.EscapeSeq())
+						links = append(links, OSVBaseVulnerabilityURL+text.Bold.EscapeSeq()+vuln+text.Reset.EscapeSeq())
 					} else {
-						links = append(links, osv.BaseVulnerabilityURL+vuln)
+						links = append(links, OSVBaseVulnerabilityURL+vuln)
 					}
 				}
 
diff --git a/internal/utility/vulns/vulnerabilities.go b/internal/utility/vulns/vulnerabilities.go
new file mode 100644
index 0000000..5e45e2d
--- /dev/null
+++ b/internal/utility/vulns/vulnerabilities.go
@@ -0,0 +1,20 @@
+package vulns
+
+import "github.com/google/osv-scanner/pkg/models"
+
+func Include(vs models.Vulnerabilities, vulnerability models.Vulnerability) bool {
+	for _, vuln := range vs {
+		if vuln.ID == vulnerability.ID {
+			return true
+		}
+
+		if isAliasOf(vuln, vulnerability) {
+			return true
+		}
+		if isAliasOf(vulnerability, vuln) {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/internal/utility/vulns/vulnerability.go b/internal/utility/vulns/vulnerability.go
new file mode 100644
index 0000000..25f103d
--- /dev/null
+++ b/internal/utility/vulns/vulnerability.go
@@ -0,0 +1,152 @@
+package vulns
+
+import (
+	"fmt"
+	"os"
+	"sort"
+
+	"github.com/google/osv-scanner/internal/semantic"
+	"github.com/google/osv-scanner/pkg/lockfile"
+	"github.com/google/osv-scanner/pkg/models"
+	"golang.org/x/exp/slices"
+)
+
+func eventVersion(e models.Event) string {
+	if e.Introduced != "" {
+		return e.Introduced
+	}
+
+	if e.Fixed != "" {
+		return e.Fixed
+	}
+
+	if e.Limit != "" {
+		return e.Limit
+	}
+
+	if e.LastAffected != "" {
+		return e.LastAffected
+	}
+
+	return ""
+}
+
+func rangeContainsVersion(ar models.Range, pkg lockfile.PackageDetails) bool {
+	if ar.Type != models.RangeEcosystem && ar.Type != models.RangeSemVer {
+		return false
+	}
+	// todo: we should probably warn here
+	if len(ar.Events) == 0 {
+		return false
+	}
+
+	vp := semantic.MustParse(pkg.Version, pkg.CompareAs)
+
+	sort.Slice(ar.Events, func(i, j int) bool {
+		a := ar.Events[i]
+		b := ar.Events[j]
+
+		if a.Introduced == "0" {
+			return true
+		}
+
+		if b.Introduced == "0" {
+			return false
+		}
+
+		return semantic.MustParse(eventVersion(a), pkg.CompareAs).CompareStr(eventVersion(b)) < 0
+	})
+
+	var affected bool
+	for _, e := range ar.Events {
+		if affected {
+			if e.Fixed != "" {
+				affected = vp.CompareStr(e.Fixed) < 0
+			} else if e.LastAffected != "" {
+				affected = e.LastAffected == pkg.Version || vp.CompareStr(e.LastAffected) <= 0
+			}
+		} else if e.Introduced != "" {
+			affected = e.Introduced == "0" || vp.CompareStr(e.Introduced) >= 0
+		}
+	}
+
+	return affected
+}
+
+// rangeAffectsVersion checks if the given version is within the range
+// specified by the events of any "Ecosystem" or "Semver" type ranges
+func rangeAffectsVersion(a []models.Range, pkg lockfile.PackageDetails) bool {
+	for _, r := range a {
+		if r.Type != models.RangeEcosystem && r.Type != models.RangeSemVer {
+			return false
+		}
+		if rangeContainsVersion(r, pkg) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func isAliasOfID(v models.Vulnerability, id string) bool {
+	for _, alias := range v.Aliases {
+		if alias == id {
+			return true
+		}
+	}
+
+	return false
+}
+
+func isAliasOf(v models.Vulnerability, vulnerability models.Vulnerability) bool {
+	for _, alias := range vulnerability.Aliases {
+		if v.ID == alias || isAliasOfID(v, alias) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func AffectsEcosystem(v models.Vulnerability, ecosystem lockfile.Ecosystem) bool {
+	for _, affected := range v.Affected {
+		if string(affected.Package.Ecosystem) == string(ecosystem) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func IsAffected(v models.Vulnerability, pkg lockfile.PackageDetails) bool {
+	for _, affected := range v.Affected {
+		if string(affected.Package.Ecosystem) == string(pkg.Ecosystem) &&
+			affected.Package.Name == pkg.Name {
+			if len(affected.Ranges) == 0 && len(affected.Versions) == 0 {
+				_, _ = fmt.Fprintf(
+					os.Stderr,
+					"%s does not have any ranges or versions - this is probably a mistake!\n",
+					v.ID,
+				)
+
+				continue
+			}
+
+			if slices.Contains(affected.Versions, pkg.Version) {
+				return true
+			}
+
+			if rangeAffectsVersion(affected.Ranges, pkg) {
+				return true
+			}
+
+			// if a package does not have a version, assume it is vulnerable
+			// as false positives are better than false negatives here
+			if pkg.Version == "" {
+				return true
+			}
+		}
+	}
+
+	return false
+}
diff --git a/pkg/models/vulnerabilities.go b/pkg/models/vulnerabilities.go
index 53c9470..ea20967 100644
--- a/pkg/models/vulnerabilities.go
+++ b/pkg/models/vulnerabilities.go
@@ -7,23 +7,6 @@ import (
 
 type Vulnerabilities []Vulnerability
 
-func (vs Vulnerabilities) Includes(vulnerability Vulnerability) bool {
-	for _, vuln := range vs {
-		if vuln.ID == vulnerability.ID {
-			return true
-		}
-
-		if vuln.isAliasOf(vulnerability) {
-			return true
-		}
-		if vulnerability.isAliasOf(vuln) {
-			return true
-		}
-	}
-
-	return false
-}
-
 // MarshalJSON ensures that if there are no vulnerabilities,
 // an empty array is used as the value instead of "null"
 func (vs Vulnerabilities) MarshalJSON() ([]byte, error) {
diff --git a/pkg/models/vulnerability.go b/pkg/models/vulnerability.go
index 1d92217..db7a0e1 100644
--- a/pkg/models/vulnerability.go
+++ b/pkg/models/vulnerability.go
@@ -2,14 +2,7 @@ package models
 
 import (
 	"encoding/json"
-	"fmt"
-	"os"
-	"sort"
 	"time"
-
-	"github.com/google/osv-scanner/internal/semantic"
-	"github.com/google/osv-scanner/pkg/lockfile"
-	"golang.org/x/exp/slices"
 )
 
 // Package identifies the affected code library or command provided by the
@@ -174,143 +167,3 @@ func (v Vulnerability) MarshalYAML() (interface{}, error) {
 
 	return raw, nil
 }
-
-func (e Event) version() string {
-	if e.Introduced != "" {
-		return e.Introduced
-	}
-
-	if e.Fixed != "" {
-		return e.Fixed
-	}
-
-	if e.Limit != "" {
-		return e.Limit
-	}
-
-	if e.LastAffected != "" {
-		return e.LastAffected
-	}
-
-	return ""
-}
-
-func (ar Range) containsVersion(pkg lockfile.PackageDetails) bool {
-	if ar.Type != RangeEcosystem && ar.Type != RangeSemVer {
-		return false
-	}
-	// todo: we should probably warn here
-	if len(ar.Events) == 0 {
-		return false
-	}
-
-	vp := semantic.MustParse(pkg.Version, pkg.CompareAs)
-
-	sort.Slice(ar.Events, func(i, j int) bool {
-		a := ar.Events[i]
-		b := ar.Events[j]
-
-		if a.Introduced == "0" {
-			return true
-		}
-
-		if b.Introduced == "0" {
-			return false
-		}
-
-		return semantic.MustParse(a.version(), pkg.CompareAs).CompareStr(b.version()) < 0
-	})
-
-	var affected bool
-	for _, e := range ar.Events {
-		if affected {
-			if e.Fixed != "" {
-				affected = vp.CompareStr(e.Fixed) < 0
-			} else if e.LastAffected != "" {
-				affected = e.LastAffected == pkg.Version || vp.CompareStr(e.LastAffected) <= 0
-			}
-		} else if e.Introduced != "" {
-			affected = e.Introduced == "0" || vp.CompareStr(e.Introduced) >= 0
-		}
-	}
-
-	return affected
-}
-
-// affectsVersion checks if the given version is within the range
-// specified by the events of any "Ecosystem" or "Semver" type ranges
-func affectsVersion(a []Range, pkg lockfile.PackageDetails) bool {
-	for _, r := range a {
-		if r.Type != RangeEcosystem && r.Type != RangeSemVer {
-			return false
-		}
-		if r.containsVersion(pkg) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (v Vulnerability) isAliasOfID(id string) bool {
-	for _, alias := range v.Aliases {
-		if alias == id {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (v Vulnerability) isAliasOf(vulnerability Vulnerability) bool {
-	for _, alias := range vulnerability.Aliases {
-		if v.ID == alias || v.isAliasOfID(alias) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (v Vulnerability) AffectsEcosystem(ecosystem lockfile.Ecosystem) bool {
-	for _, affected := range v.Affected {
-		if string(affected.Package.Ecosystem) == string(ecosystem) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func (v Vulnerability) IsAffected(pkg lockfile.PackageDetails) bool {
-	for _, affected := range v.Affected {
-		if string(affected.Package.Ecosystem) == string(pkg.Ecosystem) &&
-			affected.Package.Name == pkg.Name {
-			if len(affected.Ranges) == 0 && len(affected.Versions) == 0 {
-				_, _ = fmt.Fprintf(
-					os.Stderr,
-					"%s does not have any ranges or versions - this is probably a mistake!\n",
-					v.ID,
-				)
-
-				continue
-			}
-
-			if slices.Contains(affected.Versions, pkg.Version) {
-				return true
-			}
-
-			if affectsVersion(affected.Ranges, pkg) {
-				return true
-			}
-
-			// if a package does not have a version, assume it is vulnerable
-			// as false positives are better than false negatives here
-			if pkg.Version == "" {
-				return true
-			}
-		}
-	}
-
-	return false
-}
diff --git a/renovate.json b/renovate.json
index 9918ff6..238fe59 100644
--- a/renovate.json
+++ b/renovate.json
@@ -20,7 +20,7 @@
       "groupName": "osv-scanner minor"
     },
     {
-      "matchPaths": [".github/"],
+      "matchFileNames": [".github/"],
       "groupName": "workflows"
     }
   ],


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5204170..a2eb3ce 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -32,7 +32,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+        uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
         with:
           persist-credentials: false
           fetch-depth: 0
diff --git a/internal/utility/vulns/vulnerabilities_test.go b/internal/utility/vulns/vulnerabilities_test.go
new file mode 100644
index 0000000..6a15ca1
--- /dev/null
+++ b/internal/utility/vulns/vulnerabilities_test.go
@@ -0,0 +1,129 @@
+package vulns_test
+
+import (
+	"testing"
+
+	"github.com/google/osv-scanner/internal/utility/vulns"
+	"github.com/google/osv-scanner/pkg/models"
+)
+
+func TestVulnerabilities_Includes(t *testing.T) {
+	t.Parallel()
+
+	type args struct {
+		osv models.Vulnerability
+	}
+	tests := []struct {
+		name string
+		vs   models.Vulnerabilities
+		args args
+		want bool
+	}{
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-2",
+					Aliases: []string{},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{"GHSA-2"},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-2",
+					Aliases: []string{},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-2",
+					Aliases: []string{"GHSA-1"},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{"CVE-1"},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-2",
+					Aliases: []string{"CVE-1"},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "",
+			vs: models.Vulnerabilities{
+				models.Vulnerability{
+					ID:      "GHSA-1",
+					Aliases: []string{"CVE-2"},
+				},
+			},
+			args: args{
+				osv: models.Vulnerability{
+					ID:      "GHSA-2",
+					Aliases: []string{"CVE-2"},
+				},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			if got := vulns.Include(tt.vs, tt.args.osv); got != tt.want {
+				t.Errorf("Includes() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/internal/utility/vulns/vulnerability_test.go b/internal/utility/vulns/vulnerability_test.go
new file mode 100644
index 0000000..9e64ee1
--- /dev/null
+++ b/internal/utility/vulns/vulnerability_test.go
@@ -0,0 +1,706 @@
+package vulns_test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/google/osv-scanner/internal/utility/vulns"
+	"github.com/google/osv-scanner/pkg/lockfile"
+	"github.com/google/osv-scanner/pkg/models"
+)
+
+func expectIsAffected(t *testing.T, vuln models.Vulnerability, version string, expectAffected bool) {
+	t.Helper()
+
+	pkg := lockfile.PackageDetails{
+		Name:      "my-package",
+		Version:   version,
+		Ecosystem: lockfile.NpmEcosystem,
+		CompareAs: lockfile.NpmEcosystem,
+	}
+
+	if vulns.IsAffected(vuln, pkg) != expectAffected {
+		if expectAffected {
+			t.Errorf("Expected OSV to affect package version %s but it did not", version)
+		} else {
+			t.Errorf("Expected OSV not to affect package version %s but it did", version)
+		}
+	}
+}
+
+func buildOSVWithAffected(affected ...models.Affected) models.Vulnerability {
+	return models.Vulnerability{
+		ID:        "1",
+		Published: time.Time{},
+		Modified:  time.Time{},
+		Details:   "This is an open source vulnerability!",
+		Affected:  affected,
+	}
+}
+
+func buildEcosystemAffectsRange(events ...models.Event) models.Range {
+	return models.Range{Type: models.RangeEcosystem, Events: events}
+}
+
+func buildSemverAffectsRange(events ...models.Event) models.Range {
+	return models.Range{Type: models.RangeSemVer, Events: events}
+}
+
+func TestOSV_AffectsEcosystem(t *testing.T) {
+	t.Parallel()
+
+	type AffectsTest struct {
+		Affected  []models.Affected
+		Ecosystem lockfile.Ecosystem
+		Expected  bool
+	}
+
+	tests := []AffectsTest{
+		{Affected: nil, Ecosystem: "Go", Expected: false},
+		{Affected: nil, Ecosystem: "npm", Expected: false},
+		{Affected: nil, Ecosystem: "PyPI", Expected: false},
+		{Affected: nil, Ecosystem: "", Expected: false},
+		{
+			Affected: []models.Affected{
+				{Package: models.Package{Ecosystem: "crates.io"}},
+				{Package: models.Package{Ecosystem: "npm"}},
+				{Package: models.Package{Ecosystem: "PyPI"}},
+			},
+			Ecosystem: "Packagist",
+			Expected:  false,
+		},
+		{
+			Affected: []models.Affected{
+				{Package: models.Package{Ecosystem: "NuGet"}},
+			},
+			Ecosystem: "NuGet",
+			Expected:  true,
+		},
+		{
+			Affected: []models.Affected{
+				{Package: models.Package{Ecosystem: "npm"}},
+				{Package: models.Package{Ecosystem: "npm"}},
+			},
+			Ecosystem: "npm",
+			Expected:  true,
+		},
+	}
+
+	for i, test := range tests {
+		vuln := models.Vulnerability{
+			ID:        "1",
+			Published: time.Time{},
+			Modified:  time.Time{},
+			Details:   "This is an open source vulnerability!",
+			Affected:  test.Affected,
+		}
+
+		if vulns.AffectsEcosystem(vuln, test.Ecosystem) != test.Expected {
+			t.Errorf(
+				"Test #%d: Expected OSV to return %t but it returned %t",
+				i,
+				test.Expected,
+				!test.Expected,
+			)
+		}
+	}
+
+	// test when the OSV doesn't have an "Affected"
+	vuln := models.Vulnerability{
+		ID:        "1",
+		Published: time.Time{},
+		Modified:  time.Time{},
+		Details:   "This is an open source vulnerability!",
+		Affected:  nil,
+	}
+
+	if vulns.AffectsEcosystem(vuln, "npm") {
+		t.Errorf(
+			"Expected OSV to report 'false' when it doesn't have an Affected, but it reported true!",
+		)
+	}
+}
+
+func TestOSV_IsAffected_AffectsWithEcosystem_DifferentEcosystem(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemPyPI, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(models.Event{Introduced: "0"}),
+			},
+		},
+	)
+
+	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+}
+
+func TestOSV_IsAffected_AffectsWithEcosystem_SingleAffected(t *testing.T) {
+	t.Parallel()
+
+	var vuln models.Vulnerability
+
+	// "Introduced: 0" means everything is affected
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(models.Event{Introduced: "0"}),
+			},
+		},
+	)
+
+	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// "Fixed: 1" means all versions after this are not vulnerable
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// multiple fixes and introduced
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "3.2.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// "LastAffected: 1" means all versions after this are not vulnerable
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{LastAffected: "1"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc", "1.0.0"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.1", "1.1.0", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// mix of fixes, last_known_affected, and introduced
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{LastAffected: "3.1.9"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc", "3.1.9"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_AffectsWithEcosystem_MultipleAffected(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+				),
+			},
+		},
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "3.2.0"},
+				),
+			},
+		},
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "3.3.0"},
+					models.Event{LastAffected: "3.5.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"3.3.1", "3.4.5"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_AffectsWithEcosystem_Unsorted(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "1"},
+					models.Event{LastAffected: "3.1.9"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc", "3.1.9"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// zeros with build strings
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			// golang.org/x/sys
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Fixed: "0.0.0-20220412211240-33da011f77ad"},
+					models.Event{Introduced: "0"},
+				),
+			},
+		},
+		models.Affected{
+			// golang.org/x/net
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildEcosystemAffectsRange(
+					models.Event{Introduced: "0.0.0-20180925071336-cf3bd585ca2a"},
+					models.Event{Fixed: "0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.14.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"0.0.0-20180925071336-cf3bd585ca2a"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_AffectsWithSemver_DifferentEcosystem(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemPyPI, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(models.Event{Introduced: "0"}),
+			},
+		},
+	)
+
+	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+}
+
+func TestOSV_IsAffected_AffectsWithSemver_SingleAffected(t *testing.T) {
+	t.Parallel()
+
+	var vuln models.Vulnerability
+
+	// "Introduced: 0" means everything is affected
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(models.Event{Introduced: "0"}),
+			},
+		},
+	)
+
+	for _, v := range []string{"v1.0.0", "v1.1.1", "v2.0.0"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// "Fixed: 1" means all versions after this are not vulnerable
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1.0.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// multiple fixes and introduced
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "3.2.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// "LastAffected: 1" means all versions after this are not vulnerable
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{LastAffected: "1.0.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc", "1.0.0"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.1", "1.1.0", "2.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// mix of fixes, last_known_affected, and introduced
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{LastAffected: "3.1.9"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_AffectsWithSemver_MultipleAffected(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Fixed: "1"},
+				),
+			},
+		},
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "3.2.0"},
+				),
+			},
+		},
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "3.3.0"},
+					models.Event{LastAffected: "3.5.0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"3.3.1", "3.4.5", "3.5.0"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_AffectsWithSemver_Unsorted(t *testing.T) {
+	t.Parallel()
+
+	// mix of fixes, last_known_affected, and introduced
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0"},
+					models.Event{Introduced: "2.1.0"},
+					models.Event{Fixed: "1"},
+					models.Event{LastAffected: "3.1.9"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+
+	// zeros with build strings
+	vuln = buildOSVWithAffected(
+		models.Affected{
+			// golang.org/x/sys
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Fixed: "0.0.0-20220412211240-33da011f77ad"},
+					models.Event{Introduced: "0"},
+				),
+			},
+		},
+		models.Affected{
+			// golang.org/x/net
+			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Ranges: []models.Range{
+				buildSemverAffectsRange(
+					models.Event{Introduced: "0.0.0-20180925071336-cf3bd585ca2a"},
+					models.Event{Fixed: "0"},
+				),
+			},
+		},
+	)
+
+	for _, v := range []string{"0.0.0", "0.14.0"} {
+		expectIsAffected(t, vuln, v, false)
+	}
+
+	for _, v := range []string{"0.0.0-20180925071336-cf3bd585ca2a"} {
+		expectIsAffected(t, vuln, v, true)
+	}
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
+
+func TestOSV_IsAffected_OnlyVersions(t *testing.T) {
+	t.Parallel()
+
+	vuln := buildOSVWithAffected(
+		models.Affected{
+			Package:  models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
+			Versions: []string{"1.0.0"},
+		},
+	)
+
+	expectIsAffected(t, vuln, "0.0.0", false)
+	expectIsAffected(t, vuln, "1.0.0", true)
+	expectIsAffected(t, vuln, "1.0.0-beta1", false)
+	expectIsAffected(t, vuln, "1.1.0", false)
+
+	// an empty version should always be treated as affected
+	expectIsAffected(t, vuln, "", true)
+}
diff --git a/pkg/models/vulnerabilities_test.go b/pkg/models/vulnerabilities_test.go
index 2430283..2d06f2d 100644
--- a/pkg/models/vulnerabilities_test.go
+++ b/pkg/models/vulnerabilities_test.go
@@ -7,127 +7,6 @@ import (
 	"github.com/google/osv-scanner/pkg/models"
 )
 
-func TestVulnerabilities_Includes(t *testing.T) {
-	t.Parallel()
-
-	type args struct {
-		osv models.Vulnerability
-	}
-	tests := []struct {
-		name string
-		vs   models.Vulnerabilities
-		args args
-		want bool
-	}{
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-2",
-					Aliases: []string{},
-				},
-			},
-			want: false,
-		},
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{},
-				},
-			},
-			want: true,
-		},
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{"GHSA-2"},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-2",
-					Aliases: []string{},
-				},
-			},
-			want: true,
-		},
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-2",
-					Aliases: []string{"GHSA-1"},
-				},
-			},
-			want: true,
-		},
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{"CVE-1"},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-2",
-					Aliases: []string{"CVE-1"},
-				},
-			},
-			want: true,
-		},
-		{
-			name: "",
-			vs: models.Vulnerabilities{
-				models.Vulnerability{
-					ID:      "GHSA-1",
-					Aliases: []string{"CVE-2"},
-				},
-			},
-			args: args{
-				osv: models.Vulnerability{
-					ID:      "GHSA-2",
-					Aliases: []string{"CVE-2"},
-				},
-			},
-			want: true,
-		},
-	}
-	for _, tt := range tests {
-		tt := tt
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			if got := tt.vs.Includes(tt.args.osv); got != tt.want {
-				t.Errorf("Includes() = %v, want %v", got, tt.want)
-			}
-		})
-	}
-}
-
 func TestVulnerabilities_MarshalJSON(t *testing.T) {
 	t.Parallel()
 
diff --git a/pkg/models/vulnerability_test.go b/pkg/models/vulnerability_test.go
index 1303e07..11171ca 100644
--- a/pkg/models/vulnerability_test.go
+++ b/pkg/models/vulnerability_test.go
@@ -5,7 +5,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/osv-scanner/pkg/lockfile"
 	"github.com/google/osv-scanner/pkg/models"
 	"gopkg.in/yaml.v3"
 )
@@ -232,699 +231,3 @@ func TestVulnerability_MarshalYAMLTimes(t *testing.T) {
 		})
 	}
 }
-
-func expectIsAffected(t *testing.T, vuln models.Vulnerability, version string, expectAffected bool) {
-	t.Helper()
-
-	pkg := lockfile.PackageDetails{
-		Name:      "my-package",
-		Version:   version,
-		Ecosystem: lockfile.NpmEcosystem,
-		CompareAs: lockfile.NpmEcosystem,
-	}
-
-	if vuln.IsAffected(pkg) != expectAffected {
-		if expectAffected {
-			t.Errorf("Expected OSV to affect package version %s but it did not", version)
-		} else {
-			t.Errorf("Expected OSV not to affect package version %s but it did", version)
-		}
-	}
-}
-
-func buildOSVWithAffected(affected ...models.Affected) models.Vulnerability {
-	return models.Vulnerability{
-		ID:        "1",
-		Published: time.Time{},
-		Modified:  time.Time{},
-		Details:   "This is an open source vulnerability!",
-		Affected:  affected,
-	}
-}
-
-func buildEcosystemAffectsRange(events ...models.Event) models.Range {
-	return models.Range{Type: models.RangeEcosystem, Events: events}
-}
-
-func buildSemverAffectsRange(events ...models.Event) models.Range {
-	return models.Range{Type: models.RangeSemVer, Events: events}
-}
-
-func TestOSV_AffectsEcosystem(t *testing.T) {
-	t.Parallel()
-
-	type AffectsTest struct {
-		Affected  []models.Affected
-		Ecosystem lockfile.Ecosystem
-		Expected  bool
-	}
-
-	tests := []AffectsTest{
-		{Affected: nil, Ecosystem: "Go", Expected: false},
-		{Affected: nil, Ecosystem: "npm", Expected: false},
-		{Affected: nil, Ecosystem: "PyPI", Expected: false},
-		{Affected: nil, Ecosystem: "", Expected: false},
-		{
-			Affected: []models.Affected{
-				{Package: models.Package{Ecosystem: "crates.io"}},
-				{Package: models.Package{Ecosystem: "npm"}},
-				{Package: models.Package{Ecosystem: "PyPI"}},
-			},
-			Ecosystem: "Packagist",
-			Expected:  false,
-		},
-		{
-			Affected: []models.Affected{
-				{Package: models.Package{Ecosystem: "NuGet"}},
-			},
-			Ecosystem: "NuGet",
-			Expected:  true,
-		},
-		{
-			Affected: []models.Affected{
-				{Package: models.Package{Ecosystem: "npm"}},
-				{Package: models.Package{Ecosystem: "npm"}},
-			},
-			Ecosystem: "npm",
-			Expected:  true,
-		},
-	}
-
-	for i, test := range tests {
-		vuln := models.Vulnerability{
-			ID:        "1",
-			Published: time.Time{},
-			Modified:  time.Time{},
-			Details:   "This is an open source vulnerability!",
-			Affected:  test.Affected,
-		}
-
-		if vuln.AffectsEcosystem(test.Ecosystem) != test.Expected {
-			t.Errorf(
-				"Test #%d: Expected OSV to return %t but it returned %t",
-				i,
-				test.Expected,
-				!test.Expected,
-			)
-		}
-	}
-
-	// test when the OSV doesn't have an "Affected"
-	vuln := models.Vulnerability{
-		ID:        "1",
-		Published: time.Time{},
-		Modified:  time.Time{},
-		Details:   "This is an open source vulnerability!",
-		Affected:  nil,
-	}
-
-	if vuln.AffectsEcosystem("npm") {
-		t.Errorf(
-			"Expected OSV to report 'false' when it doesn't have an Affected, but it reported true!",
-		)
-	}
-}
-
-func TestOSV_IsAffected_AffectsWithEcosystem_DifferentEcosystem(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemPyPI, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(models.Event{Introduced: "0"}),
-			},
-		},
-	)
-
-	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-}
-
-func TestOSV_IsAffected_AffectsWithEcosystem_SingleAffected(t *testing.T) {
-	t.Parallel()
-
-	var vuln models.Vulnerability
-
-	// "Introduced: 0" means everything is affected
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(models.Event{Introduced: "0"}),
-			},
-		},
-	)
-
-	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// "Fixed: 1" means all versions after this are not vulnerable
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// multiple fixes and introduced
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "3.2.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// "LastAffected: 1" means all versions after this are not vulnerable
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{LastAffected: "1"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc", "1.0.0"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.1", "1.1.0", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// mix of fixes, last_known_affected, and introduced
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{LastAffected: "3.1.9"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc", "3.1.9"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_AffectsWithEcosystem_MultipleAffected(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-				),
-			},
-		},
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "3.2.0"},
-				),
-			},
-		},
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "3.3.0"},
-					models.Event{LastAffected: "3.5.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"3.3.1", "3.4.5"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_AffectsWithEcosystem_Unsorted(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "1"},
-					models.Event{LastAffected: "3.1.9"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc", "3.1.9"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// zeros with build strings
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			// golang.org/x/sys
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Fixed: "0.0.0-20220412211240-33da011f77ad"},
-					models.Event{Introduced: "0"},
-				),
-			},
-		},
-		models.Affected{
-			// golang.org/x/net
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildEcosystemAffectsRange(
-					models.Event{Introduced: "0.0.0-20180925071336-cf3bd585ca2a"},
-					models.Event{Fixed: "0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.14.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"0.0.0-20180925071336-cf3bd585ca2a"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_AffectsWithSemver_DifferentEcosystem(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemPyPI, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(models.Event{Introduced: "0"}),
-			},
-		},
-	)
-
-	for _, v := range []string{"1.0.0", "1.1.1", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-}
-
-func TestOSV_IsAffected_AffectsWithSemver_SingleAffected(t *testing.T) {
-	t.Parallel()
-
-	var vuln models.Vulnerability
-
-	// "Introduced: 0" means everything is affected
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(models.Event{Introduced: "0"}),
-			},
-		},
-	)
-
-	for _, v := range []string{"v1.0.0", "v1.1.1", "v2.0.0"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// "Fixed: 1" means all versions after this are not vulnerable
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1.0.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// multiple fixes and introduced
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "3.2.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// "LastAffected: 1" means all versions after this are not vulnerable
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{LastAffected: "1.0.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc", "1.0.0"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.1", "1.1.0", "2.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// mix of fixes, last_known_affected, and introduced
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{LastAffected: "3.1.9"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_AffectsWithSemver_MultipleAffected(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Fixed: "1"},
-				),
-			},
-		},
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "3.2.0"},
-				),
-			},
-		},
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "3.3.0"},
-					models.Event{LastAffected: "3.5.0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"3.3.1", "3.4.5", "3.5.0"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_AffectsWithSemver_Unsorted(t *testing.T) {
-	t.Parallel()
-
-	// mix of fixes, last_known_affected, and introduced
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0"},
-					models.Event{Introduced: "2.1.0"},
-					models.Event{Fixed: "1"},
-					models.Event{LastAffected: "3.1.9"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.1.0", "0.0.0.1", "1.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"1.0.0", "1.1.0", "2.0.0rc2", "2.0.1"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"2.1.1", "2.3.4", "3.0.0", "3.0.0-rc"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	for _, v := range []string{"3.2.0", "3.2.1", "4.0.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-
-	// zeros with build strings
-	vuln = buildOSVWithAffected(
-		models.Affected{
-			// golang.org/x/sys
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Fixed: "0.0.0-20220412211240-33da011f77ad"},
-					models.Event{Introduced: "0"},
-				),
-			},
-		},
-		models.Affected{
-			// golang.org/x/net
-			Package: models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Ranges: []models.Range{
-				buildSemverAffectsRange(
-					models.Event{Introduced: "0.0.0-20180925071336-cf3bd585ca2a"},
-					models.Event{Fixed: "0"},
-				),
-			},
-		},
-	)
-
-	for _, v := range []string{"0.0.0", "0.14.0"} {
-		expectIsAffected(t, vuln, v, false)
-	}
-
-	for _, v := range []string{"0.0.0-20180925071336-cf3bd585ca2a"} {
-		expectIsAffected(t, vuln, v, true)
-	}
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}
-
-func TestOSV_IsAffected_OnlyVersions(t *testing.T) {
-	t.Parallel()
-
-	vuln := buildOSVWithAffected(
-		models.Affected{
-			Package:  models.Package{Ecosystem: models.EcosystemNPM, Name: "my-package"},
-			Versions: []string{"1.0.0"},
-		},
-	)
-
-	expectIsAffected(t, vuln, "0.0.0", false)
-	expectIsAffected(t, vuln, "1.0.0", true)
-	expectIsAffected(t, vuln, "1.0.0-beta1", false)
-	expectIsAffected(t, vuln, "1.1.0", false)
-
-	// an empty version should always be treated as affected
-	expectIsAffected(t, vuln, "", true)
-}