e2e/issuesearch_test.go
115 lines · 4404 bytes
1package e2e
2
3import (
4 "encoding/json"
5 "strings"
6 "testing"
7)
8
9// issueNumbers runs issue list with the given flags and returns the numbers.
10func issueNumbers(t *testing.T, inst *instance, key string, args ...string) []int64 {
11 t.Helper()
12 out, errOut, code := inst.ssh(t, key, "", append([]string{"issue", "list"}, append(args, "--json")...)...)
13 if code != 0 {
14 t.Fatalf("issue list: %s", errOut)
15 }
16 var env struct {
17 Data []struct {
18 Number int64 `json:"number"`
19 } `json:"data"`
20 }
21 if err := json.Unmarshal([]byte(out), &env); err != nil {
22 t.Fatalf("not JSON: %v\n%s", err, out)
23 }
24 var ns []int64
25 for _, d := range env.Data {
26 ns = append(ns, d.Number)
27 }
28 return ns
29}
30
31func TestIssueSearch(t *testing.T) {
32 t.Parallel()
33 inst := startInstance(t)
34 aliceKey := inst.newKey(t, "alice")
35 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
36 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
37 t.Fatalf("repo create: %s", errOut)
38 }
39
40 mk := func(title, body string) {
41 t.Helper()
42 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app",
43 "--title", "'"+title+"'", "--body", "'"+body+"'"); code != 0 {
44 t.Fatalf("issue create: %s", errOut)
45 }
46 }
47 mk("memory leak in the parser", "resident size climbs forever")
48 mk("flaky test", "the runner times out sometimes")
49 mk("docs typo", "spelled parser wrong")
50
51 // Title match.
52 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "leak"); len(got) != 1 || got[0] != 1 {
53 t.Fatalf("title search = %v, want [1]", got)
54 }
55 // Body match — the half a title-only search could never reach.
56 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "climbs"); len(got) != 1 || got[0] != 1 {
57 t.Fatalf("body search = %v, want [1]", got)
58 }
59 // One word in two issues, one in a title and one in a body.
60 got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser")
61 if len(got) != 2 {
62 t.Fatalf("search across title and body = %v, want two issues", got)
63 }
64 // Terms are ANDed, not ORed.
65 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "'parser runner'"); len(got) != 0 {
66 t.Fatalf("terms ORed rather than ANDed: %v", got)
67 }
68 // Search composes with the other filters.
69 if _, _, code := inst.ssh(t, aliceKey, "", "issue", "close", "alice/app", "1"); code != 0 {
70 t.Fatal("close failed")
71 }
72 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser"); len(got) != 1 || got[0] != 3 {
73 t.Fatalf("search ignored --state open: %v", got)
74 }
75 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser", "--state", "all"); len(got) != 2 {
76 t.Fatalf("--state all with search = %v", got)
77 }
78
79 // An edit moves the issue in the index rather than leaving the old
80 // words matching.
81 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "edit", "alice/app", "2",
82 "--title", "'renamed entirely'", "--body", "'nothing of the original remains'"); code != 0 {
83 t.Fatalf("issue edit: %s", errOut)
84 }
85 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "flaky", "--state", "all"); len(got) != 0 {
86 t.Fatalf("edited-away words still match: %v", got)
87 }
88 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "renamed", "--state", "all"); len(got) != 1 {
89 t.Fatalf("new words not indexed: %v", got)
90 }
91
92 // FTS5 operators typed by a person are terms, not syntax errors.
93 // Single characters are refused for length before they get this far,
94 // which the "x" case below covers.
95 for _, q := range []string{"c++", "AND", "NOT", "foo:", "a AND", "b*", "()"} {
96 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "list", "alice/app",
97 "--search", "'"+q+"'", "--json"); code != 0 {
98 t.Fatalf("search %q failed: %s", q, errOut)
99 }
100 }
101 // Too short is a usage error, the same as every other query.
102 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "list", "alice/app", "--search", "x"); code != 2 ||
103 !strings.Contains(errOut, "2 to 200 characters") {
104 t.Fatalf("short search: exit %d, %s", code, errOut)
105 }
106
107 // The instance-wide search reaches bodies too now.
108 out, errOut, code := inst.ssh(t, aliceKey, "", "search", "climbs", "--json")
109 if code != 0 {
110 t.Fatalf("search: %s", errOut)
111 }
112 if !strings.Contains(out, "alice/app") || !strings.Contains(out, "memory leak") {
113 t.Fatalf("global body search: %s", out)
114 }
115}