1package daemon23import (4 "context"5 "io"6 "net/http"7 "os"8 "os/exec"9 "path/filepath"10 "strconv"11 "strings"12 "testing"13 "time"1415 "net/http/httptest"1617 "github.com/FelineStateMachine/tinyrelay/internal/event"18 "github.com/FelineStateMachine/tinyrelay/internal/relay"19)2021func TestImmediateMultiRefPushAfterSignedState(t *testing.T) {22 ctx := context.Background()23 secret := strings.Repeat("0", 63) + "1"24 owner, err := event.PublicKey(secret)25 if err != nil {26 t.Fatal(err)27 }28 app, tenant := testTenant(t)29 p := tenant.Policy()30 p.Features.Grasp = true31 if err := tenant.applyPolicy(ctx, p); err != nil {32 t.Fatal(err)33 }3435 announcement := event.Event{Kind: event.KIND_REPO, CreatedAt: time.Now().Unix(), Tags: [][]string{{"d", "many"}, {"clone", "http://relay.test/" + "npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d/many.git"}, {"relays", "ws://relay.test"}}}36 if err := event.Sign(&announcement, secret); err != nil {37 t.Fatal(err)38 }39 if _, err := tenant.router.Publish(ctx, announcement, relay.Session{PubKeys: []string{owner}}); err != nil {40 t.Fatal(err)41 }4243 source := filepath.Join(t.TempDir(), "source.git")44 gitTest(t, "init", "--bare", source)45 work := filepath.Join(t.TempDir(), "work")46 gitTest(t, "clone", source, work)47 gitTest(t, "-C", work, "config", "user.email", "test@example.com")48 gitTest(t, "-C", work, "config", "user.name", "Test")49 if err := osWrite(filepath.Join(work, "README"), []byte("multi\n")); err != nil {50 t.Fatal(err)51 }52 gitTest(t, "-C", work, "add", "README")53 gitTest(t, "-C", work, "commit", "-m", "multi")54 gitTest(t, "-C", work, "branch", "-M", "main")55 sha := strings.TrimSpace(gitOutput(t, "-C", work, "rev-parse", "HEAD"))56 refs := [][]string{{"HEAD", "ref: refs/heads/main"}}57 for i := 0; i < 30; i++ {58 name := "refs/heads/branch-" + strconv.Itoa(i)59 if i == 0 {60 name = "refs/heads/main"61 }62 gitTest(t, "-C", work, "update-ref", name, sha)63 refs = append(refs, []string{name, sha})64 }65 state := event.Event{Kind: event.KIND_REPO_STATE, CreatedAt: time.Now().Unix(), Tags: append([][]string{{"d", "many"}}, refs...)}66 if err := event.Sign(&state, secret); err != nil {67 t.Fatal(err)68 }69 if _, err := tenant.router.Publish(ctx, state, relay.Session{PubKeys: []string{owner}}); err != nil {70 t.Fatal(err)71 }7273 server := httptest.NewServer(app)74 defer server.Close()75 const ownerNPub = "npub10xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqpkge6d"76 url := server.URL + "/r/main/" + ownerNPub + "/many.git"77 gitTest(t, "-C", work, "push", url, "refs/heads/*:refs/heads/*")78 // Existing repositories must obey live hosting/read policy changes too.79 checkAdvertisement := func(want int) {80 t.Helper()81 res, err := server.Client().Get(url + "/info/refs?service=git-upload-pack")82 if err != nil {83 t.Fatal(err)84 }85 defer res.Body.Close()86 _, _ = io.Copy(io.Discard, res.Body)87 if res.StatusCode != want {88 t.Fatalf("Git policy response = %d, want %d", res.StatusCode, want)89 }90 }91 p = tenant.Policy()92 p.Reads = "members"93 if err := tenant.applyPolicy(ctx, p); err != nil {94 t.Fatal(err)95 }96 checkAdvertisement(http.StatusForbidden)97 p.Reads = "open"98 p.Features.Grasp = false99 if err := tenant.applyPolicy(ctx, p); err != nil {100 t.Fatal(err)101 }102 checkAdvertisement(http.StatusNotFound)103 p.Features.Grasp = true104 if err := tenant.applyPolicy(ctx, p); err != nil {105 t.Fatal(err)106 }107 checkAdvertisement(http.StatusOK)108}109110func gitTest(t *testing.T, args ...string) {111 t.Helper()112 if out, err := exec.Command("git", args...).CombinedOutput(); err != nil {113 t.Fatalf("git %v: %v: %s", args, err, out)114 }115}116117func gitOutput(t *testing.T, args ...string) string {118 t.Helper()119 out, err := exec.Command("git", args...).Output()120 if err != nil {121 t.Fatal(err)122 }123 return string(out)124}125126func osWrite(path string, data []byte) error {127 return os.WriteFile(path, data, 0600)128}