internal/control/symbols_test.go

v1.42.0
gitbay/internal/control/symbols_test.go history · blame · raw

144 lines · 5052 bytes

  1package control
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"strings"
  7	"testing"
  8
  9	"gitbay.org/gitbay/internal/protocol"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13func symbolsFixture(t *testing.T) (*store.Store, store.Repo, store.User, store.User) {
 14	t.Helper()
 15	st, err := store.Open(":memory:")
 16	if err != nil {
 17		t.Fatal(err)
 18	}
 19	t.Cleanup(func() { st.Close() })
 20	if err := st.MigrateUp(); err != nil {
 21		t.Fatal(err)
 22	}
 23	aliceID, _ := st.CreateUser("alice", false)
 24	bobID, _ := st.CreateUser("bob", false)
 25	id, err := st.CreateRepo("user", aliceID, "secret", "private")
 26	if err != nil {
 27		t.Fatal(err)
 28	}
 29	repo, _ := st.RepoByID(id)
 30	var rows []store.SymbolRow
 31	for _, n := range []string{"Parse", "ParseAll", "ParseArgs", "Parser", "parse"} {
 32		rows = append(rows, store.SymbolRow{Name: n, Key: n, Kind: "function", Path: "p.go", Line: len(rows) + 1})
 33	}
 34	rows = append(rows, store.SymbolRow{Name: "Parser.Run", Key: "Run", Kind: "method", Path: "p.go", Line: 40})
 35	if _, err := st.ReplaceSymbolIndex(store.SymbolIndex{RepoID: id, Commit: "c0", Tree: "t0", State: "ok"}, rows); err != nil {
 36		t.Fatal(err)
 37	}
 38	return st, repo,
 39		store.User{ID: aliceID, Username: "alice"},
 40		store.User{ID: bobID, Username: "bob"}
 41}
 42
 43type symbolPage struct {
 44	Data struct {
 45		Items []symbolOut `json:"items"`
 46		Next  string      `json:"next"`
 47	} `json:"data"`
 48}
 49
 50func runSymbols(t *testing.T, st *store.Store, u store.User, argv ...string) (int, string, string) {
 51	t.Helper()
 52	var out, errOut bytes.Buffer
 53	c := &Ctx{User: u, Scope: "full", Store: st, Stdout: &out, Stderr: &errOut, JSON: true}
 54	c.Cfg.Server.Root = t.TempDir()
 55	code := Dispatch(c, argv)
 56	if code != protocol.ExitOK {
 57		// In JSON mode a refusal is the output's error field.
 58		return code, out.String(), out.String() + errOut.String()
 59	}
 60	return code, out.String(), errOut.String()
 61}
 62
 63func TestRepoSymbolsPages(t *testing.T) {
 64	st, repo, alice, _ := symbolsFixture(t)
 65	var names []string
 66	cursor := ""
 67	for pages := 0; ; pages++ {
 68		argv := []string{"repo", "symbols", repo.Path(), "Parse", "--limit", "2"}
 69		if cursor != "" {
 70			argv = append(argv, "--cursor", cursor)
 71		}
 72		code, out, errOut := runSymbols(t, st, alice, argv...)
 73		if code != protocol.ExitOK {
 74			t.Fatalf("exit %d: %s", code, errOut)
 75		}
 76		var p symbolPage
 77		if err := json.Unmarshal([]byte(out), &p); err != nil {
 78			t.Fatalf("%v: %s", err, out)
 79		}
 80		for _, it := range p.Data.Items {
 81			names = append(names, it.Name)
 82		}
 83		if p.Data.Next == "" {
 84			break
 85		}
 86		if pages > 5 {
 87			t.Fatal("paging does not end")
 88		}
 89		cursor = p.Data.Next
 90	}
 91	if got, want := strings.Join(names, " "), "Parse ParseAll ParseArgs Parser Parser.Run parse"; got != want {
 92		t.Fatalf("got %q, want %q", got, want)
 93	}
 94
 95	// A cursor from before a rebuild is refused rather than followed.
 96	st.ReplaceSymbolIndex(store.SymbolIndex{RepoID: repo.ID, Commit: "c1", Tree: "t1", State: "ok"},
 97		[]store.SymbolRow{{Name: "Parse", Key: "Parse", Kind: "function", Path: "p.go", Line: 1}})
 98	code, _, errOut := runSymbols(t, st, alice, "repo", "symbols", repo.Path(), "Parse", "--cursor", cursor)
 99	if code != protocol.ExitUsage || !strings.Contains(errOut, "rebuilt") {
100		t.Fatalf("stale cursor: exit %d: %s", code, errOut)
101	}
102}
103
104func TestRepoSymbolsRefusals(t *testing.T) {
105	st, repo, alice, bob := symbolsFixture(t)
106	for _, tc := range []struct {
107		name string
108		user store.User
109		argv []string
110		code int
111		msg  string
112	}{
113		{"outsider sees no repository", bob, []string{"repo", "symbols", repo.Path(), "Parse"}, protocol.ExitNotFound, "not found"},
114		{"another ref", alice, []string{"repo", "symbols", repo.Path(), "--ref", "feature", "Parse"}, protocol.ExitNotFound, "only the default branch"},
115		{"unknown kind", alice, []string{"repo", "symbols", repo.Path(), "--kind", "widget", "Parse"}, protocol.ExitUsage, "--kind"},
116		{"no query", alice, []string{"repo", "symbols", repo.Path()}, protocol.ExitUsage, ""},
117		{"one-character query", alice, []string{"repo", "symbols", repo.Path(), "P"}, protocol.ExitUsage, "2 to"},
118		{"reindex needs an admin", alice, []string{"admin", "symbols", "reindex", repo.Path()}, protocol.ExitDenied, "admin"},
119	} {
120		t.Run(tc.name, func(t *testing.T) {
121			code, _, errOut := runSymbols(t, st, tc.user, tc.argv...)
122			if code != tc.code || !strings.Contains(errOut, tc.msg) {
123				t.Fatalf("exit %d, want %d with %q: %s", code, tc.code, tc.msg, errOut)
124			}
125		})
126	}
127
128	code, _, errOut := runSymbols(t, st, alice, "repo", "symbols", repo.Path(), "--ref", repo.DefaultBranch, "--kind", "method", "Run")
129	if code != protocol.ExitOK {
130		t.Fatalf("default branch by name: exit %d: %s", code, errOut)
131	}
132}
133
134func TestAdminSymbolsReindexQueuesAForcedBuild(t *testing.T) {
135	st, repo, _, _ := symbolsFixture(t)
136	code, _, errOut := runSymbols(t, st, rootUser(t, st), "admin", "symbols", "reindex", repo.Path())
137	if code != protocol.ExitOK {
138		t.Fatalf("exit %d: %s", code, errOut)
139	}
140	reqs, err := st.SymbolRequests()
141	if err != nil || len(reqs) != 1 || reqs[0].RepoID != repo.ID || !reqs[0].Force {
142		t.Fatalf("requests = %+v, %v", reqs, err)
143	}
144}