133728e barerepo 1mo
1
package e2e
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"io"
133728e barerepo 1mo
5
"net/http"
133728e barerepo 1mo
6
"net/url"
133728e barerepo 1mo
7
"os/exec"
133728e barerepo 1mo
8
"strings"
133728e barerepo 1mo
9
"testing"
133728e barerepo 1mo
12
// find runs a search as one account, or as nobody when account is empty.
133728e barerepo 1mo
13
func find(t *testing.T, in *instance, account, query string) string {
133728e barerepo 1mo
14
t.Helper()
133728e barerepo 1mo
15
req, err := http.NewRequest(http.MethodGet,
133728e barerepo 1mo
16
in.http.URL+"/search?q="+url.QueryEscape(query), nil)
133728e barerepo 1mo
17
if err != nil {
133728e barerepo 1mo
18
t.Fatal(err)
133728e barerepo 1mo
20
if account != "" {
133728e barerepo 1mo
21
token, err := in.db.NewSession(t.Context(), account)
133728e barerepo 1mo
22
if err != nil {
133728e barerepo 1mo
23
t.Fatal(err)
133728e barerepo 1mo
25
req.AddCookie(&http.Cookie{Name: "barerepo_session", Value: token})
133728e barerepo 1mo
27
resp, err := http.DefaultClient.Do(req)
133728e barerepo 1mo
28
if err != nil {
133728e barerepo 1mo
29
t.Fatal(err)
133728e barerepo 1mo
31
defer resp.Body.Close()
133728e barerepo 1mo
32
body, err := io.ReadAll(resp.Body)
133728e barerepo 1mo
33
if err != nil {
133728e barerepo 1mo
34
t.Fatal(err)
133728e barerepo 1mo
36
return string(body)
133728e barerepo 1mo
39
// The highest severity mistake available here is a private match reaching somebody else. 17, BUILD.md.
133728e barerepo 1mo
40
func TestSearchNeverShowsAPrivateRepositoryToAStranger(t *testing.T) {
133728e barerepo 1mo
41
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
42
t.Skip("git is not installed")
133728e barerepo 1mo
44
in := newInstance(t)
133728e barerepo 1mo
45
john := in.account("john")
133728e barerepo 1mo
46
in.account("lisa")
133728e barerepo 1mo
48
work := t.TempDir()
133728e barerepo 1mo
49
run(t, work, "git", "init", "-q", "-b", "master", work)
133728e barerepo 1mo
50
write(t, work, "secret.go", "package main\n\nconst launchCodeZebra = 1\n")
133728e barerepo 1mo
51
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"private\"\n")
133728e barerepo 1mo
52
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
53
run(t, work, "git", "commit", "-qm", "first")
133728e barerepo 1mo
54
run(t, work, "git", "push", "-q", in.url(john, "/john/vault"), "master")
133728e barerepo 1mo
56
if body := find(t, in, "john", "launchCodeZebra"); !strings.Contains(body, "secret.go") {
133728e barerepo 1mo
57
t.Error("the owner cannot find their own private code, which makes search useless to them")
133728e barerepo 1mo
59
for _, who := range []string{"", "lisa"} {
133728e barerepo 1mo
60
// The query itself is echoed into the search box, so the file name is what proves a leak.
133728e barerepo 1mo
61
body := find(t, in, who, "launchCodeZebra")
133728e barerepo 1mo
62
if strings.Contains(body, "secret.go") {
133728e barerepo 1mo
63
t.Errorf("a private match leaked to %q:\n%s", who, body)
133728e barerepo 1mo
65
if !strings.Contains(body, "nothing matched") {
133728e barerepo 1mo
66
t.Errorf("%q was not told the search found nothing:\n%s", who, body)
133728e barerepo 1mo
70
// [access] push grants read, per chapter 18, so lisa sees it once she is listed.
133728e barerepo 1mo
71
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"private\"\n\n[access]\npush = [\"lisa\"]\n")
133728e barerepo 1mo
72
run(t, work, "git", "commit", "-qam", "let lisa in")
133728e barerepo 1mo
73
run(t, work, "git", "push", "-q", in.url(john, "/john/vault"), "master")
133728e barerepo 1mo
74
if body := find(t, in, "lisa", "launchCodeZebra"); !strings.Contains(body, "secret.go") {
133728e barerepo 1mo
75
t.Error("a named reader still cannot find the code they may read")
133728e barerepo 1mo
77
if body := find(t, in, "", "launchCodeZebra"); strings.Contains(body, "secret.go") {
133728e barerepo 1mo
78
t.Error("granting one reader published the repository")
133728e barerepo 1mo
82
// A path outside ascii has two spellings in git's output, and the index must hold the real one.
133728e barerepo 1mo
83
func TestTheIndexHoldsANameOutsideAscii(t *testing.T) {
133728e barerepo 1mo
84
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
85
t.Skip("git is not installed")
133728e barerepo 1mo
87
in := newInstance(t)
133728e barerepo 1mo
88
john := in.account("john")
133728e barerepo 1mo
89
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
91
write(t, work, "café.md", "# une note\n\nzircon is in here\n")
133728e barerepo 1mo
92
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
93
run(t, work, "git", "commit", "-qm", "add a note with an accent")
133728e barerepo 1mo
94
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
96
body := find(t, in, "", "zircon")
133728e barerepo 1mo
97
if !strings.Contains(body, "café.md") {
133728e barerepo 1mo
98
t.Errorf("the index spelled the path some other way:\n%s", body)
133728e barerepo 1mo
100
if strings.Contains(body, `\303`) {
133728e barerepo 1mo
101
t.Errorf("the index kept git's octal spelling of the name:\n%s", body)
133728e barerepo 1mo
102
}
133728e barerepo 1mo
105
// Chapter 17 indexes on push, so what a push changed is what a search finds afterwards.
133728e barerepo 1mo
106
func TestTheIndexFollowsThePush(t *testing.T) {
133728e barerepo 1mo
107
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
108
t.Skip("git is not installed")
133728e barerepo 1mo
109
}
133728e barerepo 1mo
110
in := newInstance(t)
133728e barerepo 1mo
111
john := in.account("john")
133728e barerepo 1mo
112
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
114
write(t, work, "retry.go", "package main\n\nfunc backoff(n int) int { return n * 2 }\n")
133728e barerepo 1mo
115
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
116
run(t, work, "git", "commit", "-qm", "add a backoff")
133728e barerepo 1mo
117
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
119
if body := find(t, in, "", "backoff"); !strings.Contains(body, "retry.go") {
133728e barerepo 1mo
120
t.Fatalf("a pushed file is not in the index:\n%s", body)
133728e barerepo 1mo
121
}
133728e barerepo 1mo
123
// An edit must remove what was there, or search answers with a line that no longer exists.
133728e barerepo 1mo
124
write(t, work, "retry.go", "package main\n\nfunc linger(n int) int { return n * 3 }\n")
133728e barerepo 1mo
125
run(t, work, "git", "commit", "-qam", "rename it")
133728e barerepo 1mo
126
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
127
if body := find(t, in, "", "backoff"); strings.Contains(body, "retry.go") {
133728e barerepo 1mo
128
t.Errorf("the index still holds a line the push replaced:\n%s", body)
133728e barerepo 1mo
129
}
133728e barerepo 1mo
130
if body := find(t, in, "", "linger"); !strings.Contains(body, "retry.go") {
133728e barerepo 1mo
131
t.Errorf("the index did not take the new line:\n%s", body)
133728e barerepo 1mo
132
}
133728e barerepo 1mo
134
// A deleted file must leave, or every search result is a link to a 404.
133728e barerepo 1mo
135
run(t, work, "git", "rm", "-q", "retry.go")
133728e barerepo 1mo
136
run(t, work, "git", "commit", "-qm", "drop it")
133728e barerepo 1mo
137
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
138
if body := find(t, in, "", "linger"); strings.Contains(body, "retry.go") {
133728e barerepo 1mo
139
t.Errorf("a deleted file is still in the index:\n%s", body)
133728e barerepo 1mo
140
}
133728e barerepo 1mo
143
// Chapter 17 indexes thread comments on note write, and a comment written on the web is a note write.
133728e barerepo 1mo
144
func TestACommentWrittenOnTheWebIsSearchable(t *testing.T) {
133728e barerepo 1mo
145
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
146
t.Skip("git is not installed")
133728e barerepo 1mo
147
}
133728e barerepo 1mo
148
in := newInstance(t)
133728e barerepo 1mo
149
john := in.account("john")
133728e barerepo 1mo
150
seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
152
resp := post(t, in, "john", "/john/johnbot/threads", url.Values{
133728e barerepo 1mo
153
"title": {"the reconnect loop spins"},
133728e barerepo 1mo
154
"body": {"it retries with no delay at all, which pins a core"},
133728e barerepo 1mo
155
})
133728e barerepo 1mo
156
if resp.StatusCode != http.StatusFound {
133728e barerepo 1mo
157
t.Fatalf("opening a thread answered %d", resp.StatusCode)
133728e barerepo 1mo
158
}
133728e barerepo 1mo
159
body := find(t, in, "", "pins a core")
133728e barerepo 1mo
160
if !strings.Contains(body, "the reconnect loop spins") {
133728e barerepo 1mo
161
t.Errorf("a comment written on the web is not in the index:\n%s", body)
133728e barerepo 1mo
162
}
133728e barerepo 1mo
163
if !strings.Contains(body, "thread") {
133728e barerepo 1mo
164
t.Errorf("the match did not come back as a thread:\n%s", body)
133728e barerepo 1mo
165
}
133728e barerepo 1mo
168
// The index is derived from git, so appendix E's reindex must rebuild it from nothing.
133728e barerepo 1mo
169
func TestReindexRebuildsTheWholeIndex(t *testing.T) {
133728e barerepo 1mo
170
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
171
t.Skip("git is not installed")
133728e barerepo 1mo
172
}
133728e barerepo 1mo
173
in := newInstance(t)
133728e barerepo 1mo
174
john := in.account("john")
133728e barerepo 1mo
175
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
176
write(t, work, "retry.go", "package main\n\nfunc backoff(n int) int { return n * 2 }\n")
133728e barerepo 1mo
177
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
178
run(t, work, "git", "commit", "-qm", "add a backoff")
133728e barerepo 1mo
179
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")
133728e barerepo 1mo
181
if _, err := in.db.ExecContext(t.Context(), `DELETE FROM search_docs`); err != nil {
133728e barerepo 1mo
182
t.Fatal(err)
133728e barerepo 1mo
183
}
133728e barerepo 1mo
184
if body := find(t, in, "", "backoff"); strings.Contains(body, "retry.go") {
133728e barerepo 1mo
185
t.Fatal("the index was not actually emptied, so this test proves nothing")
133728e barerepo 1mo
186
}
133728e barerepo 1mo
188
out := run(t, "", binary, "doctor", "--config", in.cfg.Path, "--reindex")
133728e barerepo 1mo
189
if !strings.Contains(out, "indexed 1 repositories") {
133728e barerepo 1mo
190
t.Errorf("reindex said %q", out)
133728e barerepo 1mo
191
}
133728e barerepo 1mo
192
if body := find(t, in, "", "backoff"); !strings.Contains(body, "retry.go") {
133728e barerepo 1mo
193
t.Errorf("reindex did not rebuild the code index:\n%s", body)
133728e barerepo 1mo
194
}
133728e barerepo 1mo
197
// Chapter 44.4 keeps a deleted repository for 30 days, and it must not be searchable while it waits.
133728e barerepo 1mo
198
func TestADeletedRepositoryLeavesTheIndex(t *testing.T) {
133728e barerepo 1mo
199
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
200
t.Skip("git is not installed")
133728e barerepo 1mo
201
}
133728e barerepo 1mo
202
in := newInstance(t)
133728e barerepo 1mo
203
john := in.account("john")
133728e barerepo 1mo
204
work := seed(t, in, john, "john", "johnbot")
133728e barerepo 1mo
205
write(t, work, "retry.go", "package main\n\nfunc backoff(n int) int { return n * 2 }\n")
133728e barerepo 1mo
206
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
207
run(t, work, "git", "commit", "-qm", "add a backoff")
133728e barerepo 1mo
208
run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master")