blob: 0be2a34d9bceef151fa100e867bc091c10b0a69f [file] [log] [blame]
// Copyright 2021 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 main
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path"
"path/filepath"
"regexp"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/net/html"
"golang.org/x/pkgsite/internal/proxy/proxytest"
"golang.org/x/pkgsite/internal/testing/htmlcheck"
"golang.org/x/pkgsite/internal/testing/testhelper"
)
var (
in = htmlcheck.In
hasText = htmlcheck.HasText
attr = htmlcheck.HasAttr
// href checks for an exact match in an href attribute.
href = func(val string) htmlcheck.Checker {
return attr("href", "^"+regexp.QuoteMeta(val)+"$")
}
)
func TestServer(t *testing.T) {
repoPath := func(fn string) string { return filepath.Join("..", "..", fn) }
abs := func(dir string) string {
a, err := filepath.Abs(dir)
if err != nil {
t.Fatal(err)
}
return a
}
localModule, _ := testhelper.WriteTxtarToTempDir(t, `
-- go.mod --
module example.com/testmod
-- a.go --
package a
`)
cacheDir := repoPath("internal/fetch/testdata/modcache")
testModules := proxytest.LoadTestModules(repoPath("internal/proxy/testdata"))
prox, teardown := proxytest.SetupTestClient(t, testModules)
defer teardown()
cfg := func(modifyDefault func(*serverConfig)) serverConfig {
c := serverConfig{
paths: []string{localModule},
gopathMode: false,
useListedMods: true,
useCache: true,
cacheDir: cacheDir,
proxy: prox,
}
if modifyDefault != nil {
modifyDefault(&c)
}
return c
}
modcacheChecker := in("",
in(".Documentation", hasText("var V = 1")),
sourceLinks(path.Join(abs(cacheDir), "modcache.com@v1.0.0"), "a.go"))
ctx := context.Background()
for _, test := range []struct {
name string
cfg serverConfig
url string
wantCode int
want htmlcheck.Checker
}{
{
"local",
cfg(nil),
"example.com/testmod",
http.StatusOK,
in("",
in(".Documentation", hasText("There is no documentation for this package.")),
sourceLinks(path.Join(abs(localModule), "example.com/testmod"), "a.go")),
},
{
"modcache",
cfg(nil),
"modcache.com@v1.0.0",
http.StatusOK,
modcacheChecker,
},
{
"modcache latest",
cfg(nil),
"modcache.com",
http.StatusOK,
modcacheChecker,
},
{
"modcache unsupported",
cfg(func(c *serverConfig) {
c.useCache = false
}),
"modcache.com",
http.StatusFailedDependency, // TODO(rfindley): should this be 404?
hasText("page is not supported"),
},
{
"proxy",
cfg(nil),
"example.com/single/pkg",
http.StatusOK,
hasText("G is new in v1.1.0"),
},
{
"proxy unsupported",
cfg(func(c *serverConfig) {
c.proxy = nil
}),
"example.com/single/pkg",
http.StatusFailedDependency, // TODO(rfindley): should this be 404?
hasText("page is not supported"),
},
{
"search",
cfg(nil),
"search?q=a",
http.StatusOK,
in(".SearchResults",
hasText("example.com/testmod"),
),
},
{
"no symbol search",
cfg(nil),
"search?q=A", // using a capital letter should not cause symbol search
http.StatusOK,
in(".SearchResults",
hasText("example.com/testmod"),
),
},
{
"search not found",
cfg(nil),
"search?q=zzz",
http.StatusOK,
in(".SearchResults",
hasText("no matches"),
),
},
{
"search vulns not found",
cfg(nil),
"search?q=GO-1234-1234",
http.StatusOK,
in(".SearchResults",
hasText("no matches"),
),
},
{
"search unsupported",
cfg(func(c *serverConfig) {
c.paths = nil
}),
"search?q=zzz",
http.StatusFailedDependency,
hasText("page is not supported"),
},
{
"vulns unsupported",
cfg(nil),
"vuln/",
http.StatusFailedDependency,
hasText("page is not supported"),
},
// TODO(rfindley): add a test for the standard library once it doesn't go
// through the stdlib package.
// See also golang/go#58923.
} {
t.Run(test.name, func(t *testing.T) {
server, err := buildServer(ctx, test.cfg)
if err != nil {
t.Fatal(err)
}
mux := http.NewServeMux()
server.Install(mux.Handle, nil, nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, httptest.NewRequest("GET", "/"+test.url, nil))
if w.Code != test.wantCode {
t.Fatalf("got status code = %d, want %d", w.Code, test.wantCode)
}
doc, err := html.Parse(w.Body)
if err != nil {
t.Fatal(err)
}
if err := test.want(doc); err != nil {
if testing.Verbose() {
html.Render(os.Stdout, doc)
}
t.Error(err)
}
})
}
}
func sourceLinks(dir, filename string) htmlcheck.Checker {
filesPath := path.Join("/files", dir) + "/"
return in("",
in(".UnitMeta-repo a", href(filesPath)),
in(".UnitFiles-titleLink a", href(filesPath)),
in(".UnitFiles-fileList a", href(filesPath+filename)))
}
func TestCollectPaths(t *testing.T) {
got := collectPaths([]string{"a", "b,c2,d3", "e4", "f,g"})
want := []string{"a", "b", "c2", "d3", "e4", "f", "g"}
if !cmp.Equal(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}