1// Copyright 2019 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package integrations 6 7import ( 8 "context" 9 "fmt" 10 "net/http" 11 "net/http/httptest" 12 "net/url" 13 "os" 14 "testing" 15 "time" 16 17 "code.gitea.io/gitea/models/perm" 18 repo_model "code.gitea.io/gitea/models/repo" 19 "code.gitea.io/gitea/modules/json" 20 "code.gitea.io/gitea/modules/queue" 21 api "code.gitea.io/gitea/modules/structs" 22 "code.gitea.io/gitea/services/forms" 23 24 "github.com/stretchr/testify/assert" 25) 26 27type APITestContext struct { 28 Reponame string 29 Session *TestSession 30 Token string 31 Username string 32 ExpectedCode int 33} 34 35func NewAPITestContext(t *testing.T, username, reponame string) APITestContext { 36 session := loginUser(t, username) 37 token := getTokenForLoggedInUser(t, session) 38 return APITestContext{ 39 Session: session, 40 Token: token, 41 Username: username, 42 Reponame: reponame, 43 } 44} 45 46func (ctx APITestContext) GitPath() string { 47 return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame) 48} 49 50func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*testing.T, api.Repository)) func(*testing.T) { 51 return func(t *testing.T) { 52 createRepoOption := &api.CreateRepoOption{ 53 AutoInit: !empty, 54 Description: "Temporary repo", 55 Name: ctx.Reponame, 56 Private: true, 57 Template: true, 58 Gitignores: "", 59 License: "WTFPL", 60 Readme: "Default", 61 } 62 req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+ctx.Token, createRepoOption) 63 if ctx.ExpectedCode != 0 { 64 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 65 return 66 } 67 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 68 69 var repository api.Repository 70 DecodeJSON(t, resp, &repository) 71 if len(callback) > 0 { 72 callback[0](t, repository) 73 } 74 } 75} 76 77func doAPIEditRepository(ctx APITestContext, editRepoOption *api.EditRepoOption, callback ...func(*testing.T, api.Repository)) func(*testing.T) { 78 return func(t *testing.T) { 79 req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame), ctx.Token), editRepoOption) 80 if ctx.ExpectedCode != 0 { 81 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 82 return 83 } 84 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 85 86 var repository api.Repository 87 DecodeJSON(t, resp, &repository) 88 if len(callback) > 0 { 89 callback[0](t, repository) 90 } 91 } 92} 93 94func doAPIAddCollaborator(ctx APITestContext, username string, mode perm.AccessMode) func(*testing.T) { 95 return func(t *testing.T) { 96 permission := "read" 97 98 if mode == perm.AccessModeAdmin { 99 permission = "admin" 100 } else if mode > perm.AccessModeRead { 101 permission = "write" 102 } 103 addCollaboratorOption := &api.AddCollaboratorOption{ 104 Permission: &permission, 105 } 106 req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/collaborators/%s?token=%s", ctx.Username, ctx.Reponame, username, ctx.Token), addCollaboratorOption) 107 if ctx.ExpectedCode != 0 { 108 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 109 return 110 } 111 ctx.Session.MakeRequest(t, req, http.StatusNoContent) 112 } 113} 114 115func doAPIForkRepository(ctx APITestContext, username string, callback ...func(*testing.T, api.Repository)) func(*testing.T) { 116 return func(t *testing.T) { 117 createForkOption := &api.CreateForkOption{} 118 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks?token=%s", username, ctx.Reponame, ctx.Token), createForkOption) 119 if ctx.ExpectedCode != 0 { 120 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 121 return 122 } 123 resp := ctx.Session.MakeRequest(t, req, http.StatusAccepted) 124 var repository api.Repository 125 DecodeJSON(t, resp, &repository) 126 if len(callback) > 0 { 127 callback[0](t, repository) 128 } 129 } 130} 131 132func doAPIGetRepository(ctx APITestContext, callback ...func(*testing.T, api.Repository)) func(*testing.T) { 133 return func(t *testing.T) { 134 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) 135 136 req := NewRequest(t, "GET", urlStr) 137 if ctx.ExpectedCode != 0 { 138 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 139 return 140 } 141 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 142 143 var repository api.Repository 144 DecodeJSON(t, resp, &repository) 145 if len(callback) > 0 { 146 callback[0](t, repository) 147 } 148 } 149} 150 151func doAPIDeleteRepository(ctx APITestContext) func(*testing.T) { 152 return func(t *testing.T) { 153 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", ctx.Username, ctx.Reponame, ctx.Token) 154 155 req := NewRequest(t, "DELETE", urlStr) 156 if ctx.ExpectedCode != 0 { 157 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 158 return 159 } 160 ctx.Session.MakeRequest(t, req, http.StatusNoContent) 161 } 162} 163 164func doAPICreateUserKey(ctx APITestContext, keyname, keyFile string, callback ...func(*testing.T, api.PublicKey)) func(*testing.T) { 165 return func(t *testing.T) { 166 urlStr := fmt.Sprintf("/api/v1/user/keys?token=%s", ctx.Token) 167 168 dataPubKey, err := os.ReadFile(keyFile + ".pub") 169 assert.NoError(t, err) 170 req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateKeyOption{ 171 Title: keyname, 172 Key: string(dataPubKey), 173 }) 174 if ctx.ExpectedCode != 0 { 175 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 176 return 177 } 178 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 179 var publicKey api.PublicKey 180 DecodeJSON(t, resp, &publicKey) 181 if len(callback) > 0 { 182 callback[0](t, publicKey) 183 } 184 } 185} 186 187func doAPIDeleteUserKey(ctx APITestContext, keyID int64) func(*testing.T) { 188 return func(t *testing.T) { 189 urlStr := fmt.Sprintf("/api/v1/user/keys/%d?token=%s", keyID, ctx.Token) 190 191 req := NewRequest(t, "DELETE", urlStr) 192 if ctx.ExpectedCode != 0 { 193 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 194 return 195 } 196 ctx.Session.MakeRequest(t, req, http.StatusNoContent) 197 } 198} 199 200func doAPICreateDeployKey(ctx APITestContext, keyname, keyFile string, readOnly bool) func(*testing.T) { 201 return func(t *testing.T) { 202 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", ctx.Username, ctx.Reponame, ctx.Token) 203 204 dataPubKey, err := os.ReadFile(keyFile + ".pub") 205 assert.NoError(t, err) 206 req := NewRequestWithJSON(t, "POST", urlStr, api.CreateKeyOption{ 207 Title: keyname, 208 Key: string(dataPubKey), 209 ReadOnly: readOnly, 210 }) 211 212 if ctx.ExpectedCode != 0 { 213 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 214 return 215 } 216 ctx.Session.MakeRequest(t, req, http.StatusCreated) 217 } 218} 219 220func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBranch string) func(*testing.T) (api.PullRequest, error) { 221 return func(t *testing.T) (api.PullRequest, error) { 222 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", 223 owner, repo, ctx.Token) 224 req := NewRequestWithJSON(t, http.MethodPost, urlStr, &api.CreatePullRequestOption{ 225 Head: headBranch, 226 Base: baseBranch, 227 Title: fmt.Sprintf("create a pr from %s to %s", headBranch, baseBranch), 228 }) 229 230 expected := 201 231 if ctx.ExpectedCode != 0 { 232 expected = ctx.ExpectedCode 233 } 234 resp := ctx.Session.MakeRequest(t, req, expected) 235 236 decoder := json.NewDecoder(resp.Body) 237 pr := api.PullRequest{} 238 err := decoder.Decode(&pr) 239 return pr, err 240 } 241} 242 243func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) { 244 return func(t *testing.T) (api.PullRequest, error) { 245 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d?token=%s", 246 owner, repo, index, ctx.Token) 247 req := NewRequest(t, http.MethodGet, urlStr) 248 249 expected := 200 250 if ctx.ExpectedCode != 0 { 251 expected = ctx.ExpectedCode 252 } 253 resp := ctx.Session.MakeRequest(t, req, expected) 254 255 decoder := json.NewDecoder(resp.Body) 256 pr := api.PullRequest{} 257 err := decoder.Decode(&pr) 258 return pr, err 259 } 260} 261 262func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) { 263 return func(t *testing.T) { 264 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", 265 owner, repo, index, ctx.Token) 266 267 var req *http.Request 268 var resp *httptest.ResponseRecorder 269 270 for i := 0; i < 6; i++ { 271 req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ 272 MergeMessageField: "doAPIMergePullRequest Merge", 273 Do: string(repo_model.MergeStyleMerge), 274 }) 275 276 resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus) 277 278 if resp.Code != http.StatusMethodNotAllowed { 279 break 280 } 281 err := api.APIError{} 282 DecodeJSON(t, resp, &err) 283 assert.EqualValues(t, "Please try again later", err.Message) 284 queue.GetManager().FlushAll(context.Background(), 5*time.Second) 285 <-time.After(1 * time.Second) 286 } 287 288 expected := ctx.ExpectedCode 289 if expected == 0 { 290 expected = 200 291 } 292 293 if !assert.EqualValues(t, expected, resp.Code, 294 "Request: %s %s", req.Method, req.URL.String()) { 295 logUnexpectedResponse(t, resp) 296 } 297 } 298} 299 300func doAPIManuallyMergePullRequest(ctx APITestContext, owner, repo, commitID string, index int64) func(*testing.T) { 301 return func(t *testing.T) { 302 urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", 303 owner, repo, index, ctx.Token) 304 req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ 305 Do: string(repo_model.MergeStyleManuallyMerged), 306 MergeCommitID: commitID, 307 }) 308 309 if ctx.ExpectedCode != 0 { 310 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 311 return 312 } 313 ctx.Session.MakeRequest(t, req, 200) 314 } 315} 316 317func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) { 318 return func(t *testing.T) { 319 req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s?token=%s", ctx.Username, ctx.Reponame, branch, ctx.Token) 320 if ctx.ExpectedCode != 0 { 321 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 322 return 323 } 324 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 325 326 var branch api.Branch 327 DecodeJSON(t, resp, &branch) 328 if len(callback) > 0 { 329 callback[0](t, branch) 330 } 331 } 332} 333 334func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateFileOptions, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) { 335 return func(t *testing.T) { 336 url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", ctx.Username, ctx.Reponame, treepath, ctx.Token) 337 req := NewRequestWithJSON(t, "POST", url, &options) 338 if ctx.ExpectedCode != 0 { 339 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 340 return 341 } 342 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 343 344 var contents api.FileResponse 345 DecodeJSON(t, resp, &contents) 346 if len(callback) > 0 { 347 callback[0](t, contents) 348 } 349 } 350} 351 352func doAPICreateOrganization(ctx APITestContext, options *api.CreateOrgOption, callback ...func(*testing.T, api.Organization)) func(t *testing.T) { 353 return func(t *testing.T) { 354 url := fmt.Sprintf("/api/v1/orgs?token=%s", ctx.Token) 355 356 req := NewRequestWithJSON(t, "POST", url, &options) 357 if ctx.ExpectedCode != 0 { 358 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 359 return 360 } 361 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 362 363 var contents api.Organization 364 DecodeJSON(t, resp, &contents) 365 if len(callback) > 0 { 366 callback[0](t, contents) 367 } 368 } 369} 370 371func doAPICreateOrganizationRepository(ctx APITestContext, orgName string, options *api.CreateRepoOption, callback ...func(*testing.T, api.Repository)) func(t *testing.T) { 372 return func(t *testing.T) { 373 url := fmt.Sprintf("/api/v1/orgs/%s/repos?token=%s", orgName, ctx.Token) 374 375 req := NewRequestWithJSON(t, "POST", url, &options) 376 if ctx.ExpectedCode != 0 { 377 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 378 return 379 } 380 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 381 382 var contents api.Repository 383 DecodeJSON(t, resp, &contents) 384 if len(callback) > 0 { 385 callback[0](t, contents) 386 } 387 } 388} 389 390func doAPICreateOrganizationTeam(ctx APITestContext, orgName string, options *api.CreateTeamOption, callback ...func(*testing.T, api.Team)) func(t *testing.T) { 391 return func(t *testing.T) { 392 url := fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", orgName, ctx.Token) 393 394 req := NewRequestWithJSON(t, "POST", url, &options) 395 if ctx.ExpectedCode != 0 { 396 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 397 return 398 } 399 resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) 400 401 var contents api.Team 402 DecodeJSON(t, resp, &contents) 403 if len(callback) > 0 { 404 callback[0](t, contents) 405 } 406 } 407} 408 409func doAPIAddUserToOrganizationTeam(ctx APITestContext, teamID int64, username string) func(t *testing.T) { 410 return func(t *testing.T) { 411 url := fmt.Sprintf("/api/v1/teams/%d/members/%s?token=%s", teamID, username, ctx.Token) 412 413 req := NewRequest(t, "PUT", url) 414 if ctx.ExpectedCode != 0 { 415 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 416 return 417 } 418 ctx.Session.MakeRequest(t, req, http.StatusNoContent) 419 } 420} 421 422func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, repoName string) func(t *testing.T) { 423 return func(t *testing.T) { 424 url := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s?token=%s", teamID, orgName, repoName, ctx.Token) 425 426 req := NewRequest(t, "PUT", url) 427 if ctx.ExpectedCode != 0 { 428 ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 429 return 430 } 431 ctx.Session.MakeRequest(t, req, http.StatusNoContent) 432 } 433} 434