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