| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package cveutils | |
| import ( | |
| "encoding/json" | |
| "errors" | |
| "net/http" | |
| "net/http/httptest" | |
| "path/filepath" | |
| "testing" | |
| "time" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/google/go-cmp/cmp/cmpopts" | |
| "golang.org/x/tools/txtar" | |
| "golang.org/x/vulndb/internal/test" | |
| ) | |
| func TestList(t *testing.T) { | |
| realDeltaLog, err := readFileFromArchive(deltaLogTxtar, "cves/deltaLog.json") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| simple := simpleDeltaLog() | |
| tcs := []struct { | |
| name string | |
| since string | |
| deltaLog []byte | |
| want []string | |
| }{ | |
| { | |
| // If the "since" time is after the latest fetch time, | |
| // no CVEs are found. | |
| name: "since>latest", | |
| since: jan7, | |
| deltaLog: simple, | |
| want: nil, | |
| }, | |
| { | |
| // If the "since" time is equal to the earliest fetch time, | |
| // all CVEs in the log are looked at (but not necessarily returned). | |
| // In this case, cve1 is not returned because it was | |
| // updated before the "since" time. | |
| // cve4 was added and updated, but only appears once. | |
| name: "since=earliest", | |
| since: jan2, | |
| deltaLog: simple, | |
| want: []string{testCVE2, testCVE3, testCVE4, testCVE5}, | |
| }, | |
| { | |
| // cve1 and and cve2 don't make the cutoff. | |
| name: "latest>since>earliest", | |
| since: jan3, | |
| deltaLog: simple, | |
| want: []string{testCVE3, testCVE4, testCVE5}, | |
| }, | |
| { | |
| name: "real log", | |
| since: "2023-12-13T17:54:14.241Z", | |
| deltaLog: realDeltaLog.Data, | |
| want: []string{ | |
| "CVE-2023-43813", | |
| "CVE-2023-46726", | |
| "CVE-2023-6795", | |
| "CVE-2023-6790", | |
| "CVE-2023-6792", | |
| "CVE-2023-6794", | |
| "CVE-2023-6767", | |
| }, | |
| }, | |
| } | |
| for _, tc := range tcs { | |
| t.Run(tc.name, func(t *testing.T) { | |
| s, c := newTestClientAndServer(t, tc.deltaLog) | |
| t.Cleanup(s.Close) | |
| since := mustParse(tc.since) | |
| got, err := list(c, s.URL, since) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if diff := cmp.Diff(tc.want, got, | |
| // Ignore order. | |
| cmpopts.SortSlices(func(x, y string) bool { return x < y })); diff != "" { | |
| t.Errorf("list(%s) mismatch (-want, +got):\n%s", tc.since, diff) | |
| } | |
| }) | |
| } | |
| } | |
| func TestListFail(t *testing.T) { | |
| realDeltaLog, err := readFileFromArchive(deltaLogTxtar, "cves/deltaLog.json") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| tcs := []struct { | |
| name string | |
| since string | |
| deltaLog []byte | |
| wantErr error | |
| }{ | |
| { | |
| name: "simple", | |
| since: jan1, | |
| deltaLog: simpleDeltaLog(), | |
| wantErr: errSinceTooEarly, | |
| }, | |
| { | |
| name: "real log", | |
| since: "2023-11-13T19:25:22.000Z", // right before earliest fetch time | |
| deltaLog: realDeltaLog.Data, | |
| wantErr: errSinceTooEarly, | |
| }, | |
| } | |
| for _, tc := range tcs { | |
| t.Run(tc.name, func(t *testing.T) { | |
| s, c := newTestClientAndServer(t, realDeltaLog.Data) | |
| t.Cleanup(s.Close) | |
| since := mustParse(tc.since) | |
| _, err := list(c, s.URL, since) | |
| if !errors.Is(err, errSinceTooEarly) { | |
| t.Errorf("list() = %s, want error", errSinceTooEarly) | |
| } | |
| }) | |
| } | |
| } | |
| // A txtar archive containing an actual delta log pulled from cvelistV5. | |
| var deltaLogTxtar = filepath.Join("testdata", "deltaLog.txtar") | |
| const ( | |
| jan7 = "2000-01-07T00:00:00.000Z" | |
| jan6 = "2000-01-06T00:00:00.000Z" | |
| jan5Noon = "2000-01-05T12:00:00.000Z" | |
| jan5 = "2000-01-05T00:00:00.000Z" | |
| jan4Noon = "2000-01-04T12:00:00.000Z" | |
| jan4 = "2000-01-04T00:00:00.000Z" | |
| jan3Noon = "2000-01-03T12:00:00.000Z" | |
| jan3 = "2000-01-03T00:00:00.000Z" | |
| jan2 = "2000-01-02T00:00:00.000Z" | |
| jan1 = "2000-01-01T00:00:00.000Z" | |
| testCVE5 = "CVE-2000-0005" | |
| testCVE4 = "CVE-2000-0004" | |
| testCVE3 = "CVE-2000-0003" | |
| testCVE2 = "CVE-2000-0002" | |
| testCVE1 = "CVE-2000-0001" | |
| ) | |
| // simpleDeltaLog returns a delta log with fake data for testing. | |
| func simpleDeltaLog() []byte { | |
| dl := []*updateMeta{ | |
| { | |
| FetchTime: jan6, | |
| New: []*cveMeta{ | |
| { | |
| ID: testCVE5, | |
| Updated: jan5Noon, | |
| }, | |
| }, | |
| Updated: []*cveMeta{ | |
| { | |
| ID: testCVE4, | |
| Updated: jan4Noon, | |
| }, | |
| }, | |
| }, | |
| { | |
| FetchTime: jan4, | |
| New: []*cveMeta{ | |
| { | |
| ID: testCVE4, | |
| Updated: jan4, | |
| }, | |
| }, | |
| Updated: []*cveMeta{ | |
| { | |
| ID: testCVE3, | |
| Updated: jan3Noon, | |
| }, | |
| // It's possible for the "updated" time to be before | |
| // the next "fetch" time, because of an inconsistency in | |
| // the way "fetch" and "updated" are calculated. | |
| // See the comment on the cveMeta.Updated for more info. | |
| { | |
| ID: testCVE1, | |
| Updated: jan1, | |
| }, | |
| }, | |
| }, | |
| { | |
| FetchTime: jan2, | |
| New: []*cveMeta{ | |
| { | |
| ID: testCVE2, | |
| Updated: jan2, | |
| }, | |
| }, | |
| Updated: []*cveMeta{}, | |
| }, | |
| } | |
| b, err := json.Marshal(dl) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return b | |
| } | |
| func readFileFromArchive(filename, fileInArchive string) (*txtar.File, error) { | |
| ar, err := txtar.ParseFile(filename) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return test.FindFile(ar, fileInArchive) | |
| } | |
| func newTestClientAndServer(t *testing.T, b []byte) (*httptest.Server, *http.Client) { | |
| s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| _, err := w.Write(b) | |
| if err != nil { | |
| w.WriteHeader(http.StatusInternalServerError) | |
| t.Errorf("could not write response body: %v", err) | |
| } | |
| })) | |
| c := s.Client() | |
| return s, c | |
| } | |
| func mustParse(s string) time.Time { | |
| t, err := parseTime(s) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return t | |
| } |