vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
| 1 | package server_test |
| 2 | |
| 3 | // A raw download is the route a range request is for: resuming a download, |
| 4 | // previewing a large asset, or seeking in a file. These tests hold the contract: |
| 5 | // a range gets exactly its bytes with 206, a bad range is refused with 416, and |
| 6 | // a header the server does not implement still gets the whole file. |
| 7 | |
| 8 | import ( |
| 9 | "io" |
| 10 | "net/http" |
| 11 | "strconv" |
| 12 | "testing" |
| 13 | ) |
| 14 | |
| 15 | func TestRawAnswersByteRanges(t *testing.T) { |
| 16 | t.Parallel() |
| 17 | f := newFixture(t) |
| 18 | f.publish(f.repo) |
| 19 | const body = "0123456789abcdefghij" // 20 bytes |
| 20 | work := workRepo(t, map[string]string{"data.txt": body}) |
| 21 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { |
| 22 | t.Fatalf("push: %s", errs) |
| 23 | } |
| 24 | |
| 25 | get := func(rangeHeader string) (int, string, http.Header) { |
| 26 | t.Helper() |
| 27 | req, err := http.NewRequest(http.MethodGet, f.http.URL+"/"+f.repo+"/raw/main/data.txt", nil) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | if rangeHeader != "" { |
| 32 | req.Header.Set("Range", rangeHeader) |
| 33 | } |
| 34 | resp, err := http.DefaultClient.Do(req) |
| 35 | if err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | defer resp.Body.Close() |
| 39 | got, _ := io.ReadAll(resp.Body) |
| 40 | return resp.StatusCode, string(got), resp.Header |
| 41 | } |
| 42 | |
| 43 | code, got, h := get("") |
| 44 | if code != http.StatusOK || got != body { |
| 45 | t.Fatalf("no range: %d %q, want 200 %q", code, got, body) |
| 46 | } |
| 47 | if h.Get("Accept-Ranges") != "bytes" { |
| 48 | t.Fatalf("Accept-Ranges = %q, want bytes", h.Get("Accept-Ranges")) |
| 49 | } |
| 50 | if h.Get("Content-Length") != "20" { |
| 51 | t.Fatalf("Content-Length = %q, want 20", h.Get("Content-Length")) |
| 52 | } |
| 53 | |
| 54 | cases := []struct { |
| 55 | name, header, wantBody, wantRange string |
| 56 | }{ |
| 57 | {"prefix", "bytes=0-9", "0123456789", "bytes 0-9/20"}, |
| 58 | {"middle", "bytes=5-9", "56789", "bytes 5-9/20"}, |
| 59 | {"open ended", "bytes=15-", "fghij", "bytes 15-19/20"}, |
| 60 | {"end past the file", "bytes=15-99", "fghij", "bytes 15-19/20"}, |
| 61 | {"suffix", "bytes=-5", "fghij", "bytes 15-19/20"}, |
| 62 | {"suffix larger than the file", "bytes=-99", body, "bytes 0-19/20"}, |
| 63 | } |
| 64 | for _, c := range cases { |
| 65 | t.Run(c.name, func(t *testing.T) { |
| 66 | code, got, h := get(c.header) |
| 67 | if code != http.StatusPartialContent { |
| 68 | t.Fatalf("%s: status %d, want 206", c.header, code) |
| 69 | } |
| 70 | if got != c.wantBody { |
| 71 | t.Fatalf("%s: body %q, want %q", c.header, got, c.wantBody) |
| 72 | } |
| 73 | if h.Get("Content-Range") != c.wantRange { |
| 74 | t.Fatalf("%s: Content-Range = %q, want %q", c.header, h.Get("Content-Range"), c.wantRange) |
| 75 | } |
| 76 | if h.Get("Content-Length") != strconv.Itoa(len(c.wantBody)) { |
| 77 | t.Fatalf("%s: Content-Length = %q, want %d", c.header, h.Get("Content-Length"), len(c.wantBody)) |
| 78 | } |
| 79 | }) |
| 80 | } |
| 81 | |
| 82 | code, got, h = get("bytes=20-") |
| 83 | if code != http.StatusRequestedRangeNotSatisfiable { |
| 84 | t.Fatalf("range past the end: status %d, want 416", code) |
| 85 | } |
| 86 | if h.Get("Content-Range") != "bytes */20" { |
| 87 | t.Fatalf("Content-Range = %q, want bytes */20", h.Get("Content-Range")) |
| 88 | } |
| 89 | if got != "" { |
| 90 | t.Fatalf("416 body = %q, want empty", got) |
| 91 | } |
| 92 | |
| 93 | // A header the server does not implement is not an error: RFC 9110 allows |
| 94 | // answering with the whole representation. |
| 95 | for _, header := range []string{"bytes=0-1,3-4", "bytes=abc", "bytes=5-3", "items=0-1"} { |
| 96 | code, got, _ := get(header) |
| 97 | if code != http.StatusOK || got != body { |
| 98 | t.Fatalf("%q: %d %q, want 200 and the whole file", header, code, got) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 |