package httpd
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"gitbay.org/gitbay/internal/config"
"gitbay.org/gitbay/internal/control"
"gitbay.org/gitbay/internal/store"
"gitbay.org/gitbay/internal/symbols"
)
func symbolGit(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(),
"GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null",
"GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@example.test",
"GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@example.test")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}
// symbolServer serves alice/app (public) and alice/secret (private), each
// with main indexed and a side branch whose tree differs.
func symbolServer(t *testing.T) *Server {
t.Helper()
st, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
if err := st.MigrateUp(); err != nil {
t.Fatal(err)
}
uid, err := st.CreateUser("alice", false)
if err != nil {
t.Fatal(err)
}
root := t.TempDir()
src := filepath.Join(root, "src")
symbolGit(t, root, "init", "-q", "-b", "main", "src")
files := map[string]string{
"main.go": "package main\n\nfunc main() { helper(); dup(); unknown() }\n\nfunc helper() {}\n",
"a/dup.go": "package a\n\nfunc dup() {}\n",
"b/dup.go": "package b\n\nfunc dup() {}\n",
}
for name, body := range files {
os.MkdirAll(filepath.Join(src, filepath.Dir(name)), 0o755)
os.WriteFile(filepath.Join(src, name), []byte(body), 0o644)
}
symbolGit(t, src, "add", ".")
symbolGit(t, src, "commit", "-q", "-m", "base")
symbolGit(t, src, "checkout", "-q", "-b", "side")
os.WriteFile(filepath.Join(src, "side.go"), []byte("package main\n"), 0o644)
symbolGit(t, src, "add", ".")
symbolGit(t, src, "commit", "-q", "-m", "side")
w := symbols.NewWith(st, func(owner, name string) string { return control.RepoDir(root, owner, name) }, 0)
for _, r := range []struct{ name, vis string }{{"app", "public"}, {"secret", "private"}} {
id, err := st.CreateRepo("user", uid, r.name, r.vis)
if err != nil {
t.Fatal(err)
}
dir := control.RepoDir(root, "alice", r.name)
os.MkdirAll(filepath.Dir(dir), 0o755)
symbolGit(t, root, "init", "-q", "--bare", dir)
symbolGit(t, src, "push", "-q", dir, "main", "side")
if _, err := w.Index(context.Background(), id, false); err != nil {
t.Fatal(err)
}
}
cfg := config.Default()
cfg.Server.Root = root
cfg.Server.SiteURL = "https://forge.test/"
return New(cfg, st, nil)
}
func TestBlobLinksIndexedNames(t *testing.T) {
h := symbolServer(t).Handler()
w := get(t, h, "/alice/app/blob/main/main.go", nil)
if w.Code != 200 {
t.Fatalf("blob: %d", w.Code)
}
body := w.Body.String()
for _, want := range []string{
// defined once: straight to the line, at the ref being viewed
`helper`,
// defined twice: to the results page
`dup`,
// the per-file list
`2 symbols in this file`,
} {
if !strings.Contains(body, want) {
t.Errorf("blob page lacks %s", want)
}
}
if strings.Contains(body, `>unknown`) {
t.Error("a name the index does not hold is linked")
}
// A branch at another tree is not what the index describes.
w = get(t, h, "/alice/app/blob/side/main.go", nil)
if w.Code != 200 || strings.Contains(w.Body.String(), `class="sym"`) || strings.Contains(w.Body.String(), "in this file") {
t.Errorf("side branch: %d, links or list present", w.Code)
}
}
func TestSymbolsPage(t *testing.T) {
h := symbolServer(t).Handler()
w := get(t, h, "/alice/app/symbols?q=dup", nil)
body := w.Body.String()
if w.Code != 200 || !strings.Contains(body, "a/dup.go:3") || !strings.Contains(body, "b/dup.go:3") {
t.Fatalf("results page: %d\n%s", w.Code, body)
}
w = get(t, h, "/alice/app/symbols?q=dup&kind=type", nil)
if strings.Contains(w.Body.String(), "a/dup.go:3") {
t.Error("kind filter ignored")
}
if w := get(t, h, "/alice/app/symbols?q=d", nil); !strings.Contains(w.Body.String(), "query must be 2 to 200 characters") {
t.Error("a one-character query was searched")
}
// A cursor from an index that has been replaced says so rather than
// showing an empty page.
if w := get(t, h, "/alice/app/symbols?q=dup&after=999999.1", nil); !strings.Contains(w.Body.String(), "rebuilt since that cursor") {
t.Errorf("stale cursor:\n%s", w.Body.String())
}
for _, p := range []string{"/alice/secret/symbols?q=dup", "/alice/secret/blob/main/main.go"} {
if w := get(t, h, p, nil); w.Code != 404 {
t.Errorf("%s anonymously: %d, want 404", p, w.Code)
}
}
}