socle

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())
+		}
+	}
+}