x/vulndb: add tests for all top-level lint errors
Adds tests (and makes minor behavior fixes) for all lint errors emitted
directly by the Lint function.
Behavior fixes:
- getModVersions and getCanonicalModName now check status of http.Get
call
- lint check on CVE metadata now only checks for malformed ID if no ID is present
For #52945
Change-Id: Idd291d86beda8691dd7b8404c783364e839109fb
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/406197
Run-TryBot: Tatiana Bradley <tatiana@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/report/lint.go b/internal/report/lint.go
index cc37654..a609dbf 100644
--- a/internal/report/lint.go
+++ b/internal/report/lint.go
@@ -40,6 +40,8 @@
resp, err := http.Get(fmt.Sprintf("%s/%s/@v/list", proxyURL, escaped))
if err != nil {
return nil, err
+ } else if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("http.Get returned status %v", resp.Status)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
@@ -66,6 +68,8 @@
resp, err := http.Get(fmt.Sprintf("%s/%s/@v/%s.mod", proxyURL, escapedPath, escapedVersion))
if err != nil {
return "", err
+ } else if resp.StatusCode != 200 {
+ return "", fmt.Errorf("http.Get returned status %v", resp.Status)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
@@ -254,8 +258,7 @@
if r.CVEMetadata != nil {
if r.CVEMetadata.ID == "" {
addIssue("cve_metadata.id is required")
- }
- if !cveRegex.MatchString(r.CVEMetadata.ID) {
+ } else if !cveRegex.MatchString(r.CVEMetadata.ID) {
addIssue("malformed cve_metadata.id identifier")
}
}
diff --git a/internal/report/lint_test.go b/internal/report/lint_test.go
index 43a858d..24f714e 100644
--- a/internal/report/lint_test.go
+++ b/internal/report/lint_test.go
@@ -8,39 +8,233 @@
"bytes"
"strings"
"testing"
+ "time"
)
+// TODO: Add tests for helper functions that call the proxy.
+
func TestLint(t *testing.T) {
+ jan2000 := time.Date(2000, time.January, 0, 0, 0, 0, 0, time.UTC)
+ jan2022 := time.Date(2022, time.January, 0, 0, 0, 0, 0, time.UTC)
for _, test := range []struct {
+ desc string
report Report
want []string
- }{{
- report: Report{
- Packages: []Package{{
- Module: "std",
- Package: "time",
- Versions: []VersionRange{{
- Fixed: "1.2.1",
- }, {
- Fixed: "1.3.2",
- }},
- }},
+ }{
+ {
+ desc: "no packages",
+ report: Report{
+ Description: "description",
+ },
+ want: []string{"no packages"},
},
- want: []string{"version ranges overlap"},
- }, {
- report: Report{
- Packages: []Package{{
- Module: "std",
- Package: "time",
- Versions: []VersionRange{{
- Introduced: "1.3",
- Fixed: "1.2.1",
+ {
+ desc: "missing module",
+ report: Report{
+ Packages: []Package{{
+ // no module
+ Package: "test.com/a/package",
}},
- }},
+ Description: "description",
+ },
+ want: []string{"missing module"},
},
- want: []string{`version "1.3" >= "1.2.1"`},
- }} {
+ {
+ desc: "missing description",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ // no description
+ },
+ want: []string{"missing description"},
+ },
+ {
+ desc: "third party: redundant module and package",
+ report: Report{
+ Packages: []Package{{
+ Module: "github.com/golang/vulndb",
+ Package: "github.com/golang/vulndb",
+ }},
+ Description: "description",
+ },
+ want: []string{"package is redundant and can be removed"},
+ },
+ {
+ desc: "third party: module is not a prefix of package",
+ report: Report{
+ Packages: []Package{{
+ Module: "github.com/golang/vulndb",
+ Package: "github.com/golang/crypto",
+ }},
+ Description: "description",
+ },
+ want: []string{"module must be a prefix of package"},
+ },
+ {
+ desc: "third party: invalid import path",
+ report: Report{
+ Packages: []Package{{
+ Module: "invalid.",
+ Versions: []VersionRange{{
+ Fixed: "1.2.1",
+ }},
+ }},
+ Description: "description",
+ },
+ want: []string{"malformed import path",
+ "unable to retrieve module versions from proxy"},
+ },
+ {
+ desc: "standard library: missing package",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ // no package
+ }},
+ Description: "description",
+ },
+ want: []string{"missing package"},
+ },
+ {
+ desc: "overlapping version ranges",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ Versions: []VersionRange{{
+ Fixed: "1.2.1",
+ }, {
+ Fixed: "1.3.2",
+ }},
+ }},
+ Description: "description",
+ },
+ want: []string{"version ranges overlap"},
+ },
+ {
+ desc: "fixed before introduced",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ Versions: []VersionRange{{
+ Introduced: "1.3",
+ Fixed: "1.2.1",
+ }},
+ }},
+ Description: "description",
+ },
+ want: []string{`version "1.3" >= "1.2.1"`},
+ },
+ {
+ desc: "invalid semantic version",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ Versions: []VersionRange{{
+ Introduced: "1.3.X",
+ }},
+ }},
+ Description: "description",
+ },
+ want: []string{`invalid semantic version: "1.3.X"`},
+ },
+ {
+ desc: "last modified before published",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ LastModified: &jan2000,
+ Published: jan2022,
+ },
+ want: []string{"last_modified is before published"},
+ },
+ {
+ desc: "bad cve identifier",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ CVEs: []string{"CVE.123.456"},
+ },
+ want: []string{"malformed cve identifier"},
+ },
+ {
+ desc: "cve and cve metadata both present",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ CVEs: []string{"CVE-2022-12345"},
+ CVEMetadata: &CVEMeta{
+ ID: "CVE-2022-23456",
+ },
+ },
+ want: []string{"only one of cve and cve_metadata.id should be present"},
+ },
+ {
+ desc: "missing cve metadata id",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ CVEMetadata: &CVEMeta{
+ // no id
+ },
+ },
+ want: []string{"cve_metadata.id is required"},
+ },
+ {
+ desc: "bad cve metadata id",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ CVEMetadata: &CVEMeta{
+ ID: "CVE.2022.00000",
+ },
+ },
+ want: []string{"malformed cve_metadata.id identifier"},
+ },
+ {
+ desc: "unfixed links",
+ report: Report{
+ Packages: []Package{{
+ Module: "std",
+ Package: "time",
+ }},
+ Description: "description",
+ Links: Links{
+ Commit: "https://github.com/golang/go/commit/123",
+ Context: []string{
+ "https://github.com/golang/go/issues/123",
+ "https://golang.org/xxx",
+ "https://groups.google.com/forum/#!/golang-announce/123/1/"},
+ },
+ },
+ want: []string{
+ `"https://github.com/golang/go/issues/123" should be "https://go.dev/issue/123"`,
+ `"https://golang.org/xxx" should be "https://go.dev/xxx"`,
+ `"https://github.com/golang/go/commit/123" should be "https://go.googlesource.com/+/123"`,
+ `"https://groups.google.com/forum/#!/golang-announce/123/1/" should be "https://groups.google.com/g/golang-announce/c/123/m/1/"`},
+ },
+ } {
got := test.report.Lint()
+
var missing []string
for _, w := range test.want {
found := false
@@ -59,13 +253,49 @@
if err := test.report.encode(&buf); err != nil {
t.Error(err)
}
- t.Errorf("missing expected lint warnings in report:\n%v", buf.String())
+ t.Errorf("TestLint(%q): missing expected lint errors in report:\n"+
+ "%v\n"+
+ "got: %q\n"+
+ "want: %q\n", test.desc, buf.String(), got, missing)
+ }
+
+ // Check for unexpected lint errors if there are no missing ones.
+ if len(missing) == 0 {
+ var unexpected []string
for _, g := range got {
- t.Errorf(" got: %v", g)
+ found := false
+ for _, w := range test.want {
+ if strings.Contains(g, w) {
+ found = true
+ continue
+ }
+ }
+ if !found {
+ unexpected = append(unexpected, g)
+ }
}
- for _, w := range missing {
- t.Errorf(" want: %v", w)
+ if len(unexpected) > 0 {
+ var buf bytes.Buffer
+ if err := test.report.encode(&buf); err != nil {
+ t.Error(err)
+ }
+ t.Errorf("TestLint(%q): unexpected lint errors in report:\n"+
+ "%v\n"+
+ "got: %q\n", test.desc, buf.String(), unexpected)
}
}
}
}
+
+func TestLintFile(t *testing.T) {
+ f := "testdata/report.yaml"
+ lintErrs, err := LintFile(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(lintErrs) > 0 {
+ t.Errorf("unexpected lint errors for %q:\n"+
+ "got: %q\n"+
+ "want: []", f, lintErrs)
+ }
+}
diff --git a/internal/report/testdata/report.yaml b/internal/report/testdata/report.yaml
index 977bc36..122b6b1 100644
--- a/internal/report/testdata/report.yaml
+++ b/internal/report/testdata/report.yaml
@@ -3,7 +3,7 @@
symbols:
- defaultLogFormatter
versions:
- - fixed: v1.6.0
+ - fixed: 1.6.0
description: |
The default Formatter for the Logger middleware (LoggerConfig.Formatter),
which is included in the Default engine, allows attackers to inject arbitrary