e2e/searchweb_test.go
123 lines · 5081 bytes
1package e2e
2
3import (
4 "net/url"
5 "strings"
6 "testing"
7)
8
9// TestGlobalSearchAndNotificationsWeb covers the two web surfaces #118
10// still lacked: /search across the instance, and the notification inbox.
11func TestGlobalSearchAndNotificationsWeb(t *testing.T) {
12 t.Parallel()
13 inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
14 aliceKey := inst.newKey(t, "alice")
15 bobKey := inst.newKey(t, "bob")
16 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
17 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
18
19 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/widget"); code != 0 {
20 t.Fatalf("repo create: %s", errOut)
21 }
22 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/secret", "--private"); code != 0 {
23 t.Fatalf("private repo create: %s", errOut)
24 }
25 if _, errOut, code := inst.ssh(t, bobKey, "", "issue", "create", "alice/widget",
26 "--title", "'widget leaks memory'", "--body", "'it climbs'"); code != 0 {
27 t.Fatalf("issue create: %s", errOut)
28 }
29 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/secret",
30 "--title", "'widget in the private repo'"); code != 0 {
31 t.Fatalf("private issue create: %s", errOut)
32 }
33
34 // Anonymous search reaches the public repository and its issue, and
35 // neither the private repository nor its issue.
36 status, body := inst.get(t, "/search?q=widget")
37 if status != 200 {
38 t.Fatalf("anonymous search: %d", status)
39 }
40 if !strings.Contains(body, `href="/alice/widget"`) ||
41 !strings.Contains(body, "widget leaks memory") ||
42 !strings.Contains(body, `href="/alice/widget/issues/1"`) {
43 t.Fatalf("anonymous search missing public hits:\n%s", body)
44 }
45 if strings.Contains(body, "alice/secret") || strings.Contains(body, "private repo") {
46 t.Fatal("anonymous search leaked a private repository")
47 }
48
49 // Filtering by kind keeps the repository row and drops the issue.
50 _, body = inst.get(t, "/search?q=widget&kind=repo")
51 if !strings.Contains(body, `href="/alice/widget"`) || strings.Contains(body, "widget leaks memory") {
52 t.Fatalf("kind=repo filter:\n%s", body)
53 }
54 _, body = inst.get(t, "/search?q=x")
55 if !strings.Contains(body, "2 to 200 characters") {
56 t.Fatal("short query not refused")
57 }
58
59 // A query that matches nothing says what to do next instead of
60 // repeating the count line's "no matches" under it (#248).
61 _, body = inst.get(t, "/search?q=zzznothing&kind=issue")
62 if !strings.Contains(body, "0 results") || !strings.Contains(body, "Try fewer words") ||
63 !strings.Contains(body, "search everything") {
64 t.Fatalf("empty search lacks its recovery line:\n%s", body)
65 }
66 if strings.Contains(body, "no matches") {
67 t.Fatalf("empty search repeats the count line:\n%s", body)
68 }
69
70 // Alice sees her private repository and its issue in the same page.
71 browser := inst.login(t, aliceKey)
72 status, body = browserGet(t, browser, inst.base()+"/search?q=widget")
73 if status != 200 || !strings.Contains(body, "alice/secret") ||
74 !strings.Contains(body, "widget in the private repo") {
75 t.Fatalf("owner search misses private rows:\n%s", body)
76 }
77
78 // The rail carries the unread badge and the notifications page lists
79 // the issue bob opened.
80 status, body = browserGet(t, browser, inst.base()+"/notifications")
81 if status != 200 || !strings.Contains(body, "bob opened issue #1") ||
82 !strings.Contains(body, `href="/alice/widget/issues/1"`) {
83 t.Fatalf("notifications page:\n%s", body)
84 }
85 if !strings.Contains(body, `href="/notifications"`) {
86 t.Fatal("rail has no notifications link")
87 }
88
89 // Marking all read empties the unread list but not the full one.
90 if status, _ := browserPost(t, browser, inst.base()+"/notifications", url.Values{}); status != 200 {
91 t.Fatalf("mark all read: %d", status)
92 }
93 _, body = browserGet(t, browser, inst.base()+"/notifications")
94 if !strings.Contains(body, "nothing unread") {
95 t.Fatalf("unread list after sweep:\n%s", body)
96 }
97 _, body = browserGet(t, browser, inst.base()+"/notifications?all=1")
98 if !strings.Contains(body, "bob opened issue #1") {
99 t.Fatalf("all list after sweep:\n%s", body)
100 }
101
102 // Watching from the repository header is the same state the CLI sets.
103 if status, _ := browserPost(t, browser, inst.base()+"/alice/widget/watch", url.Values{}); status != 200 {
104 t.Fatalf("watch toggle: %d", status)
105 }
106 out, errOut, code := inst.ssh(t, aliceKey, "", "notifications", "list", "--all", "--json")
107 if code != 0 {
108 t.Fatalf("notifications list: %s", errOut)
109 }
110 if !strings.Contains(out, "alice/widget") {
111 t.Fatalf("inbox over ssh: %s", out)
112 }
113 _, body = browserGet(t, browser, inst.base()+"/alice/widget")
114 if !strings.Contains(body, ">Watching</button>") {
115 t.Fatalf("repo header does not show the watch state:\n%s", body)
116 }
117
118 // Anonymous visitors get the search page; the inbox sends them to log
119 // in. http.Get follows the redirect, so the login page is the proof.
120 if status, body := inst.get(t, "/notifications"); status != 200 || !strings.Contains(body, "Sign in") {
121 t.Fatalf("anonymous /notifications: %d\n%s", status, body)
122 }
123}