Go · 3175 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package protocol_test
4
5 import (
6 "bytes"
7 "context"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/tenseleyFlow/shithub/internal/git/protocol"
15 )
16
17 func TestWritePkt_Format(t *testing.T) {
18 t.Parallel()
19 var buf bytes.Buffer
20 if err := protocol.WritePkt(&buf, "hi"); err != nil {
21 t.Fatalf("WritePkt: %v", err)
22 }
23 // 4-hex prefix = len(payload)+4 = 6 -> "0006"
24 if got := buf.String(); got != "0006hi" {
25 t.Fatalf("got %q, want %q", got, "0006hi")
26 }
27 }
28
29 func TestWritePkt_RejectsTooLong(t *testing.T) {
30 t.Parallel()
31 var buf bytes.Buffer
32 huge := strings.Repeat("a", protocol.MaxPktLine)
33 if err := protocol.WritePkt(&buf, huge); err == nil {
34 t.Fatal("expected error for over-length pkt")
35 }
36 }
37
38 func TestServiceAdvertisement(t *testing.T) {
39 t.Parallel()
40 var buf bytes.Buffer
41 if err := protocol.WriteServiceAdvertisement(&buf, "git-upload-pack"); err != nil {
42 t.Fatalf("WriteServiceAdvertisement: %v", err)
43 }
44 got := buf.String()
45 // Expected: "001e# service=git-upload-pack\n0000"
46 want := "001e# service=git-upload-pack\n0000"
47 if got != want {
48 t.Fatalf("got %q, want %q", got, want)
49 }
50 }
51
52 // TestCmd_StreamsAndDrainsStderr exercises the exec wrapper end-to-end:
53 // init a bare repo, run upload-pack --advertise-refs against it, drain
54 // stderr, and check stdout looks like a valid (empty) ref advertisement.
55 func TestCmd_StreamsAndDrainsStderr(t *testing.T) {
56 t.Parallel()
57 gitDir := initBare(t)
58
59 cmd := protocol.Cmd(context.Background(), protocol.UploadPack, gitDir, true, nil)
60 stderr := protocol.DrainStderr(cmd)
61 out, err := cmd.Output()
62 if err != nil {
63 t.Fatalf("Cmd run: %v\nstderr: %s", err, stderr())
64 }
65 // First 4 chars are a hex length prefix; the body should mention
66 // either a ref or the no-refs sentinel git emits for unborn HEAD.
67 if len(out) < 4 {
68 t.Fatalf("output too short: %q", out)
69 }
70 }
71
72 // TestCmd_KillsOnContextCancel verifies that cancelling ctx kills the
73 // subprocess promptly (Go's default cmd.Cancel sends SIGKILL).
74 func TestCmd_KillsOnContextCancel(t *testing.T) {
75 t.Parallel()
76 ctx, cancel := context.WithCancel(context.Background())
77
78 // `git fetch` against a never-resolving URL hangs indefinitely;
79 // we'll use that as our long-running subprocess.
80 gitDir := initBare(t)
81 //nolint:gosec // G204: gitDir is t.TempDir.
82 cmd := exec.CommandContext(ctx, "git", "-C", gitDir, "fetch", "https://example.invalid/never-resolves")
83 cmd.WaitDelay = 250 * time.Millisecond
84 if err := cmd.Start(); err != nil {
85 t.Fatalf("Start: %v", err)
86 }
87
88 go func() {
89 time.Sleep(100 * time.Millisecond)
90 cancel()
91 }()
92 start := time.Now()
93 _ = cmd.Wait() // expect non-nil; we just care it returns
94 if elapsed := time.Since(start); elapsed > 5*time.Second {
95 t.Fatalf("subprocess took %s to die after cancel; expected <5s", elapsed)
96 }
97 }
98
99 func initBare(t *testing.T) string {
100 t.Helper()
101 root := t.TempDir()
102 gitDir := filepath.Join(root, "x.git")
103 //nolint:gosec // G204: t.TempDir.
104 if out, err := exec.Command("git", "init", "--bare", "--initial-branch=trunk", gitDir).CombinedOutput(); err != nil {
105 t.Fatalf("git init: %v\n%s", err, out)
106 }
107 return gitDir
108 }
109