e2e/search_test.go
107 lines · 4352 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10func TestSearch(t *testing.T) {
11 t.Parallel()
12 inst := startInstance(t)
13 aliceKey := inst.newKey(t, "alice")
14 bobKey := inst.newKey(t, "bob")
15 inst.admin(t, "admin", "user", "create", "alice",
16 "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
17 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
18
19 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/webapp",
20 "--description", "'a small web application'"); code != 0 {
21 t.Fatalf("repo create: %s", errOut)
22 }
23 if _, _, code := inst.ssh(t, aliceKey, "", "repo", "topics", "add", "alice/webapp", "golang"); code != 0 {
24 t.Fatal("topics add failed")
25 }
26 if _, _, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/secret", "--private",
27 "--description", "'hidden things'"); code != 0 {
28 t.Fatal("private repo create failed")
29 }
30
31 work := t.TempDir()
32 env := inst.gitEnv(aliceKey)
33 mustGit(t, work, env, "clone", inst.sshURL("alice/webapp"), "w")
34 dir := filepath.Join(work, "w")
35 os.WriteFile(filepath.Join(dir, "main.go"),
36 []byte("package main\n\nfunc Greet() string {\n\treturn \"hello, forge\"\n}\n"), 0o644)
37 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
38 mustGit(t, dir, env, "add", ".")
39 mustGit(t, dir, env, "commit", "-q", "-m", "base")
40 mustGit(t, dir, env, "push", "-q", "origin", "main")
41
42 // repo search over SSH: by name, description, and topic; visibility
43 // respected — bob never sees the private repo, alice does.
44 out, _, code := inst.ssh(t, aliceKey, "", "repo", "search", "webapp")
45 if code != 0 || !strings.Contains(out, "alice/webapp") {
46 t.Fatalf("search by name: %s", out)
47 }
48 if out, _, _ = inst.ssh(t, bobKey, "", "repo", "search", "application"); !strings.Contains(out, "alice/webapp") {
49 t.Fatalf("search by description: %s", out)
50 }
51 if out, _, _ = inst.ssh(t, bobKey, "", "repo", "search", "golang"); !strings.Contains(out, "alice/webapp") {
52 t.Fatalf("search by topic: %s", out)
53 }
54 if out, _, _ = inst.ssh(t, bobKey, "", "repo", "search", "secret"); strings.Contains(out, "alice/secret") {
55 t.Fatal("private repo leaked to bob's search")
56 }
57 if out, _, _ = inst.ssh(t, aliceKey, "", "repo", "search", "hidden"); !strings.Contains(out, "alice/secret") {
58 t.Fatalf("owner's search missed private repo: %s", out)
59 }
60 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "search", "x"); code != 2 || !strings.Contains(errOut, "2 to 200") {
61 t.Fatalf("short query: exit %d, %s", code, errOut)
62 }
63
64 // repo grep over SSH: literal, case-insensitive, 404-parity on private.
65 out, _, code = inst.ssh(t, aliceKey, "", "repo", "grep", "alice/webapp", "HELLO", "--json")
66 if code != 0 || !strings.Contains(out, `"path":"main.go"`) || !strings.Contains(out, `"line":4`) {
67 t.Fatalf("grep: %s", out)
68 }
69 if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "grep", "alice/secret", "anything"); code != 3 || !strings.Contains(errOut, "not found") {
70 t.Fatalf("private grep parity: exit %d, %s", code, errOut)
71 }
72
73 // Web: explore filter respects the query and visibility.
74 status, body := inst.get(t, "/explore?q=webapp")
75 if status != 200 || !strings.Contains(body, "alice/webapp") {
76 t.Fatalf("explore filter: %d", status)
77 }
78 _, body = inst.get(t, "/explore?q=nomatchhere")
79 if strings.Contains(body, "alice/webapp") {
80 t.Fatal("explore filter did not filter")
81 }
82 _, body = inst.get(t, "/explore?q=hidden")
83 if strings.Contains(body, "secret") {
84 t.Fatal("private repo leaked on explore search")
85 }
86
87 // Web: repo code search with mark and blob line anchor.
88 status, body = inst.get(t, "/alice/webapp/search?q=hello")
89 if status != 200 || !strings.Contains(body, `<mark>hello</mark>`) ||
90 !strings.Contains(body, `/alice/webapp/blob/main/main.go#L4">main.go:4</a>`) {
91 t.Fatalf("repo search page: %d\n%s", status, body)
92 }
93 _, body = inst.get(t, "/alice/webapp/search?q=zzznothing")
94 if !strings.Contains(body, "no matches") {
95 t.Fatal("empty state missing")
96 }
97 _, body = inst.get(t, "/alice/webapp/search?q=x")
98 if !strings.Contains(body, "2 to 200") {
99 t.Fatal("short query error missing")
100 }
101
102 // Blob line numbers are linkable anchors for the search links.
103 _, body = inst.get(t, "/alice/webapp/blob/main/main.go")
104 if !strings.Contains(body, `id="L4"`) {
105 t.Fatal("blob line anchors missing")
106 }
107}