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.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 52m ago
4b8c9e1d
| 1 | package server_test |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | voleid "rodent.software/vole/core/id" |
| 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 | |
| 34 | if _, body := f.get("/?q=notes"); !strings.Contains(body, `href="/notes"`) || strings.Contains(body, "secret") { |
| 35 | t.Fatalf("name search = %q", body) |
| 36 | } |
| 37 | if _, body := f.get("/?q=RELEASE"); !strings.Contains(body, `href="/notes"`) { |
| 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 | |
| 69 | _, page1 := f.get("/" + f.repo + "/issues") |
| 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 | } |
| 76 | _, page2 := f.get("/" + f.repo + "/issues?page=2") |
| 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 | } |
| 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 | |
| 101 | code, body := f.get("/" + f.repo + "/search?q=needle&ref=main") |
| 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 | } |
| 113 | if _, body := f.get("/" + f.repo + "/search?q=guide&ref=main"); !strings.Contains(body, "docs/guide.md") { |
| 114 | t.Fatalf("file name search missed docs/guide.md:\n%s", body) |
| 115 | } |
| 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") { |
| 117 | t.Fatalf("empty search state missing:\n%s", body) |
| 118 | } |
| 119 | if code, _ := f.get("/" + f.repo + "/search?q=needle&ref=nope"); code != 200 { |
| 120 | t.Fatalf("unknown ref = %d, want an empty search page", code) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | func TestHomeFeedMergesVisibleRepositories(t *testing.T) { |
| 125 | t.Parallel() |
| 126 | f := newFixture(t) |
| 127 | f.publish(f.repo) |
| 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.")}) |
| 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 | } |
| 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 | } |
| 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 | } |
| 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 | } |
| 155 | |
| 156 | _, body := f.get("/") |
| 157 | if !strings.Contains(body, "Public issue marker") || !strings.Contains(body, `href="/demo"`) { |
| 158 | t.Fatalf("home feed does not show the public repository:\n%s", body) |
| 159 | } |
| 160 | if strings.Contains(body, "Private issue marker") || strings.Contains(body, `href="/secret"`) { |
| 161 | t.Fatalf("home feed leaked a private repository:\n%s", body) |
| 162 | } |
| 163 | } |
| 164 |