blob: c65dc4cdae98d024050c7ddaf5027d560f0b4da0 [file] [log] [blame]
// 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 vulndbreqs
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"cloud.google.com/go/civil"
"cloud.google.com/go/storage"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
test "golang.org/x/pkgsite-metrics/internal/testing"
)
func TestComputeFromLogs(t *testing.T) {
test.NeedsIntegrationEnv(t)
projID := os.Getenv("GO_ECOSYSTEM_VULNDB_BUCKET_PROJECT")
if projID == "" {
t.Skip("GO_ECOSYSTEM_VULNDB_BUCKET_PROJECT not defined")
}
// Compute yesterday's counts, up to 10 log entries.
// Assume there are more than 10 requests a day.
yesterday := civil.DateOf(time.Now()).AddDays(-1)
const n = 10
igot, err := computeFromLogs(context.Background(), projID, yesterday, testHMACKey, n)
if err != nil {
t.Fatal(err)
}
got := sumRequestCounts(igot)
want := []*RequestCount{{
Date: yesterday,
Count: 10,
}}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want, +got):\n%s", diff)
}
}
func TestComputeFromStorage(t *testing.T) {
test.NeedsIntegrationEnv(t)
// Compute one day's counts, reading only 1 file.
// The file is always the same.
got, err := computeFromStorage(context.Background(), testDate, testHMACKey, 1)
if err != nil {
t.Fatal(err)
}
// The returned slice comes from a map, so sort for determinism.
slices.SortFunc(got, func(r1, r2 *IPRequestCount) bool {
return r1.Count < r2.Count
})
want := []*IPRequestCount{
{Date: testDate, Count: 30},
{Date: testDate, Count: 33},
{Date: testDate, Count: 112},
}
if diff := cmp.Diff(got, want, cmpopts.IgnoreFields(IPRequestCount{}, "IP")); diff != "" {
t.Errorf("mismatch (-want, +got)\n%s", diff)
}
}
var (
testHMACKey = []byte("this-is-a-fake-hmac-key")
testDate = civil.Date{Year: 2023, Month: 5, Day: 30}
// These reflect the contents of testdata/logfile.json,
// which is also stored in the "test" directory of the
// vulndb logs bucket.
testFileDates = map[civil.Date]int{testDate: 13}
testFileIPs = map[string]int{
obfuscate("1.2.3.4", testHMACKey): 3,
obfuscate("5.6.7.8", testHMACKey): 2,
obfuscate("9.10.11.12", testHMACKey): 8,
}
)
func TestReadJSONLogEntries(t *testing.T) {
f, err := os.Open(filepath.Join("testdata", "logfile.json"))
if err != nil {
t.Fatal(err)
}
defer f.Close()
gotDates := map[civil.Date]int{}
gotIPs := map[string]int{}
err = readJSONLogEntries(f, testHMACKey, func(e *logEntry) error {
gotDates[civil.DateOf(e.Timestamp)]++
gotIPs[e.HTTPRequest.RemoteIP]++
return nil
})
if err != nil {
t.Fatal(err)
}
if !maps.Equal(gotDates, testFileDates) {
t.Errorf("dates:\ngot %v\nwant %v", gotDates, testFileDates)
}
if !maps.Equal(gotIPs, testFileIPs) {
t.Errorf("IPs:\ngot %v\nwant %v", gotIPs, testFileIPs)
}
}
func TestCountFiles(t *testing.T) {
test.NeedsIntegrationEnv(t)
// Actual bucket containing logs data.
bucketName := os.Getenv("GOOGLE_CLOUD_PROJECT") + bucketSuffix
// Files manually copied to the bucket for testing.
const testPrefix = "test"
const wantPrefix = "test/2023/05/30/"
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
t.Fatal(err)
}
defer client.Close()
bucket := client.Bucket(bucketName)
t.Run("ObjectNamesForDate", func(t *testing.T) {
const wantNFiles = 2
got, err := objectNamesForDate(ctx, bucket, testPrefix, testDate)
if err != nil {
t.Fatal(err)
}
if g := len(got); g != wantNFiles {
t.Errorf("got %d files, want %d", g, wantNFiles)
}
for _, g := range got {
if !strings.HasPrefix(g, wantPrefix) || !strings.HasSuffix(g, ".json") {
t.Errorf(`got %q, want "%sFILENAME.json"`, g, wantPrefix)
}
}
})
t.Run("CountLogsForObjects", func(t *testing.T) {
// The two files with the testPrefix are both copies of testdata/logfile.json.
objNames := []string{wantPrefix + "logfile1.json", wantPrefix + "logfile2.json"}
gotDates, gotIPs, err := countLogsForObjects(ctx, bucket, objNames, testHMACKey)
if err != nil {
t.Fatal(err)
}
// There are two files, so data is doubled.
wantDates := maps.Clone(testFileDates)
for k, v := range wantDates {
wantDates[k] = v * 2
}
wantIPs := maps.Clone(testFileIPs)
for k, v := range wantIPs {
wantIPs[k] = v * 2
}
if !maps.Equal(gotDates, wantDates) {
t.Errorf("dates:\ngot %v\nwant %v", gotDates, wantDates)
}
if !maps.Equal(gotIPs, wantIPs) {
t.Errorf("IPs:\ngot %v\nwant %v", gotIPs, wantIPs)
}
})
}