clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / internal / daemon / git_proxy_push_test.go
git_proxy_push_test.go 3.5K
1package daemon23import (4 "context"5 "crypto/rand"6 "io"7 "net/http/httptest"8 "net/http/httputil"9 "net/url"10 "os"11 "path/filepath"12 "strings"13 "testing"14 "time"1516 "github.com/FelineStateMachine/tinyrelay/internal/event"17 "github.com/FelineStateMachine/tinyrelay/internal/relay"18)1920// A reverse proxy without full duplex HTTP/1.1 discards the unread request21// body as soon as the backend starts responding. git-http-backend writes its22// headers before it reads the pack, so the relay must hold the response until23// git produces body bytes or a large push arrives truncated at the proxy.24func TestLargePushSurvivesHalfDuplexReverseProxy(t *testing.T) {25 ctx := context.Background()26 secret := strings.Repeat("0", 63) + "1"27 owner, err := event.PublicKey(secret)28 if err != nil {29 t.Fatal(err)30 }31 app, tenant := testTenant(t)32 p := tenant.Policy()33 p.Features.Grasp = true34 if err := tenant.applyPolicy(ctx, p); err != nil {35 t.Fatal(err)36 }37 const ownerNPub = "npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d"38 announcement := event.Event{Kind: event.KIND_REPO, CreatedAt: time.Now().Unix(), Tags: [][]string{{"d", "big"}, {"clone", "http://relay.test/" + ownerNPub + "/big.git"}, {"relays", "ws://relay.test"}}}39 if err := event.Sign(&announcement, secret); err != nil {40 t.Fatal(err)41 }42 if _, err := tenant.router.Publish(ctx, announcement, relay.Session{PubKeys: []string{owner}}); err != nil {43 t.Fatal(err)44 }4546 work := filepath.Join(t.TempDir(), "work")47 gitTest(t, "init", "-b", "main", work)48 gitTest(t, "-C", work, "config", "user.email", "test@example.com")49 gitTest(t, "-C", work, "config", "user.name", "Test")50 blob := make([]byte, 1<<20)51 if _, err := rand.Read(blob); err != nil {52 t.Fatal(err)53 }54 if err := os.WriteFile(filepath.Join(work, "blob.bin"), blob, 0o600); err != nil {55 t.Fatal(err)56 }57 gitTest(t, "-C", work, "add", "blob.bin")58 gitTest(t, "-C", work, "commit", "-m", "big")59 sha := strings.TrimSpace(gitOutput(t, "-C", work, "rev-parse", "HEAD"))60 state := event.Event{Kind: event.KIND_REPO_STATE, CreatedAt: time.Now().Unix(), Tags: [][]string{{"d", "big"}, {"HEAD", "ref: refs/heads/main"}, {"refs/heads/main", sha}}}61 if err := event.Sign(&state, secret); err != nil {62 t.Fatal(err)63 }64 if _, err := tenant.router.Publish(ctx, state, relay.Session{PubKeys: []string{owner}}); err != nil {65 t.Fatal(err)66 }6768 backend := httptest.NewServer(app)69 defer backend.Close()70 target, err := url.Parse(backend.URL)71 if err != nil {72 t.Fatal(err)73 }74 // Throttle the upload so the backend's headers arrive while the pack is75 // still streaming, as they do over a real network.76 proxy := httptest.NewServer(&httputil.ReverseProxy{Rewrite: func(request *httputil.ProxyRequest) {77 request.SetURL(target)78 request.Out.Body = &slowBody{ReadCloser: request.In.Body}79 }})80 defer proxy.Close()8182 // A small post buffer makes git stream the pack as a chunked HTTP/1.1 body.83 gitTest(t, "-C", work, "-c", "http.postBuffer=1024", "-c", "http.version=HTTP/1.1", "push", proxy.URL+"/r/main/"+ownerNPub+"/big.git", "refs/heads/main:refs/heads/main")84 clone := filepath.Join(t.TempDir(), "clone")85 gitTest(t, "clone", proxy.URL+"/r/main/"+ownerNPub+"/big.git", clone)86 if got := strings.TrimSpace(gitOutput(t, "-C", clone, "rev-parse", "HEAD")); got != sha {87 t.Fatalf("clone HEAD = %s, want %s", got, sha)88 }89}9091type slowBody struct{ io.ReadCloser }9293func (b *slowBody) Read(p []byte) (int, error) {94 if len(p) > 4096 {95 p = p[:4096]96 }97 time.Sleep(2 * time.Millisecond)98 return b.ReadCloser.Read(p)99}