1// Copyright 2021 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package cmd
5
6import (
7 "bufio"
8 "bytes"
9 "io"
10 "net/http"
11 "net/http/httptest"
12 "os"
13 "strings"
14 "testing"
15 "time"
16
17 "forgejo.org/modules/setting"
18 "forgejo.org/modules/test"
19
20 "github.com/stretchr/testify/assert"
21 "github.com/stretchr/testify/require"
22 "github.com/urfave/cli/v2"
23)
24
25// Capture what's being written into a standard file descriptor.
26func captureOutput(t *testing.T, stdFD *os.File) (finish func() (output string)) {
27 t.Helper()
28
29 r, w, err := os.Pipe()
30 require.NoError(t, err)
31 resetStdout := test.MockVariableValue(stdFD, *w)
32
33 return func() (output string) {
34 w.Close()
35 resetStdout()
36
37 out, err := io.ReadAll(r)
38 require.NoError(t, err)
39 return string(out)
40 }
41}
42
43func TestPktLine(t *testing.T) {
44 ctx := t.Context()
45
46 t.Run("Read", func(t *testing.T) {
47 s := strings.NewReader("0000")
48 r := bufio.NewReader(s)
49 result, err := readPktLine(ctx, r, pktLineTypeFlush)
50 require.NoError(t, err)
51 assert.Equal(t, pktLineTypeFlush, result.Type)
52
53 s = strings.NewReader("0006a\n")
54 r = bufio.NewReader(s)
55 result, err = readPktLine(ctx, r, pktLineTypeData)
56 require.NoError(t, err)
57 assert.Equal(t, pktLineTypeData, result.Type)
58 assert.Equal(t, []byte("a\n"), result.Data)
59
60 s = strings.NewReader("0004")
61 r = bufio.NewReader(s)
62 result, err = readPktLine(ctx, r, pktLineTypeData)
63 require.Error(t, err)
64 assert.Nil(t, result)
65
66 data := strings.Repeat("x", 65516)
67 r = bufio.NewReader(strings.NewReader("fff0" + data))
68 result, err = readPktLine(ctx, r, pktLineTypeData)
69 require.NoError(t, err)
70 assert.Equal(t, pktLineTypeData, result.Type)
71 assert.Equal(t, []byte(data), result.Data)
72
73 r = bufio.NewReader(strings.NewReader("fff1a"))
74 result, err = readPktLine(ctx, r, pktLineTypeData)
75 require.Error(t, err)
76 assert.Nil(t, result)
77 })
78
79 t.Run("Write", func(t *testing.T) {
80 w := bytes.NewBuffer([]byte{})
81 err := writeFlushPktLine(ctx, w)
82 require.NoError(t, err)
83 assert.Equal(t, []byte("0000"), w.Bytes())
84
85 w.Reset()
86 err = writeDataPktLine(ctx, w, []byte("a\nb"))
87 require.NoError(t, err)
88 assert.Equal(t, []byte("0007a\nb"), w.Bytes())
89
90 w.Reset()
91 data := bytes.Repeat([]byte{0x05}, 288)
92 err = writeDataPktLine(ctx, w, data)
93 require.NoError(t, err)
94 assert.Equal(t, append([]byte("0124"), data...), w.Bytes())
95
96 w.Reset()
97 err = writeDataPktLine(ctx, w, nil)
98 require.Error(t, err)
99 assert.Empty(t, w.Bytes())
100
101 w.Reset()
102 data = bytes.Repeat([]byte{0x64}, 65516)
103 err = writeDataPktLine(ctx, w, data)
104 require.NoError(t, err)
105 assert.Equal(t, append([]byte("fff0"), data...), w.Bytes())
106
107 w.Reset()
108 err = writeDataPktLine(ctx, w, bytes.Repeat([]byte{0x64}, 65516+1))
109 require.Error(t, err)
110 assert.Empty(t, w.Bytes())
111 })
112}
113
114func TestDelayWriter(t *testing.T) {
115 // Setup the environment.
116 defer test.MockVariableValue(&setting.InternalToken, "Random")()
117 defer test.MockVariableValue(&setting.InstallLock, true)()
118 defer test.MockVariableValue(&setting.Git.VerbosePush, true)()
119 t.Setenv("SSH_ORIGINAL_COMMAND", "true")
120
121 // Setup the Stdin.
122 f, err := os.OpenFile(t.TempDir()+"/stdin", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
123 require.NoError(t, err)
124 _, err = f.Write([]byte("00000000000000000000 00000000000000000001 refs/head/main\n"))
125 require.NoError(t, err)
126 _, err = f.Seek(0, 0)
127 require.NoError(t, err)
128 defer test.MockVariableValue(os.Stdin, *f)()
129
130 // Setup the server that processes the hooks.
131 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
132 time.Sleep(time.Millisecond * 600)
133 }))
134 defer ts.Close()
135 defer test.MockVariableValue(&setting.LocalURL, ts.URL+"/")()
136
137 app := cli.NewApp()
138 app.Commands = []*cli.Command{subcmdHookPreReceive}
139
140 t.Run("Should delay", func(t *testing.T) {
141 defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Millisecond*500)()
142 finish := captureOutput(t, os.Stdout)
143
144 err = app.Run([]string{"./forgejo", "pre-receive"})
145 require.NoError(t, err)
146 out := finish()
147
148 require.Contains(t, out, "* Checking 1 references")
149 require.Contains(t, out, "Checked 1 references in total")
150 })
151
152 t.Run("Shouldn't delay", func(t *testing.T) {
153 defer test.MockVariableValue(&setting.Git.VerbosePushDelay, time.Second*5)()
154 finish := captureOutput(t, os.Stdout)
155
156 err = app.Run([]string{"./forgejo", "pre-receive"})
157 require.NoError(t, err)
158 out := finish()
159
160 require.NoError(t, err)
161 require.Empty(t, out)
162 })
163}
164
165func TestRunHookUpdate(t *testing.T) {
166 app := cli.NewApp()
167 app.Commands = []*cli.Command{subcmdHookUpdate}
168
169 t.Run("Removal of internal reference", func(t *testing.T) {
170 defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
171 defer test.MockVariableValue(&setting.IsProd, false)()
172 finish := captureOutput(t, os.Stderr)
173
174 err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
175 out := finish()
176 require.Error(t, err)
177
178 assert.Contains(t, out, "The deletion of refs/pull/1/head is skipped as it's an internal reference.")
179 })
180
181 t.Run("Update of internal reference", func(t *testing.T) {
182 defer test.MockVariableValue(&cli.OsExiter, func(code int) {})()
183 defer test.MockVariableValue(&setting.IsProd, false)()
184 finish := captureOutput(t, os.Stderr)
185
186 err := app.Run([]string{"./forgejo", "update", "refs/pull/1/head", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000001"})
187 out := finish()
188 require.Error(t, err)
189
190 assert.Contains(t, out, "The modification of refs/pull/1/head is skipped as it's an internal reference.")
191 })
192
193 t.Run("Removal of branch", func(t *testing.T) {
194 err := app.Run([]string{"./forgejo", "update", "refs/head/main", "0a51ae26bc73c47e2f754560c40904cf14ed51a9", "0000000000000000000000000000000000000000"})
195 require.NoError(t, err)
196 })
197
198 t.Run("Not enough arguments", func(t *testing.T) {
199 err := app.Run([]string{"./forgejo", "update"})
200 require.NoError(t, err)
201 })
202}