vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
|
5cab7b18
Keenan Nemetz
15d ago
|
1 | package server_test |
| 2 | ||
| 3 | import ( | |
| 4 | "fmt" | |
| 5 | "strings" | |
| 6 | "testing" | |
| 7 | "time" | |
| 8 | ||
|
d37d301f
Keenan Nemetz
16h ago
|
9 | voleid "rodent.software/vole/core/id" |
|
5cab7b18
Keenan Nemetz
15d ago
|
10 | ) |
| 11 | ||
| 12 | func TestRepositorySearchOnlyFindsVisibleRepositories(t *testing.T) { | |
| 13 | t.Parallel() | |
| 14 | f := newFixture(t) | |
| 15 | f.publish(f.repo) | |
| 16 | ||
| 17 | notes, err := f.srv.Repos.Create("notes", f.ownerID, "release notes and drafts") | |
| 18 | if err != nil { | |
| 19 | t.Fatal(err) | |
| 20 | } | |
| 21 | if err := f.srv.Index.Upsert(notes); err != nil { | |
| 22 | t.Fatal(err) | |
| 23 | } | |
| 24 | f.publish("notes") | |
| 25 | ||
| 26 | secret, err := f.srv.Repos.Create("secret", f.ownerID, "hidden roadmap") | |
| 27 | if err != nil { | |
| 28 | t.Fatal(err) | |
| 29 | } | |
| 30 | if err := f.srv.Index.Upsert(secret); err != nil { | |
| 31 | t.Fatal(err) | |
| 32 | } | |
| 33 | ||
|
ed03b3d1
Keenan Nemetz
3h ago
|
34 | if _, body := f.get("/?q=notes"); !strings.Contains(body, `href="/notes"`) || strings.Contains(body, "secret") { |
|
5cab7b18
Keenan Nemetz
15d ago
|
35 | t.Fatalf("name search = %q", body) |
| 36 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
37 | if _, body := f.get("/?q=RELEASE"); !strings.Contains(body, `href="/notes"`) { |
|
5cab7b18
Keenan Nemetz
15d ago
|
38 | t.Fatalf("case-insensitive description search missed notes:\n%s", body) |
| 39 | } | |
| 40 | if _, body := f.get("/?q=hidden"); strings.Contains(body, "secret") { | |
| 41 | t.Fatalf("search leaked a private repository:\n%s", body) | |
| 42 | } | |
| 43 | if _, body := f.get("/?q=nothingmatches"); !strings.Contains(body, "No repositories match") { | |
| 44 | t.Fatalf("empty search state missing:\n%s", body) | |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | func TestIssueListPaginates(t *testing.T) { | |
| 49 | t.Parallel() | |
| 50 | f := newFixture(t) | |
| 51 | f.publish(f.repo) | |
| 52 | ||
| 53 | files := map[string]string{} | |
| 54 | now := time.Now().UTC() | |
| 55 | for i := 1; i <= 27; i++ { | |
| 56 | id, err := voleid.New(now.Add(time.Duration(i) * time.Millisecond)) | |
| 57 | if err != nil { | |
| 58 | t.Fatal(err) | |
| 59 | } | |
| 60 | files["issues/"+id+"/issue.md"] = issueFile( | |
| 61 | fmt.Sprintf("Issue %02d", i), "keenan", "open", "2026-10-22T21:00:00Z", nil, "body\n", | |
| 62 | ) | |
| 63 | } | |
| 64 | work := f.issuesRepo(t, files) | |
| 65 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "HEAD:refs/heads/vole"); err != nil { | |
| 66 | t.Fatalf("issues push: %s", errs) | |
| 67 | } | |
| 68 | ||
|
ed03b3d1
Keenan Nemetz
3h ago
|
69 | _, page1 := f.get("/" + f.repo + "/issues") |
|
5cab7b18
Keenan Nemetz
15d ago
|
70 | if !strings.Contains(page1, "Issue 27") || strings.Contains(page1, "Issue 01") { |
| 71 | t.Fatalf("page 1 has the wrong issues:\n%s", page1) | |
| 72 | } | |
| 73 | if !strings.Contains(page1, "page 1 of 2") || !strings.Contains(page1, ">Next<") { | |
| 74 | t.Fatalf("page 1 has no next link:\n%s", page1) | |
| 75 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
76 | _, page2 := f.get("/" + f.repo + "/issues?page=2") |
|
5cab7b18
Keenan Nemetz
15d ago
|
77 | if !strings.Contains(page2, "Issue 01") || !strings.Contains(page2, "Issue 02") { |
| 78 | t.Fatalf("page 2 is missing the oldest issues:\n%s", page2) | |
| 79 | } | |
| 80 | if strings.Contains(page2, "Issue 27") { | |
| 81 | t.Fatalf("page 2 repeated the newest issue:\n%s", page2) | |
| 82 | } | |
| 83 | if !strings.Contains(page2, ">Previous<") { | |
| 84 | t.Fatalf("page 2 has no previous link:\n%s", page2) | |
| 85 | } | |
| 86 | } | |
|
aef99284
Keenan Nemetz
15d ago
|
87 | |
| 88 | func TestCodeSearchFindsNamesAndContents(t *testing.T) { | |
| 89 | t.Parallel() | |
| 90 | f := newFixture(t) | |
| 91 | f.publish(f.repo) | |
| 92 | work := workRepo(t, map[string]string{ | |
| 93 | "main.go": "package main\n// HelloNeedle lives here\n", | |
| 94 | "docs/guide.md": "# Guide\nneedle in docs\n", | |
| 95 | "README.md": "nothing to find\n", | |
| 96 | }) | |
| 97 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | |
| 98 | t.Fatalf("push: %s", errs) | |
| 99 | } | |
| 100 | ||
|
ed03b3d1
Keenan Nemetz
3h ago
|
101 | code, body := f.get("/" + f.repo + "/search?q=needle&ref=main") |
|
aef99284
Keenan Nemetz
15d ago
|
102 | if code != 200 { |
| 103 | t.Fatalf("search page = %d", code) | |
| 104 | } | |
| 105 | for _, want := range []string{"main.go", "docs/guide.md", "HelloNeedle", "needle in docs"} { | |
| 106 | if !strings.Contains(body, want) { | |
| 107 | t.Fatalf("content search does not show %q:\n%s", want, body) | |
| 108 | } | |
| 109 | } | |
| 110 | if !strings.Contains(body, "main.go#L2") { | |
| 111 | t.Fatalf("content search has no line anchor:\n%s", body) | |
| 112 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
113 | if _, body := f.get("/" + f.repo + "/search?q=guide&ref=main"); !strings.Contains(body, "docs/guide.md") { |
|
aef99284
Keenan Nemetz
15d ago
|
114 | t.Fatalf("file name search missed docs/guide.md:\n%s", body) |
| 115 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
116 | if _, body := f.get("/" + f.repo + "/search?q=nothingmatches&ref=main"); !strings.Contains(body, "No file names match") || !strings.Contains(body, "No file content matches") { |
|
aef99284
Keenan Nemetz
15d ago
|
117 | t.Fatalf("empty search state missing:\n%s", body) |
| 118 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
119 | if code, _ := f.get("/" + f.repo + "/search?q=needle&ref=nope"); code != 200 { |
|
aef99284
Keenan Nemetz
15d ago
|
120 | t.Fatalf("unknown ref = %d, want an empty search page", code) |
| 121 | } | |
| 122 | } | |
|
b355db72
Keenan Nemetz
15d ago
|
123 | |
| 124 | func TestHomeFeedMergesVisibleRepositories(t *testing.T) { | |
| 125 | t.Parallel() | |
| 126 | f := newFixture(t) | |
| 127 | f.publish(f.repo) | |
|
6ce635a8
Keenan Nemetz
1d ago
|
128 | // The rail sits beside the introduction card; without one the page is the |
| 129 | // repository list alone. | |
| 130 | f.instanceRepo(t, map[string][]byte{"profile/README.md": []byte("What this forge is for.")}) | |
|
b355db72
Keenan Nemetz
15d ago
|
131 | work := workRepo(t, map[string]string{"readme.md": "public\n"}) |
| 132 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | |
| 133 | t.Fatalf("public push: %s", errs) | |
| 134 | } | |
|
074ae160
Keenan Nemetz
14d ago
|
135 | publicIssues := f.issuesRepo(t, issueFiles("01M5JR2JK29KKMAA1T9MDNTPE1", "Public issue marker", "body\n")) |
| 136 | if _, errs, err := f.git(publicIssues, "push", f.sshURL(f.repo), "HEAD:refs/heads/vole"); err != nil { | |
| 137 | t.Fatalf("public issues push: %s", errs) | |
| 138 | } | |
|
b355db72
Keenan Nemetz
15d ago
|
139 | |
| 140 | secret, err := f.srv.Repos.Create("secret", f.ownerID, "private") | |
| 141 | if err != nil { | |
| 142 | t.Fatal(err) | |
| 143 | } | |
| 144 | if err := f.srv.Index.Upsert(secret); err != nil { | |
| 145 | t.Fatal(err) | |
| 146 | } | |
| 147 | privateWork := workRepo(t, map[string]string{"hidden.md": "private\n"}) | |
| 148 | if _, errs, err := f.git(privateWork, "push", f.sshURL("secret"), "main:refs/heads/main"); err != nil { | |
| 149 | t.Fatalf("private push: %s", errs) | |
| 150 | } | |
|
074ae160
Keenan Nemetz
14d ago
|
151 | privateIssues := f.issuesRepo(t, issueFiles("01M5JR2JK29KKMAA1T9MDNTPE2", "Private issue marker", "body\n")) |
| 152 | if _, errs, err := f.git(privateIssues, "push", f.sshURL("secret"), "HEAD:refs/heads/vole"); err != nil { | |
| 153 | t.Fatalf("private issues push: %s", errs) | |
| 154 | } | |
|
b355db72
Keenan Nemetz
15d ago
|
155 | |
| 156 | _, body := f.get("/") | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
157 | if !strings.Contains(body, "Public issue marker") || !strings.Contains(body, `href="/demo"`) { |
|
b355db72
Keenan Nemetz
15d ago
|
158 | t.Fatalf("home feed does not show the public repository:\n%s", body) |
| 159 | } | |
|
ed03b3d1
Keenan Nemetz
3h ago
|
160 | if strings.Contains(body, "Private issue marker") || strings.Contains(body, `href="/secret"`) { |
|
b355db72
Keenan Nemetz
15d ago
|
161 | t.Fatalf("home feed leaked a private repository:\n%s", body) |
| 162 | } | |
| 163 | } |