clone

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

main
tinyrelay / tinygit / private_repair_acceptance_test.go
private_repair_acceptance_test.go 4.1K
1package tinygit23import (4 "context"5 "crypto/x509"6 "encoding/pem"7 "io"8 "net/http"9 "net/http/httptest"10 "os"11 "os/exec"12 "path/filepath"13 "strconv"14 "strings"15 "sync/atomic"16 "testing"1718 "github.com/FelineStateMachine/tinyrelay/internal/policy"19 "github.com/FelineStateMachine/tinyrelay/internal/storage"20)2122func TestFetchMissingHTTPSRepairAndNoop(t *testing.T) {23 root := t.TempDir()24 remoteRoot := filepath.Join(root, "remote")25 work := filepath.Join(root, "work")26 gitRepairRun(t, "init", "--bare", filepath.Join(remoteRoot, "repo.git"))27 gitRepairRun(t, "init", work)28 gitRepairRun(t, "-C", work, "config", "user.email", "repair@example.test")29 gitRepairRun(t, "-C", work, "config", "user.name", "Repair")30 if err := os.WriteFile(filepath.Join(work, "README"), []byte("repair\n"), 0600); err != nil {31 t.Fatal(err)32 }33 gitRepairRun(t, "-C", work, "add", "README")34 gitRepairRun(t, "-C", work, "commit", "-m", "repair")35 commit := strings.TrimSpace(gitRepairOutput(t, "-C", work, "rev-parse", "HEAD"))36 gitRepairRun(t, "-C", work, "push", filepath.Join(remoteRoot, "repo.git"), "HEAD:refs/heads/main")3738 var requests atomic.Int3239 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {40 requests.Add(1)41 serveGitBackend(t, w, r, remoteRoot)42 }))43 t.Cleanup(server.Close)44 caFile := filepath.Join(root, "ca.pem")45 cert, err := x509.ParseCertificate(server.Certificate().Raw)46 if err != nil {47 t.Fatal(err)48 }49 if err := os.WriteFile(caFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}), 0600); err != nil {50 t.Fatal(err)51 }52 t.Setenv("GIT_SSL_CAINFO", caFile)5354 store, err := storage.Open(context.Background(), filepath.Join(root, "relay.db"))55 if err != nil {56 t.Fatal(err)57 }58 t.Cleanup(func() { _ = store.Close() })59 p := policy.Defaults("")60 g, err := New(Config{Store: tinyStore(store), Root: filepath.Join(root, "git"), Policy: func() Policy { return fromInternalPolicy(p) }, AllowPrivateRelays: true})61 if err != nil {62 t.Fatal(err)63 }64 repo := Repository{Owner: strings.Repeat("a", 64), Identifier: "repo", Clone: []string{server.URL + "/repo.git"}, Refs: map[string]string{"refs/heads/main": commit}}65 if err := g.FetchMissing(context.Background(), repo, repo.Clone, repo.Refs); err != nil {66 t.Fatal(err)67 }68 if requests.Load() == 0 || !gitRepairObject(t, filepath.Join(root, "git", repo.Owner, "repo.git"), commit) {69 t.Fatalf("repair did not fetch commit: requests=%d", requests.Load())70 }71 firstRequests := requests.Load()72 if err := g.FetchMissing(context.Background(), repo, repo.Clone, repo.Refs); err != nil {73 t.Fatal(err)74 }75 if requests.Load() != firstRequests {76 t.Fatalf("noop repair made network requests: before=%d after=%d", firstRequests, requests.Load())77 }78}7980func serveGitBackend(t *testing.T, w http.ResponseWriter, r *http.Request, root string) {81 t.Helper()82 env := append(os.Environ(), "GIT_PROJECT_ROOT="+root, "GIT_HTTP_EXPORT_ALL=1", "PATH_INFO="+r.URL.Path, "REQUEST_METHOD="+r.Method, "QUERY_STRING="+r.URL.RawQuery, "CONTENT_TYPE="+r.Header.Get("Content-Type"), "CONTENT_LENGTH="+strconv.FormatInt(r.ContentLength, 10))83 cmd := exec.Command("git", "http-backend")84 cmd.Env = env85 cmd.Stdin = r.Body86 out, err := cmd.Output()87 if err != nil {88 http.Error(w, err.Error(), http.StatusBadGateway)89 return90 }91 parts := strings.SplitN(string(out), "\r\n\r\n", 2)92 if len(parts) != 2 {93 http.Error(w, "invalid git backend response", http.StatusBadGateway)94 return95 }96 for _, line := range strings.Split(parts[0], "\r\n") {97 fields := strings.SplitN(line, ": ", 2)98 if len(fields) == 2 {99 w.Header().Set(fields[0], fields[1])100 }101 }102 _, _ = io.WriteString(w, parts[1])103}104105func gitRepairRun(t *testing.T, args ...string) {106 t.Helper()107 if out, err := exec.Command("git", args...).CombinedOutput(); err != nil {108 t.Fatalf("git %v: %v: %s", args, err, out)109 }110}111func gitRepairOutput(t *testing.T, args ...string) string {112 t.Helper()113 out, err := exec.Command("git", args...).Output()114 if err != nil {115 t.Fatal(err)116 }117 return string(out)118}119func gitRepairObject(t *testing.T, dir, object string) bool {120 t.Helper()121 return exec.Command("git", "--git-dir", dir, "cat-file", "-e", object).Run() == nil122}