loading up the forgejo repo on tangled to test page performance
1// Copyright The Forgejo Authors
2// SPDX-License-Identifier: MIT
3
4package integration
5
6import (
7 "context"
8 "errors"
9 "fmt"
10 "net"
11 "net/http"
12 "net/url"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "strconv"
17 "testing"
18 "time"
19
20 asymkey_model "forgejo.org/models/asymkey"
21 auth_model "forgejo.org/models/auth"
22 "forgejo.org/models/db"
23 repo_model "forgejo.org/models/repo"
24 "forgejo.org/models/unit"
25 "forgejo.org/models/unittest"
26 user_model "forgejo.org/models/user"
27 "forgejo.org/modules/git"
28 "forgejo.org/modules/optional"
29 "forgejo.org/modules/setting"
30 api "forgejo.org/modules/structs"
31 "forgejo.org/modules/test"
32 "forgejo.org/services/migrations"
33 mirror_service "forgejo.org/services/mirror"
34 repo_service "forgejo.org/services/repository"
35 "forgejo.org/tests"
36
37 "github.com/stretchr/testify/assert"
38 "github.com/stretchr/testify/require"
39)
40
41func TestAPIPushMirror(t *testing.T) {
42 onGiteaRun(t, testAPIPushMirror)
43}
44
45func testAPIPushMirror(t *testing.T, u *url.URL) {
46 defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
47 defer test.MockVariableValue(&setting.Mirror.Enabled, true)()
48 defer test.MockProtect(&mirror_service.AddPushMirrorRemote)()
49 defer test.MockProtect(&repo_model.DeletePushMirrors)()
50
51 require.NoError(t, migrations.Init())
52
53 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
54 srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
55 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: srcRepo.OwnerID})
56 session := loginUser(t, user.Name)
57 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll)
58 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/push_mirrors", owner.Name, srcRepo.Name)
59
60 mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{
61 Name: "test-push-mirror",
62 })
63 require.NoError(t, err)
64 remoteAddress := fmt.Sprintf("%s%s/%s", u.String(), url.PathEscape(user.Name), url.PathEscape(mirrorRepo.Name))
65
66 deletePushMirrors := repo_model.DeletePushMirrors
67 deletePushMirrorsError := errors.New("deletePushMirrorsError")
68 deletePushMirrorsFail := func(ctx context.Context, opts repo_model.PushMirrorOptions) error {
69 return deletePushMirrorsError
70 }
71
72 addPushMirrorRemote := mirror_service.AddPushMirrorRemote
73 addPushMirrorRemoteError := errors.New("addPushMirrorRemoteError")
74 addPushMirrorRemoteFail := func(ctx context.Context, m *repo_model.PushMirror, addr string) error {
75 return addPushMirrorRemoteError
76 }
77
78 for _, testCase := range []struct {
79 name string
80 message string
81 status int
82 mirrorCount int
83 setup func()
84 }{
85 {
86 name: "success",
87 status: http.StatusOK,
88 mirrorCount: 1,
89 setup: func() {
90 mirror_service.AddPushMirrorRemote = addPushMirrorRemote
91 repo_model.DeletePushMirrors = deletePushMirrors
92 },
93 },
94 {
95 name: "fail to add and delete",
96 message: deletePushMirrorsError.Error(),
97 status: http.StatusInternalServerError,
98 mirrorCount: 1,
99 setup: func() {
100 mirror_service.AddPushMirrorRemote = addPushMirrorRemoteFail
101 repo_model.DeletePushMirrors = deletePushMirrorsFail
102 },
103 },
104 {
105 name: "fail to add",
106 message: addPushMirrorRemoteError.Error(),
107 status: http.StatusInternalServerError,
108 mirrorCount: 0,
109 setup: func() {
110 mirror_service.AddPushMirrorRemote = addPushMirrorRemoteFail
111 repo_model.DeletePushMirrors = deletePushMirrors
112 },
113 },
114 } {
115 t.Run(testCase.name, func(t *testing.T) {
116 testCase.setup()
117 req := NewRequestWithJSON(t, "POST", urlStr, &api.CreatePushMirrorOption{
118 RemoteAddress: remoteAddress,
119 Interval: "8h",
120 }).AddTokenAuth(token)
121
122 resp := MakeRequest(t, req, testCase.status)
123 if testCase.message != "" {
124 err := api.APIError{}
125 DecodeJSON(t, resp, &err)
126 assert.Equal(t, testCase.message, err.Message)
127 }
128
129 req = NewRequest(t, "GET", urlStr).AddTokenAuth(token)
130 resp = MakeRequest(t, req, http.StatusOK)
131 var pushMirrors []*api.PushMirror
132 DecodeJSON(t, resp, &pushMirrors)
133 if assert.Len(t, pushMirrors, testCase.mirrorCount) && testCase.mirrorCount > 0 {
134 pushMirror := pushMirrors[0]
135 assert.Equal(t, remoteAddress, pushMirror.RemoteAddress)
136
137 repo_model.DeletePushMirrors = deletePushMirrors
138 req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s", urlStr, pushMirror.RemoteName)).AddTokenAuth(token)
139 MakeRequest(t, req, http.StatusNoContent)
140 }
141 })
142 }
143}
144
145func TestAPIPushMirrorSSH(t *testing.T) {
146 _, err := exec.LookPath("ssh")
147 if err != nil {
148 t.Skip("SSH executable not present")
149 }
150
151 onGiteaRun(t, func(t *testing.T, _ *url.URL) {
152 defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
153 defer test.MockVariableValue(&setting.Mirror.Enabled, true)()
154 defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())()
155 require.NoError(t, migrations.Init())
156
157 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
158 srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
159 assert.False(t, srcRepo.HasWiki())
160 session := loginUser(t, user.Name)
161 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
162 pushToRepo, _, f := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{
163 Name: optional.Some("push-mirror-test"),
164 AutoInit: optional.Some(false),
165 EnabledUnits: optional.Some([]unit.Type{unit.TypeCode}),
166 })
167 defer f()
168
169 sshURL := fmt.Sprintf("ssh://%s@%s/%s.git", setting.SSH.User, net.JoinHostPort(setting.SSH.ListenHost, strconv.Itoa(setting.SSH.ListenPort)), pushToRepo.FullName())
170
171 t.Run("Mutual exclusive", func(t *testing.T) {
172 defer tests.PrintCurrentTest(t)()
173
174 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/push_mirrors", srcRepo.FullName()), &api.CreatePushMirrorOption{
175 RemoteAddress: sshURL,
176 Interval: "8h",
177 UseSSH: true,
178 RemoteUsername: "user",
179 RemotePassword: "password",
180 }).AddTokenAuth(token)
181 resp := MakeRequest(t, req, http.StatusBadRequest)
182
183 var apiError api.APIError
184 DecodeJSON(t, resp, &apiError)
185 assert.Equal(t, "'use_ssh' is mutually exclusive with 'remote_username' and 'remote_passoword'", apiError.Message)
186 })
187
188 t.Run("SSH not available", func(t *testing.T) {
189 defer tests.PrintCurrentTest(t)()
190 defer test.MockVariableValue(&git.HasSSHExecutable, false)()
191
192 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/push_mirrors", srcRepo.FullName()), &api.CreatePushMirrorOption{
193 RemoteAddress: sshURL,
194 Interval: "8h",
195 UseSSH: true,
196 }).AddTokenAuth(token)
197 resp := MakeRequest(t, req, http.StatusBadRequest)
198
199 var apiError api.APIError
200 DecodeJSON(t, resp, &apiError)
201 assert.Equal(t, "SSH authentication not available.", apiError.Message)
202 })
203
204 t.Run("Normal", func(t *testing.T) {
205 var pushMirror *repo_model.PushMirror
206 t.Run("Adding", func(t *testing.T) {
207 defer tests.PrintCurrentTest(t)()
208
209 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/push_mirrors", srcRepo.FullName()), &api.CreatePushMirrorOption{
210 RemoteAddress: sshURL,
211 Interval: "8h",
212 UseSSH: true,
213 }).AddTokenAuth(token)
214 MakeRequest(t, req, http.StatusOK)
215
216 pushMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{RepoID: srcRepo.ID})
217 assert.NotEmpty(t, pushMirror.PrivateKey)
218 assert.NotEmpty(t, pushMirror.PublicKey)
219 })
220
221 publickey := pushMirror.GetPublicKey()
222 t.Run("Publickey", func(t *testing.T) {
223 defer tests.PrintCurrentTest(t)()
224
225 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/push_mirrors", srcRepo.FullName())).AddTokenAuth(token)
226 resp := MakeRequest(t, req, http.StatusOK)
227
228 var pushMirrors []*api.PushMirror
229 DecodeJSON(t, resp, &pushMirrors)
230 assert.Len(t, pushMirrors, 1)
231 assert.Equal(t, publickey, pushMirrors[0].PublicKey)
232 })
233
234 t.Run("Add deploy key", func(t *testing.T) {
235 defer tests.PrintCurrentTest(t)()
236
237 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/keys", pushToRepo.FullName()), &api.CreateKeyOption{
238 Title: "push mirror key",
239 Key: publickey,
240 ReadOnly: false,
241 }).AddTokenAuth(token)
242 MakeRequest(t, req, http.StatusCreated)
243
244 unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{Name: "push mirror key", RepoID: pushToRepo.ID})
245 })
246
247 t.Run("Synchronize", func(t *testing.T) {
248 defer tests.PrintCurrentTest(t)()
249
250 req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/push_mirrors-sync", srcRepo.FullName())).AddTokenAuth(token)
251 MakeRequest(t, req, http.StatusOK)
252 })
253
254 t.Run("Check mirrored content", func(t *testing.T) {
255 defer tests.PrintCurrentTest(t)()
256 sha := "1032bbf17fbc0d9c95bb5418dabe8f8c99278700"
257
258 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/commits?limit=1", srcRepo.FullName())).AddTokenAuth(token)
259 resp := MakeRequest(t, req, http.StatusOK)
260
261 var commitList []*api.Commit
262 DecodeJSON(t, resp, &commitList)
263
264 assert.Len(t, commitList, 1)
265 assert.Equal(t, sha, commitList[0].SHA)
266
267 assert.Eventually(t, func() bool {
268 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/commits?limit=1", srcRepo.FullName())).AddTokenAuth(token)
269 resp := MakeRequest(t, req, http.StatusOK)
270
271 var commitList []*api.Commit
272 DecodeJSON(t, resp, &commitList)
273
274 return len(commitList) != 0 && commitList[0].SHA == sha
275 }, time.Second*30, time.Second)
276 })
277
278 t.Run("Check known host keys", func(t *testing.T) {
279 defer tests.PrintCurrentTest(t)()
280
281 knownHosts, err := os.ReadFile(filepath.Join(setting.SSH.RootPath, "known_hosts"))
282 require.NoError(t, err)
283
284 publicKey, err := os.ReadFile(setting.SSH.ServerHostKeys[0] + ".pub")
285 require.NoError(t, err)
286
287 assert.Contains(t, string(knownHosts), string(publicKey))
288 })
289 })
290 })
291}