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 · 24m ago
4b8c9e1d
| 1 | package metadata |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | // The paths a fuzzed document arrives at. They have to satisfy the tree shape the |
| 9 | // loader expects, so only the file contents move. |
| 10 | const ( |
| 11 | fuzzIssuePath = IssuesDir + "/01M5XCJ4VYKJXTBJXC387WE922/" + issueName |
| 12 | fuzzCommentPath = IssuesDir + "/01M5XCJ4VYKJXTBJXC387WE922/comments/" + "01M5XCHRQFT0FTM0CJYRYWVD3V" + ".md" |
| 13 | ) |
| 14 | |
| 15 | // FuzzParseIssue drives the reader that turns repository bytes into an issue. A |
| 16 | // push decides what it sees, so a panic here is an outage and a disagreement |
| 17 | // between reading and writing is worse: the server rewrites the file on every |
| 18 | // edit, and an issue it can read but not write back loses whatever it could not |
| 19 | // express. |
| 20 | // |
| 21 | // The property is therefore a round trip on the accepted inputs only. Refusing is |
| 22 | // always allowed; accepting and then failing to write is not. |
| 23 | func FuzzParseIssue(f *testing.F) { |
| 24 | for _, seed := range []string{ |
| 25 | "---\ntitle: An example\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\nstate: open\n---\nA body.\n", |
| 26 | "---\ntitle: An example\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\nlabels:\n - bug\n---\n", |
| 27 | // A body that starts like frontmatter is the shape that breaks a naive |
| 28 | // reader, and a title holding a newline is the shape that breaks a writer. |
| 29 | "---\ntitle: T\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n---\ntitle: inner\n---\n", |
| 30 | "---\ntitle: 'line one\n line two'\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 31 | "---\ntitle: T\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\r\nbody\r\n", |
| 32 | "---\ntitle: T\nauthor: a\ncreated: 2026-10-30T00:00:00Z\nstate: wedged\n---\n", |
| 33 | "---\ntitle: T\ntitle: again\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 34 | "---\ntitle: T\nauthor: a\ncreated: not-a-time\n---\n", |
| 35 | "---\n---\n", |
| 36 | "", |
| 37 | "no frontmatter at all\n", |
| 38 | "---\ntitle: T\nauthor: a\ncreated: 2026-10-30T00:00:00Z\nlabels: [a, a, a]\n---\nbody\x00with a nul\n", |
| 39 | "---\ntitle: \xff\xfe\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 40 | } { |
| 41 | f.Add(seed) |
| 42 | } |
| 43 | f.Fuzz(func(t *testing.T, raw string) { |
| 44 | issues, err := ParseIssues(map[string]string{fuzzIssuePath: raw}) |
| 45 | if err != nil { |
| 46 | return |
| 47 | } |
| 48 | if len(issues) != 1 { |
| 49 | t.Fatalf("one issue file gave %d issues", len(issues)) |
| 50 | } |
| 51 | iss := issues[0] |
| 52 | rendered, err := renderIssue(iss) |
| 53 | if err != nil { |
| 54 | t.Fatalf("an accepted issue does not render: %v", err) |
| 55 | } |
| 56 | again, err := ParseIssues(map[string]string{fuzzIssuePath: string(rendered)}) |
| 57 | if err != nil { |
| 58 | t.Fatalf("the issue written by renderIssue is refused: %v\n%s", err, rendered) |
| 59 | } |
| 60 | if len(again) != 1 { |
| 61 | t.Fatalf("the issue written by renderIssue loaded as %d issues", len(again)) |
| 62 | } |
| 63 | got := again[0] |
| 64 | if got.Title != iss.Title || got.State != iss.State || got.Author != iss.Author { |
| 65 | t.Fatalf("%q: the round trip changed the header: %+v then %+v", raw, iss, got) |
| 66 | } |
| 67 | if !got.Created.Equal(iss.Created) { |
| 68 | t.Fatalf("%q: created became %s, was %s", raw, got.Created, iss.Created) |
| 69 | } |
| 70 | // The writer trims the trailing newlines of a body, so the comparison is on |
| 71 | // the trimmed form: what matters is that no other byte moves. |
| 72 | if body := strings.TrimRight(got.Description, "\n"); body != strings.TrimRight(iss.Description, "\n") { |
| 73 | t.Fatalf("%q: the round trip changed the body: %q then %q", raw, iss.Description, got.Description) |
| 74 | } |
| 75 | if strings.Join(got.Labels, ",") != strings.Join(iss.Labels, ",") { |
| 76 | t.Fatalf("%q: labels became %v, were %v", raw, got.Labels, iss.Labels) |
| 77 | } |
| 78 | // Writing an issue twice has to be a fixed point, or every edit rewrites the |
| 79 | // file and the diff shows work nobody did. |
| 80 | renderedAgain, err := renderIssue(got) |
| 81 | if err != nil { |
| 82 | t.Fatalf("the second write failed: %v", err) |
| 83 | } |
| 84 | if string(renderedAgain) != string(rendered) { |
| 85 | t.Fatalf("%q: writing twice differed:\nfirst:\n%s\nsecond:\n%s", raw, rendered, renderedAgain) |
| 86 | } |
| 87 | }) |
| 88 | } |
| 89 | |
| 90 | // FuzzParseComment is the same property for a comment, which is edited far more |
| 91 | // often than an issue body and holds code blocks, quoted frontmatter, and |
| 92 | // whatever a person pastes. |
| 93 | func FuzzParseComment(f *testing.F) { |
| 94 | for _, seed := range []string{ |
| 95 | "---\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\n---\nA reply.\n", |
| 96 | "---\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\nreply_to: 01M5XCHRQFT0FTM0CJYRYWVD3V\n---\nA reply.\n", |
| 97 | "---\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\ndeleted: true\n---\n", |
| 98 | "---\nauthor: keenan\ncreated: 2026-10-30T00:00:00Z\npath: a/b.go\nside: right\nline: 12\nblob: 6beaa53\n---\nx\n", |
| 99 | // A body that looks like frontmatter, and one that ends where the writer |
| 100 | // puts its delimiter. |
| 101 | "---\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n---\nauthor: b\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 102 | "---\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n```\n---\n```\n", |
| 103 | "---\nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 104 | "---\nauthor: \nauthor: a\ncreated: 2026-10-30T00:00:00Z\n---\n", |
| 105 | "---\nauthor: a\ncreated: 2026-10-30T00:00:00Z\nline: not a number\n---\n", |
| 106 | "", |
| 107 | "---\n---\n", |
| 108 | } { |
| 109 | f.Add(seed) |
| 110 | } |
| 111 | f.Fuzz(func(t *testing.T, raw string) { |
| 112 | c, problem := ParseComment("01M5XCHRQFT0FTM0CJYRYWVD3V", raw, fuzzCommentPath) |
| 113 | if problem != "" { |
| 114 | return |
| 115 | } |
| 116 | rendered, err := RenderComment(&c) |
| 117 | if err != nil { |
| 118 | t.Fatalf("an accepted comment does not render: %v", err) |
| 119 | } |
| 120 | again, problem := ParseComment(c.ID, string(rendered), fuzzCommentPath) |
| 121 | if problem != "" { |
| 122 | t.Fatalf("the comment written by RenderComment is refused: %s\n%s", problem, rendered) |
| 123 | } |
| 124 | if again.Author != c.Author || again.ReplyTo != c.ReplyTo || again.Deleted != c.Deleted { |
| 125 | t.Fatalf("%q: the round trip changed the header: %+v then %+v", raw, c, again) |
| 126 | } |
| 127 | // A file the server wrote ends with a newline, and Split keeps it in the body. |
| 128 | // A hand-written file that ends without one is therefore normalized on the |
| 129 | // first write: the body gains nothing but that newline, which is why the |
| 130 | // comparison is on the trimmed form. Every caller trims before it writes, so |
| 131 | // this is the shape of the format, not a difference anyone sees. |
| 132 | if body := strings.TrimRight(again.Body, "\n"); body != strings.TrimRight(c.Body, "\n") { |
| 133 | t.Fatalf("%q: the body became %q, was %q", raw, again.Body, c.Body) |
| 134 | } |
| 135 | // The fixed point is the real invariant: once the file has been written by the |
| 136 | // server, reading and writing it again must not change a byte. |
| 137 | renderedAgain, err := RenderComment(&again) |
| 138 | if err != nil { |
| 139 | t.Fatalf("the second write failed: %v", err) |
| 140 | } |
| 141 | if string(renderedAgain) != string(rendered) { |
| 142 | t.Fatalf("%q: writing twice differed:\nfirst:\n%s\nsecond:\n%s", raw, rendered, renderedAgain) |
| 143 | } |
| 144 | }) |
| 145 | } |
| 146 |