internal/httpd/symbols_test.go

v1.41.0
gitbay/internal/httpd/symbols_test.go history · blame · raw

139 lines · 4783 bytes

  1package httpd
  2
  3import (
  4	"context"
  5	"os"
  6	"os/exec"
  7	"path/filepath"
  8	"strings"
  9	"testing"
 10
 11	"gitbay.org/gitbay/internal/config"
 12	"gitbay.org/gitbay/internal/control"
 13	"gitbay.org/gitbay/internal/store"
 14	"gitbay.org/gitbay/internal/symbols"
 15)
 16
 17func symbolGit(t *testing.T, dir string, args ...string) {
 18	t.Helper()
 19	cmd := exec.Command("git", args...)
 20	cmd.Dir = dir
 21	cmd.Env = append(os.Environ(),
 22		"GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null",
 23		"GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.test",
 24		"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.test")
 25	if out, err := cmd.CombinedOutput(); err != nil {
 26		t.Fatalf("git %v: %v\n%s", args, err, out)
 27	}
 28}
 29
 30// symbolServer serves alice/app (public) and alice/secret (private), each
 31// with main indexed and a side branch whose tree differs.
 32func symbolServer(t *testing.T) *Server {
 33	t.Helper()
 34	st, err := store.Open(":memory:")
 35	if err != nil {
 36		t.Fatal(err)
 37	}
 38	t.Cleanup(func() { st.Close() })
 39	if err := st.MigrateUp(); err != nil {
 40		t.Fatal(err)
 41	}
 42	uid, err := st.CreateUser("alice", false)
 43	if err != nil {
 44		t.Fatal(err)
 45	}
 46	root := t.TempDir()
 47	src := filepath.Join(root, "src")
 48	symbolGit(t, root, "init", "-q", "-b", "main", "src")
 49	files := map[string]string{
 50		"main.go":  "package main\n\nfunc main() { helper(); dup(); unknown() }\n\nfunc helper() {}\n",
 51		"a/dup.go": "package a\n\nfunc dup() {}\n",
 52		"b/dup.go": "package b\n\nfunc dup() {}\n",
 53	}
 54	for name, body := range files {
 55		os.MkdirAll(filepath.Join(src, filepath.Dir(name)), 0o755)
 56		os.WriteFile(filepath.Join(src, name), []byte(body), 0o644)
 57	}
 58	symbolGit(t, src, "add", ".")
 59	symbolGit(t, src, "commit", "-q", "-m", "base")
 60	symbolGit(t, src, "checkout", "-q", "-b", "side")
 61	os.WriteFile(filepath.Join(src, "side.go"), []byte("package main\n"), 0o644)
 62	symbolGit(t, src, "add", ".")
 63	symbolGit(t, src, "commit", "-q", "-m", "side")
 64
 65	w := symbols.NewWith(st, func(owner, name string) string { return control.RepoDir(root, owner, name) }, 0)
 66	for _, r := range []struct{ name, vis string }{{"app", "public"}, {"secret", "private"}} {
 67		id, err := st.CreateRepo("user", uid, r.name, r.vis)
 68		if err != nil {
 69			t.Fatal(err)
 70		}
 71		dir := control.RepoDir(root, "alice", r.name)
 72		os.MkdirAll(filepath.Dir(dir), 0o755)
 73		symbolGit(t, root, "init", "-q", "--bare", dir)
 74		symbolGit(t, src, "push", "-q", dir, "main", "side")
 75		if _, err := w.Index(context.Background(), id, false); err != nil {
 76			t.Fatal(err)
 77		}
 78	}
 79	cfg := config.Default()
 80	cfg.Server.Root = root
 81	cfg.Server.SiteURL = "https://forge.test/"
 82	return New(cfg, st, nil)
 83}
 84
 85func TestBlobLinksIndexedNames(t *testing.T) {
 86	h := symbolServer(t).Handler()
 87	w := get(t, h, "/alice/app/blob/main/main.go", nil)
 88	if w.Code != 200 {
 89		t.Fatalf("blob: %d", w.Code)
 90	}
 91	body := w.Body.String()
 92	for _, want := range []string{
 93		// defined once: straight to the line, at the ref being viewed
 94		`<a class="sym" href="/alice/app/blob/main/main.go#L5"><span class="nf">helper</span></a>`,
 95		// defined twice: to the results page
 96		`<a class="sym" href="/alice/app/symbols?q=dup"><span class="nf">dup</span></a>`,
 97		// the per-file list
 98		`2 symbols in this file`,
 99	} {
100		if !strings.Contains(body, want) {
101			t.Errorf("blob page lacks %s", want)
102		}
103	}
104	if strings.Contains(body, `>unknown</span></a>`) {
105		t.Error("a name the index does not hold is linked")
106	}
107
108	// A branch at another tree is not what the index describes.
109	w = get(t, h, "/alice/app/blob/side/main.go", nil)
110	if w.Code != 200 || strings.Contains(w.Body.String(), `class="sym"`) || strings.Contains(w.Body.String(), "in this file") {
111		t.Errorf("side branch: %d, links or list present", w.Code)
112	}
113}
114
115func TestSymbolsPage(t *testing.T) {
116	h := symbolServer(t).Handler()
117	w := get(t, h, "/alice/app/symbols?q=dup", nil)
118	body := w.Body.String()
119	if w.Code != 200 || !strings.Contains(body, "a/dup.go:3") || !strings.Contains(body, "b/dup.go:3") {
120		t.Fatalf("results page: %d\n%s", w.Code, body)
121	}
122	w = get(t, h, "/alice/app/symbols?q=dup&kind=type", nil)
123	if strings.Contains(w.Body.String(), "a/dup.go:3") {
124		t.Error("kind filter ignored")
125	}
126	if w := get(t, h, "/alice/app/symbols?q=d", nil); !strings.Contains(w.Body.String(), "query must be 2 to 200 characters") {
127		t.Error("a one-character query was searched")
128	}
129	// A cursor from an index that has been replaced says so rather than
130	// showing an empty page.
131	if w := get(t, h, "/alice/app/symbols?q=dup&after=999999.1", nil); !strings.Contains(w.Body.String(), "rebuilt since that cursor") {
132		t.Errorf("stale cursor:\n%s", w.Body.String())
133	}
134	for _, p := range []string{"/alice/secret/symbols?q=dup", "/alice/secret/blob/main/main.go"} {
135		if w := get(t, h, p, nil); w.Code != 404 {
136			t.Errorf("%s anonymously: %d, want 404", p, w.Code)
137		}
138	}
139}