test: handler coverage for dispatch, caching, smart HTTP, file-view modes
commit 44017bb364718c3edf45deb3c8841194ae9fcb4c · parent e4143c23e1
· browse this tree
Ximianth, 2026-10-26 20:05:02 UTC
All over the hermetic fixture repo, plus a second unpinned fixture for the file-view decision ladder (image inline, binary and oversized blobs degrade to a raw link). Each test was watched failing against a sabotaged subject before being trusted: unconditional immutable headers, a never-skipped v0 preamble, dropped gzip unwrapping, a disabled binary sniff, and errNotFound mapped to 400 each produced clean assertion failures about the right thing.
diff --git a/serve_test.go b/serve_test.go new file mode 100644 index 0000000..1d2ef95 --- /dev/null +++ b/serve_test.go @@ -0,0 +1,226 @@ +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "vitrine/display" +) + +// fixtureServer builds a server over the standard hermetic fixture repo, +// with a pinned theme seed so output stays deterministic. +func fixtureServer(t *testing.T) *server { + t.Helper() + return newServer(map[string]*repo{"fixturama": fixtureRepo(t)}, display.NewTheme("test")) +} + +// do runs one request through the handler and returns the recorded response. +// A non-nil body makes it a POST (with the given content type); otherwise a GET. +func do(s *server, target string, body []byte, hdr map[string]string) *httptest.ResponseRecorder { + method := http.MethodGet + var rd io.Reader + if body != nil { + method = http.MethodPost + rd = bytes.NewReader(body) + } + req := httptest.NewRequest(method, "http://socle.example"+target, rd) + for k, v := range hdr { + req.Header.Set(k, v) + } + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + return rec +} + +// fetchBody is a minimal protocol-v0 fetch request: want the tip, no haves, done. +func fetchBody() []byte { + var req bytes.Buffer + pktline(&req, "want "+fixtureShaMain+"\n") + req.WriteString("0000") + pktline(&req, "done\n") + return req.Bytes() +} + +func TestDispatchStatuses(t *testing.T) { + t.Parallel() + s := fixtureServer(t) + for _, c := range []struct { + target string + body []byte // non-nil means POST + want int + location string // expected Location header, when a redirect + }{ + {target: "/", want: 200}, + {target: "/-/style.css", want: 200}, + {target: "/fixturama", want: 301, location: "/fixturama/"}, + {target: "/fixturama/", want: 200}, + {target: "/fixturama.git/", want: 200}, + {target: "/nosuch/", want: 404}, + {target: "/fixturama/tree/main", want: 200}, + {target: "/fixturama/tree/main/", want: 200}, + {target: "/fixturama/tree/main/docs/", want: 200}, + {target: "/fixturama/tree/main/docs/with%20space.txt", want: 200}, + {target: "/fixturama/tree/bogus/", want: 404}, + {target: "/fixturama/tree/main..dev/", want: 400}, + {target: "/fixturama/tree/main/absent", want: 404}, + {target: "/fixturama/raw/main/README.md", want: 200}, + {target: "/fixturama/raw/main/docs", want: 404}, // a directory has no raw bytes + {target: "/fixturama/log/main", want: 200}, + {target: "/fixturama/log/bogus", want: 404}, + {target: "/fixturama/info/refs", want: 403}, + {target: "/fixturama/info/refs?service=git-receive-pack", want: 403}, + {target: "/fixturama/info/refs?service=git-upload-pack", want: 200}, + {target: "/fixturama/git-upload-pack", body: fetchBody(), want: 200}, + {target: "/fixturama/git-upload-pack", want: 405}, // fetch RPC is POST-only + {target: "/fixturama/tree/main/", body: []byte{}, want: 405}, // and nothing else accepts POST + {target: "/fixturama/README.md", want: 302, location: "/fixturama/tree/main/README.md"}, + {target: "/fixturama/docs/pic.png", want: 302, location: "/fixturama/raw/main/docs/pic.png"}, + } { + rec := do(s, c.target, c.body, nil) + if rec.Code != c.want { + t.Errorf("%s: status = %d, want %d (body: %.120s)", c.target, rec.Code, c.want, rec.Body.String()) + } + if c.location != "" && rec.Header().Get("Location") != c.location { + t.Errorf("%s: Location = %q, want %q", c.target, rec.Header().Get("Location"), c.location) + } + } +} + +func TestCacheHeaders(t *testing.T) { + t.Parallel() + s := fixtureServer(t) + const immutable = "public, max-age=31536000, immutable" + for _, c := range []struct { + target string + want string + }{ + {"/fixturama/tree/" + fixtureShaMain + "/README.md", immutable}, + {"/fixturama/raw/" + fixtureShaMain + "/README.md", immutable}, + {"/fixturama/log/" + fixtureShaMain, immutable}, + {"/fixturama/tree/main/README.md", ""}, // a branch moves; never immutable + {"/fixturama/raw/v1.0/README.md", ""}, // a tag names a commit but is not one + {"/fixturama/info/refs?service=git-upload-pack", "no-cache"}, + } { + rec := do(s, c.target, nil, nil) + if rec.Code != 200 { + t.Errorf("%s: status = %d, want 200", c.target, rec.Code) + } + if got := rec.Header().Get("Cache-Control"); got != c.want { + t.Errorf("%s: Cache-Control = %q, want %q", c.target, got, c.want) + } + } +} + +func TestRawResponse(t *testing.T) { + t.Parallel() + s := fixtureServer(t) + rec := do(s, "/fixturama/raw/main/README.md", nil, nil) + if rec.Body.String() != fixtureReadme { + t.Errorf("raw body = %q, want the README", rec.Body.String()) + } + if got := rec.Header().Get("Content-Type"); got != "text/plain; charset=utf-8" { + t.Errorf("Content-Type = %q, want text/plain (markdown must display, not download)", got) + } + if got, want := rec.Header().Get("Content-Length"), fmt.Sprint(len(fixtureReadme)); got != want { + t.Errorf("Content-Length = %q, want %s", got, want) + } +} + +func TestInfoRefsProtocolVariants(t *testing.T) { + t.Parallel() + s := fixtureServer(t) + t.Run("v0 advertisement carries the service preamble and refs", func(t *testing.T) { + rec := do(s, "/fixturama/info/refs?service=git-upload-pack", nil, nil) + body := rec.Body.String() + if !strings.HasPrefix(body, "001e# service=git-upload-pack\n0000") { + t.Errorf("v0 advertisement should start with the service pkt-line, got %.60q", body) + } + if !strings.Contains(body, "refs/heads/main") || !strings.Contains(body, fixtureShaMain) { + t.Errorf("advertisement lacks main's ref line:\n%s", body) + } + if got := rec.Header().Get("Content-Type"); got != "application/x-git-upload-pack-advertisement" { + t.Errorf("Content-Type = %q", got) + } + }) + t.Run("v2 advertisement has no preamble", func(t *testing.T) { + rec := do(s, "/fixturama/info/refs?service=git-upload-pack", nil, map[string]string{"Git-Protocol": "version=2"}) + body := rec.Body.String() + if strings.Contains(body, "# service=") { + t.Errorf("v2 advertisement must not carry the v0 preamble, got %.60q", body) + } + if !strings.Contains(body, "version 2") { + t.Errorf("expected a v2 capability advertisement:\n%s", body) + } + }) +} + +func TestUploadPackRPC(t *testing.T) { + t.Parallel() + s := fixtureServer(t) + check := func(t *testing.T, rec *httptest.ResponseRecorder) { + t.Helper() + if rec.Code != 200 { + t.Fatalf("status = %d", rec.Code) + } + if !bytes.Contains(rec.Body.Bytes(), []byte("NAK")) || !bytes.Contains(rec.Body.Bytes(), []byte("PACK")) { + t.Errorf("expected NAK and a packfile in the response, got %d bytes", rec.Body.Len()) + } + } + t.Run("plain body", func(t *testing.T) { + check(t, do(s, "/fixturama/git-upload-pack", fetchBody(), nil)) + }) + t.Run("gzip body", func(t *testing.T) { + var zbuf bytes.Buffer + zw := gzip.NewWriter(&zbuf) + zw.Write(fetchBody()) + zw.Close() + check(t, do(s, "/fixturama/git-upload-pack", zbuf.Bytes(), map[string]string{"Content-Encoding": "gzip"})) + }) + t.Run("malformed gzip is a clean 400", func(t *testing.T) { + rec := do(s, "/fixturama/git-upload-pack", []byte("not gzip"), map[string]string{"Content-Encoding": "gzip"}) + if rec.Code != 400 { + t.Errorf("status = %d, want 400", rec.Code) + } + }) +} + +// TestFileViewModes covers the file-view decision ladder with blobs the standard +// fixture doesn't carry: an image, binary data, and an oversized text file. +// These shas are deliberately not pinned; nothing here snapshots output. +func TestFileViewModes(t *testing.T) { + t.Parallel() + dir := t.TempDir() + gitFixture(t, dir, "init", "-q", "-b", "main", "--object-format=sha1", ".") + writeFixtureFile(t, dir, "logo.png", "\x89PNG\r\n\x1a\n not actually a decodable image, which is fine: only the extension matters") + writeFixtureFile(t, dir, "data.bin", "binary\x00data") + writeFixtureFile(t, dir, "huge.txt", strings.Repeat("a", display.MaxRenderSize+1)) + gitFixture(t, dir, "add", "-A") + gitFixture(t, dir, "commit", "-q", "-m", "media sampler") + rp, err := openRepo("media", repoConfig{Path: dir}) + if err != nil { + t.Fatal(err) + } + s := newServer(map[string]*repo{"media": rp}, display.NewTheme("test")) + for _, c := range []struct { + target string + want string + }{ + {"/media/tree/main/logo.png", `<img class="file" src="/media/raw/main/logo.png"`}, + {"/media/tree/main/data.bin", `binary data; <a href="/media/raw/main/data.bin">view raw</a>`}, + {"/media/tree/main/huge.txt", `too large to display inline; <a href="/media/raw/main/huge.txt">view raw</a>`}, + } { + rec := do(s, c.target, nil, nil) + if rec.Code != 200 { + t.Errorf("%s: status = %d", c.target, rec.Code) + } + if !strings.Contains(rec.Body.String(), c.want) { + t.Errorf("%s: body lacks %q:\n%.400s", c.target, c.want, rec.Body.String()) + } + } +}