e2e/issuesearch_test.go
114 lines · 4390 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 inst := startInstance(t)
33 aliceKey := inst.newKey(t, "alice")
34 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
35 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
36 t.Fatalf("repo create: %s", errOut)
37 }
38
39 mk := func(title, body string) {
40 t.Helper()
41 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app",
42 "--title", "'"+title+"'", "--body", "'"+body+"'"); code != 0 {
43 t.Fatalf("issue create: %s", errOut)
44 }
45 }
46 mk("memory leak in the parser", "resident size climbs forever")
47 mk("flaky test", "the runner times out sometimes")
48 mk("docs typo", "spelled parser wrong")
49
50 // Title match.
51 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "leak"); len(got) != 1 || got[0] != 1 {
52 t.Fatalf("title search = %v, want [1]", got)
53 }
54 // Body match — the half a title-only search could never reach.
55 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "climbs"); len(got) != 1 || got[0] != 1 {
56 t.Fatalf("body search = %v, want [1]", got)
57 }
58 // One word in two issues, one in a title and one in a body.
59 got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser")
60 if len(got) != 2 {
61 t.Fatalf("search across title and body = %v, want two issues", got)
62 }
63 // Terms are ANDed, not ORed.
64 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "'parser runner'"); len(got) != 0 {
65 t.Fatalf("terms ORed rather than ANDed: %v", got)
66 }
67 // Search composes with the other filters.
68 if _, _, code := inst.ssh(t, aliceKey, "", "issue", "close", "alice/app", "1"); code != 0 {
69 t.Fatal("close failed")
70 }
71 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser"); len(got) != 1 || got[0] != 3 {
72 t.Fatalf("search ignored --state open: %v", got)
73 }
74 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "parser", "--state", "all"); len(got) != 2 {
75 t.Fatalf("--state all with search = %v", got)
76 }
77
78 // An edit moves the issue in the index rather than leaving the old
79 // words matching.
80 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "edit", "alice/app", "2",
81 "--title", "'renamed entirely'", "--body", "'nothing of the original remains'"); code != 0 {
82 t.Fatalf("issue edit: %s", errOut)
83 }
84 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "flaky", "--state", "all"); len(got) != 0 {
85 t.Fatalf("edited-away words still match: %v", got)
86 }
87 if got := issueNumbers(t, inst, aliceKey, "alice/app", "--search", "renamed", "--state", "all"); len(got) != 1 {
88 t.Fatalf("new words not indexed: %v", got)
89 }
90
91 // FTS5 operators typed by a person are terms, not syntax errors.
92 // Single characters are refused for length before they get this far,
93 // which the "x" case below covers.
94 for _, q := range []string{"c++", "AND", "NOT", "foo:", "a AND", "b*", "()"} {
95 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "list", "alice/app",
96 "--search", "'"+q+"'", "--json"); code != 0 {
97 t.Fatalf("search %q failed: %s", q, errOut)
98 }
99 }
100 // Too short is a usage error, the same as every other query.
101 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "list", "alice/app", "--search", "x"); code != 2 ||
102 !strings.Contains(errOut, "2 to 200 characters") {
103 t.Fatalf("short search: exit %d, %s", code, errOut)
104 }
105
106 // The instance-wide search reaches bodies too now.
107 out, errOut, code := inst.ssh(t, aliceKey, "", "search", "climbs", "--json")
108 if code != 0 {
109 t.Fatalf("search: %s", errOut)
110 }
111 if !strings.Contains(out, "alice/app") || !strings.Contains(out, "memory leak") {
112 t.Fatalf("global body search: %s", out)
113 }
114}