blob: 4261a815abbf2e833610c49d1e762b8ed8130319 [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 proxy
import (
"errors"
"flag"
"net/http"
"testing"
"github.com/google/go-cmp/cmp"
)
var realProxy = flag.Bool("proxy", false, "if true, contact the real module proxy and update expected responses")
func TestCanonicalModulePath(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
version string
want string
}{
{
name: "non-canonical",
path: "github.com/golang/vulndb",
version: "0.0.0-20230522180520-0cbf4ffdb4e7",
want: "golang.org/x/vulndb",
},
{
name: "canonical",
path: "golang.org/x/vulndb",
version: "0.0.0-20230522180520-0cbf4ffdb4e7",
want: "golang.org/x/vulndb",
},
{
name: "module needs to be escaped",
path: "github.com/RobotsAndPencils/go-saml",
version: "0.0.0-20230606195814-29020529affc",
want: "github.com/RobotsAndPencils/go-saml",
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got, err := c.CanonicalModulePath(tc.path, tc.version)
if err != nil {
t.Fatal(err)
}
if got != tc.want {
t.Errorf("CanonicalModulePath() = %v, want %v", got, tc.want)
}
})
}
}
func TestCanonicalModuleVersion(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
version string
want string
}{
{
name: "tagged version already canonical",
path: "golang.org/x/vuln",
version: "0.1.0",
want: "0.1.0",
},
{
name: "pseudo-version already canonical",
path: "golang.org/x/vulndb",
version: "0.0.0-20230522180520-0cbf4ffdb4e7",
want: "0.0.0-20230522180520-0cbf4ffdb4e7",
},
{
name: "commit hash",
path: "golang.org/x/vulndb",
version: "0cbf4ffdb4e70fce663ec8d59198745b04e7801b",
want: "0.0.0-20230522180520-0cbf4ffdb4e7",
},
{
name: "module needs to be escaped",
path: "github.com/RobotsAndPencils/go-saml",
version: "0.0.0-20230606195814-29020529affc",
want: "0.0.0-20230606195814-29020529affc",
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got, err := c.CanonicalModuleVersion(tc.path, tc.version)
if err != nil {
t.Fatal(err)
}
if got != tc.want {
t.Errorf("CanonicalModuleVersion() = %v, want %v", got, tc.want)
}
})
}
}
func TestModuleExistsAtTaggedVersion(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
version string
want bool
}{
{
name: "exists",
path: "golang.org/x/vuln",
version: "0.1.0",
want: true,
},
{
name: "non-canonical module ok",
path: "github.com/golang/vuln",
version: "0.1.0",
want: true,
},
{
name: "module needs to be escaped",
path: "github.com/Masterminds/squirrel",
version: "1.5.4",
want: true,
},
{
name: "non-canonical version not OK",
path: "golang.org/x/vulndb",
version: "0cbf4ffdb4e70fce663ec8d59198745b04e7801b",
want: false,
},
{
name: "module exists, version does not",
path: "golang.org/x/vulndb",
version: "1.0.0",
want: false,
},
{
name: "neither exist",
path: "golang.org/x/notamod",
version: "1.0.0",
want: false,
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
if got := c.ModuleExistsAtTaggedVersion(tc.path, tc.version); got != tc.want {
t.Errorf("ModuleExistsAtTaggedVersion() = %v, want %v", got, tc.want)
}
})
}
}
func TestVersions(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
want []string
}{
{
name: "no tagged versions",
path: "golang.org/x/vulndb",
want: nil,
},
{
name: "tagged versions",
path: "golang.org/x/vuln",
want: []string{
"0.1.0",
"0.2.0",
"1.0.0",
"1.0.1",
},
},
{
name: "module needs to be escaped",
path: "github.com/RobotsAndPencils/go-saml",
want: nil, // no tagged versions
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got, err := c.Versions(tc.path)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("Versions() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestLatest(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
path string
want string
}{
{
path: "golang.org/x/vulndb",
want: "0.0.0-20230911193511-c7cbbd05f085",
},
{
path: "golang.org/x/vuln",
want: "1.0.1",
},
{
// module needs to be escaped
path: "github.com/RobotsAndPencils/go-saml",
want: "0.0.0-20230606195814-29020529affc",
},
}
for _, tc := range tcs {
t.Run(tc.path, func(t *testing.T) {
got, err := c.Latest(tc.path)
if err != nil {
t.Fatal(err)
}
if got != tc.want {
t.Errorf("Latest() = %v, want %v", got, tc.want)
}
})
}
}
func TestFindModule(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
want string
wantErr error
}{
{
name: "module is a prefix of path",
path: "k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server",
want: "k8s.io/kubernetes/staging/src/k8s.io/apiserver",
},
{
name: "path is a module",
path: "k8s.io/kubernetes/staging/src/k8s.io/apiserver",
want: "k8s.io/kubernetes/staging/src/k8s.io/apiserver",
},
{
name: "no module",
path: "example.co.io/module/package/src/versions/v8",
wantErr: errNoModuleFound,
},
{
name: "module needs to be escaped",
path: "github.com/RobotsAndPencils/go-saml/util",
want: "github.com/RobotsAndPencils/go-saml",
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got, err := c.FindModule(tc.path)
if !errors.Is(err, tc.wantErr) {
t.Errorf("FindModule() error = %v, want err containing %v", err, tc.wantErr)
} else if got != tc.want {
t.Errorf("FindModule() = %v, want %v", got, tc.want)
}
})
}
}
func TestModuleExists(t *testing.T) {
c, err := NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
tcs := []struct {
name string
path string
want bool
}{
{
name: "exists",
path: "k8s.io/kubernetes",
want: true,
},
{
name: "exists (needs escape)",
path: "github.com/RobotsAndPencils/go-saml",
want: true,
},
{
name: "does not exist",
path: "example.com/not/a/module",
want: false,
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
got := c.ModuleExists(tc.path)
if got != tc.want {
t.Errorf("ModuleExists() = %v, want %v", got, tc.want)
}
})
}
}
func TestCacheAndErrors(t *testing.T) {
okEndpoint, notFoundEndpoint := "endpoint", "not/found"
okResponse := "response"
responses := map[string]*response{
okEndpoint: {
Body: okResponse,
StatusCode: http.StatusOK,
},
notFoundEndpoint: {
Body: "",
StatusCode: http.StatusNotFound,
},
}
c, cleanup := fakeClient(responses)
t.Cleanup(cleanup)
wantHits := 3
for i := 0; i < wantHits+1; i++ {
b, err := c.lookup(okEndpoint)
if err != nil {
t.Fatal(err)
}
if got, want := string(b), okResponse; got != want {
t.Errorf("lookup(%q) = %s, want %s", okEndpoint, got, want)
}
}
if c.cache.hits != wantHits {
t.Errorf("cache hits = %d, want %d", c.cache.hits, wantHits)
}
if _, err := c.lookup(notFoundEndpoint); err == nil {
t.Errorf("lookup(%q) succeeded, want error", notFoundEndpoint)
}
want, got := responses, c.responses()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Responses() unexpected diff (want-, got+):\n%s", diff)
}
}