blob: 40237535f82d89df8f9fc9ceb6e1b0f45fce802f [file] [log] [blame]
// Copyright 2020 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 frontend
import (
"context"
"net/http"
"strconv"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/postgres"
"golang.org/x/pkgsite/internal/proxy"
"golang.org/x/pkgsite/internal/testing/sample"
"golang.org/x/pkgsite/internal/testing/testhelper"
)
var (
sourceTimeout = 1 * time.Second
testModulePath = "github.com/module"
testSemver = "v1.5.2"
testFetchTimeout = 300 * time.Second
testModulesForProxy = []*proxy.Module{
{
ModulePath: testModulePath,
Version: testSemver,
Files: map[string]string{
"bar/foo/foo.go": "// Package foo\npackage foo\n\nconst Foo = 42",
"README.md": "This is a readme",
"LICENSE": testhelper.MITLicense,
},
},
}
)
func TestFetch(t *testing.T) {
for _, test := range []struct {
name, fullPath, version, want string
}{
{
name: "path at master package is in module root",
fullPath: testModulePath,
version: "master",
},
{
name: "path at main package is in module root",
fullPath: testModulePath,
version: "main",
},
{
name: "path at latest package is in module root",
fullPath: testModulePath,
version: internal.LatestVersion,
},
{
name: "path at semver package is in module root",
fullPath: testModulePath,
version: "v1.5.2",
},
{
name: "package at latest package is not in module root",
fullPath: testModulePath + "/bar/foo",
version: internal.LatestVersion,
},
{
name: "directory at master package is not in module root",
fullPath: testModulePath + "/bar",
version: "master",
},
{
name: "directory at main package is not in module root",
fullPath: testModulePath + "/bar",
version: "main",
},
} {
t.Run(test.name, func(t *testing.T) {
s, _, teardown := newTestServer(t, testModulesForProxy, nil)
defer teardown()
ctx, cancel := context.WithTimeout(context.Background(), testFetchTimeout)
defer cancel()
status, responseText := s.fetchAndPoll(ctx, s.getDataSource(ctx), testModulePath, test.fullPath, test.version)
if status != http.StatusOK {
t.Fatalf("fetchAndPoll(%q, %q, %q) = %d, %s; want status = %d",
testModulePath, test.fullPath, test.version, status, responseText, http.StatusOK)
}
})
}
}
func TestFetchErrors(t *testing.T) {
for _, test := range []struct {
name, modulePath, fullPath, version, wantErrorMessage string
fetchTimeout time.Duration
want int
}{
{
name: "non-existent module",
modulePath: "github.com/nonexistent",
fullPath: "github.com/nonexistent",
version: internal.LatestVersion,
want: http.StatusNotFound,
wantErrorMessage: "\"github.com/nonexistent\" could not be found.",
},
{
name: "version invalid",
modulePath: testModulePath,
fullPath: testModulePath,
version: "random-version",
want: http.StatusBadRequest,
wantErrorMessage: "Bad Request",
},
{
name: "module found but package does not exist",
modulePath: testModulePath,
fullPath: "github.com/module/pkg-nonexistent",
version: internal.LatestVersion,
want: http.StatusNotFound,
wantErrorMessage: `
<div class="Error-message">github.com/module/pkg-nonexistent could not be found.</div>
<div class="Error-message">However, you can view <a href="https://pkg.go.dev/github.com/module">module github.com/module</a>.</div>`,
},
{
name: "module exists but fetch timed out",
modulePath: testModulePath,
fullPath: testModulePath,
version: internal.LatestVersion,
fetchTimeout: 1 * time.Millisecond,
want: http.StatusRequestTimeout,
wantErrorMessage: "We're still working on “github.com/module”. Check back in a few minutes!",
},
} {
t.Run(test.name, func(t *testing.T) {
if test.fetchTimeout == 0 {
test.fetchTimeout = testFetchTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), test.fetchTimeout)
defer cancel()
s, _, teardown := newTestServer(t, testModulesForProxy, nil)
defer teardown()
got, err := s.fetchAndPoll(ctx, s.getDataSource(ctx), test.modulePath, test.fullPath, test.version)
if got != test.want {
t.Fatalf("fetchAndPoll(ctx, testDB, q, %q, %q, %q): %d; want = %d",
test.modulePath, test.fullPath, test.version, got, test.want)
}
if err != test.wantErrorMessage {
t.Fatalf("fetchAndPoll(ctx, testDB, q, %q, %q, %q): %d;\ngot = \n%q,\nwantErrorMessage = \n%q",
test.modulePath, test.fullPath, test.version, got, err, test.wantErrorMessage)
}
})
}
}
func TestFetchPathAlreadyExists(t *testing.T) {
for _, test := range []struct {
status, want int
}{
{http.StatusOK, http.StatusOK},
{http.StatusNotFound, http.StatusNotFound},
{derrors.ToStatus(derrors.AlternativeModule), http.StatusNotFound},
} {
t.Run(strconv.Itoa(test.status), func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testFetchTimeout)
defer cancel()
postgres.MustInsertModule(ctx, t, testDB, sample.DefaultModule())
if err := testDB.UpsertVersionMap(ctx, &internal.VersionMap{
ModulePath: sample.ModulePath,
RequestedVersion: sample.VersionString,
ResolvedVersion: sample.VersionString,
Status: test.status,
}); err != nil {
t.Fatal(err)
}
s, _, teardown := newTestServer(t, testModulesForProxy, nil)
defer teardown()
got, _ := s.fetchAndPoll(ctx, s.getDataSource(ctx), sample.ModulePath, sample.PackagePath, sample.VersionString)
if got != test.want {
t.Fatalf("fetchAndPoll for status %d: %d; want = %d)", test.status, got, test.want)
}
})
}
}
func TestCandidateModulePaths(t *testing.T) {
maxPathsToFetch = 7
for _, test := range []struct {
name, fullPath string
want []string
}{
{"custom path", "my.module/foo", []string{
"my.module/foo",
"my.module",
}},
{"github.com module path", sample.ModulePath, []string{sample.ModulePath}},
{"more than 7 possible paths", sample.ModulePath + "/1/2/3/4/5/6/7/8/9", []string{
sample.ModulePath + "/1/2/3/4/5/6",
sample.ModulePath + "/1/2/3/4/5",
sample.ModulePath + "/1/2/3/4",
sample.ModulePath + "/1/2/3",
sample.ModulePath + "/1/2",
sample.ModulePath + "/1",
sample.ModulePath,
}},
} {
t.Run(test.fullPath, func(t *testing.T) {
got, err := candidateModulePaths(test.fullPath)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(test.want, got); diff != "" {
t.Fatalf("mismatch (-want, +got):\n%s", diff)
}
})
}
}