tinyrelayfile
tinyrelay/tinygit/cgi_test.go/

tinygit/cgi_test.go raw
1package tinygit
2
3import (
4 "bytes"
5 "compress/gzip"
6 "context"
7 "errors"
8 "io"
9 "net/http"
10 "net/http/httptest"
11 "os"
12 "path/filepath"
13 "strconv"
14 "strings"
15 "testing"
16 "time"
17)
18
19func TestCGIFailureAfterHeadersPreservesPartialPack(t *testing.T) {
20 dir := t.TempDir()
21 script := "#!/bin/sh\nprintf 'Content-Type: application/octet-stream\\r\\n\\r\\npartial-pack'\nprintf 'backend failed' >&2\nexit 1\n"
22 if err := os.WriteFile(filepath.Join(dir, "git"), []byte(script), 0700); err != nil {
23 t.Fatal(err)
24 }
25 t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
26 g := &GitRelay{root: dir}
27 w := httptest.NewRecorder()
28 err := g.cgi(httptest.NewRequest(http.MethodGet, "/", nil), w, Repository{Owner: "owner", Identifier: "test"}, "/git-upload-pack")
29 var streamed *cgiStreamError
30 if !errors.As(err, &streamed) || !strings.Contains(err.Error(), "backend failed") {
31 t.Fatalf("backend failure did not retain committed-response state: %v", err)
32 }
33 if got := w.Body.String(); got != "partial-pack" {
34 t.Fatalf("error corrupted pack response: %q", got)
35 }
36}
37
38func TestCGIGzipInputDecodesAndRemovesSpool(t *testing.T) {
39 want := strings.Repeat("object negotiation\n", 10000)
40 var compressed bytes.Buffer
41 zw := gzip.NewWriter(&compressed)
42 if _, err := io.WriteString(zw, want); err != nil {
43 t.Fatal(err)
44 }
45 if err := zw.Close(); err != nil {
46 t.Fatal(err)
47 }
48 req := httptest.NewRequest(http.MethodPost, "/", &compressed)
49 req.Header.Set("Content-Encoding", "gzip")
50 body, length, cleanup, err := cgiInput(req)
51 if err != nil {
52 t.Fatal(err)
53 }
54 defer cleanup()
55 got, err := io.ReadAll(body)
56 if err != nil || string(got) != want || length != strconv.Itoa(len(want)) {
57 t.Fatalf("decoded request: length=%s bytes=%d err=%v", length, len(got), err)
58 }
59 file, ok := body.(*os.File)
60 if !ok {
61 t.Fatal("compressed request did not use a disk spool")
62 }
63 cleanup()
64 if _, err := os.Stat(file.Name()); !errors.Is(err, os.ErrNotExist) {
65 t.Fatalf("temporary request remains after cleanup: %v", err)
66 }
67}
68
69// A backend that waits for more input after its first output must not require
70// the whole pack to fit in memory before the client can receive any of it.
71func TestCGIStreamsBeforeBackendExits(t *testing.T) {
72 dir := t.TempDir()
73 script := "#!/bin/sh\nprintf 'Content-Type: application/octet-stream\\r\\n\\r\\nfirst'\nread release\nprintf 'last'\n"
74 if err := os.WriteFile(filepath.Join(dir, "git"), []byte(script), 0700); err != nil {
75 t.Fatal(err)
76 }
77 t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
78 input, release := io.Pipe()
79 defer input.Close()
80 defer release.Close()
81 g := &GitRelay{root: dir}
82 finished := make(chan error, 1)
83 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84 r.Body = input
85 finished <- g.cgi(r, w, Repository{Owner: "owner", Identifier: "test"}, "/git-upload-pack")
86 }))
87 defer server.Close()
88 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
89 defer cancel()
90 req, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)
91 if err != nil {
92 t.Fatal(err)
93 }
94 response, err := server.Client().Do(req)
95 if err != nil {
96 _ = release.Close()
97 t.Fatalf("backend output was not streamed: %v", err)
98 }
99 defer response.Body.Close()
100 first := make([]byte, 5)
101 if _, err := io.ReadFull(response.Body, first); err != nil || string(first) != "first" {
102 _ = release.Close()
103 t.Fatalf("first backend chunk = %q, %v", first, err)
104 }
105 if _, err := io.WriteString(release, "continue\n"); err != nil {
106 t.Fatal(err)
107 }
108 _ = release.Close()
109 rest, err := io.ReadAll(response.Body)
110 if err != nil || string(rest) != "last" {
111 t.Fatalf("remaining backend output = %q, %v", rest, err)
112 }
113 if err := <-finished; err != nil {
114 t.Fatal(err)
115 }
116}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs