code.gitea.io/gitea@v1.22.3/tests/integration/api_pull_test.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 "testing" 11 12 auth_model "code.gitea.io/gitea/models/auth" 13 "code.gitea.io/gitea/models/db" 14 issues_model "code.gitea.io/gitea/models/issues" 15 "code.gitea.io/gitea/models/perm" 16 repo_model "code.gitea.io/gitea/models/repo" 17 "code.gitea.io/gitea/models/unittest" 18 user_model "code.gitea.io/gitea/models/user" 19 "code.gitea.io/gitea/modules/setting" 20 api "code.gitea.io/gitea/modules/structs" 21 "code.gitea.io/gitea/services/forms" 22 issue_service "code.gitea.io/gitea/services/issue" 23 "code.gitea.io/gitea/tests" 24 25 "github.com/stretchr/testify/assert" 26 ) 27 28 func TestAPIViewPulls(t *testing.T) { 29 defer tests.PrepareTestEnv(t)() 30 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 31 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 32 33 ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) 34 35 req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?state=all", owner.Name, repo.Name). 36 AddTokenAuth(ctx.Token) 37 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 38 39 var pulls []*api.PullRequest 40 DecodeJSON(t, resp, &pulls) 41 expectedLen := unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}, unittest.Cond("is_pull = ?", true)) 42 assert.Len(t, pulls, expectedLen) 43 44 pull := pulls[0] 45 if assert.EqualValues(t, 5, pull.ID) { 46 resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK) 47 _, err := io.ReadAll(resp.Body) 48 assert.NoError(t, err) 49 // TODO: use diff to generate stats to test against 50 51 t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID), 52 doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) { 53 if assert.Len(t, files, 1) { 54 assert.Equal(t, "File-WoW", files[0].Filename) 55 assert.Empty(t, files[0].PreviousFilename) 56 assert.EqualValues(t, 1, files[0].Additions) 57 assert.EqualValues(t, 1, files[0].Changes) 58 assert.EqualValues(t, 0, files[0].Deletions) 59 assert.Equal(t, "added", files[0].Status) 60 } 61 })) 62 } 63 } 64 65 func TestAPIViewPullsByBaseHead(t *testing.T) { 66 defer tests.PrepareTestEnv(t)() 67 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 68 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 69 70 ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) 71 72 req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). 73 AddTokenAuth(ctx.Token) 74 resp := ctx.Session.MakeRequest(t, req, http.StatusOK) 75 76 pull := &api.PullRequest{} 77 DecodeJSON(t, resp, pull) 78 assert.EqualValues(t, 3, pull.Index) 79 assert.EqualValues(t, 2, pull.ID) 80 81 req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). 82 AddTokenAuth(ctx.Token) 83 ctx.Session.MakeRequest(t, req, http.StatusNotFound) 84 } 85 86 // TestAPIMergePullWIP ensures that we can't merge a WIP pull request 87 func TestAPIMergePullWIP(t *testing.T) { 88 defer tests.PrepareTestEnv(t)() 89 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 90 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 91 pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{Status: issues_model.PullRequestStatusMergeable}, unittest.Cond("has_merged = ?", false)) 92 pr.LoadIssue(db.DefaultContext) 93 issue_service.ChangeTitle(db.DefaultContext, pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title) 94 95 // force reload 96 pr.LoadAttributes(db.DefaultContext) 97 98 assert.Contains(t, pr.Issue.Title, setting.Repository.PullRequest.WorkInProgressPrefixes[0]) 99 100 session := loginUser(t, owner.Name) 101 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 102 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner.Name, repo.Name, pr.Index), &forms.MergePullRequestForm{ 103 MergeMessageField: pr.Issue.Title, 104 Do: string(repo_model.MergeStyleMerge), 105 }).AddTokenAuth(token) 106 107 MakeRequest(t, req, http.StatusMethodNotAllowed) 108 } 109 110 func TestAPICreatePullSuccess(t *testing.T) { 111 defer tests.PrepareTestEnv(t)() 112 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 113 // repo10 have code, pulls units. 114 repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) 115 // repo11 only have code unit but should still create pulls 116 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 117 owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) 118 119 session := loginUser(t, owner11.Name) 120 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 121 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ 122 Head: fmt.Sprintf("%s:master", owner11.Name), 123 Base: "master", 124 Title: "create a failure pr", 125 }).AddTokenAuth(token) 126 MakeRequest(t, req, http.StatusCreated) 127 MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail 128 } 129 130 func TestAPICreatePullBasePermission(t *testing.T) { 131 defer tests.PrepareTestEnv(t)() 132 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 133 // repo10 have code, pulls units. 134 repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) 135 // repo11 only have code unit but should still create pulls 136 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 137 user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) 138 139 session := loginUser(t, user4.Name) 140 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 141 opts := &api.CreatePullRequestOption{ 142 Head: fmt.Sprintf("%s:master", repo11.OwnerName), 143 Base: "master", 144 Title: "create a failure pr", 145 } 146 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) 147 MakeRequest(t, req, http.StatusForbidden) 148 149 // add user4 to be a collaborator to base repo 150 ctx := NewAPITestContext(t, repo10.OwnerName, repo10.Name, auth_model.AccessTokenScopeWriteRepository) 151 t.Run("AddUser4AsCollaborator", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeRead)) 152 153 // create again 154 req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) 155 MakeRequest(t, req, http.StatusCreated) 156 } 157 158 func TestAPICreatePullHeadPermission(t *testing.T) { 159 defer tests.PrepareTestEnv(t)() 160 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 161 // repo10 have code, pulls units. 162 repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) 163 // repo11 only have code unit but should still create pulls 164 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 165 user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) 166 167 session := loginUser(t, user4.Name) 168 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 169 opts := &api.CreatePullRequestOption{ 170 Head: fmt.Sprintf("%s:master", repo11.OwnerName), 171 Base: "master", 172 Title: "create a failure pr", 173 } 174 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) 175 MakeRequest(t, req, http.StatusForbidden) 176 177 // add user4 to be a collaborator to head repo with read permission 178 ctx := NewAPITestContext(t, repo11.OwnerName, repo11.Name, auth_model.AccessTokenScopeWriteRepository) 179 t.Run("AddUser4AsCollaboratorWithRead", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeRead)) 180 req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) 181 MakeRequest(t, req, http.StatusForbidden) 182 183 // add user4 to be a collaborator to head repo with write permission 184 t.Run("AddUser4AsCollaboratorWithWrite", doAPIAddCollaborator(ctx, user4.Name, perm.AccessModeWrite)) 185 req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &opts).AddTokenAuth(token) 186 MakeRequest(t, req, http.StatusCreated) 187 } 188 189 func TestAPICreatePullSameRepoSuccess(t *testing.T) { 190 defer tests.PrepareTestEnv(t)() 191 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 192 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 193 194 session := loginUser(t, owner.Name) 195 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 196 197 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner.Name, repo.Name), &api.CreatePullRequestOption{ 198 Head: fmt.Sprintf("%s:pr-to-update", owner.Name), 199 Base: "master", 200 Title: "successfully create a PR between branches of the same repository", 201 }).AddTokenAuth(token) 202 MakeRequest(t, req, http.StatusCreated) 203 MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail 204 } 205 206 func TestAPICreatePullWithFieldsSuccess(t *testing.T) { 207 defer tests.PrepareTestEnv(t)() 208 // repo10 have code, pulls units. 209 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 210 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 211 // repo11 only have code unit but should still create pulls 212 repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) 213 owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) 214 215 session := loginUser(t, owner11.Name) 216 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 217 218 opts := &api.CreatePullRequestOption{ 219 Head: fmt.Sprintf("%s:master", owner11.Name), 220 Base: "master", 221 Title: "create a failure pr", 222 Body: "foobaaar", 223 Milestone: 5, 224 Assignees: []string{owner10.Name}, 225 Labels: []int64{5}, 226 } 227 228 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts). 229 AddTokenAuth(token) 230 231 res := MakeRequest(t, req, http.StatusCreated) 232 pull := new(api.PullRequest) 233 DecodeJSON(t, res, pull) 234 235 assert.NotNil(t, pull.Milestone) 236 assert.EqualValues(t, opts.Milestone, pull.Milestone.ID) 237 if assert.Len(t, pull.Assignees, 1) { 238 assert.EqualValues(t, opts.Assignees[0], owner10.Name) 239 } 240 assert.NotNil(t, pull.Labels) 241 assert.EqualValues(t, opts.Labels[0], pull.Labels[0].ID) 242 } 243 244 func TestAPICreatePullWithFieldsFailure(t *testing.T) { 245 defer tests.PrepareTestEnv(t)() 246 // repo10 have code, pulls units. 247 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 248 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 249 // repo11 only have code unit but should still create pulls 250 repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11}) 251 owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) 252 253 session := loginUser(t, owner11.Name) 254 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 255 256 opts := &api.CreatePullRequestOption{ 257 Head: fmt.Sprintf("%s:master", owner11.Name), 258 Base: "master", 259 } 260 261 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts). 262 AddTokenAuth(token) 263 MakeRequest(t, req, http.StatusUnprocessableEntity) 264 opts.Title = "is required" 265 266 opts.Milestone = 666 267 MakeRequest(t, req, http.StatusUnprocessableEntity) 268 opts.Milestone = 5 269 270 opts.Assignees = []string{"qweruqweroiuyqweoiruywqer"} 271 MakeRequest(t, req, http.StatusUnprocessableEntity) 272 opts.Assignees = []string{owner10.LoginName} 273 274 opts.Labels = []int64{55555} 275 MakeRequest(t, req, http.StatusUnprocessableEntity) 276 opts.Labels = []int64{5} 277 } 278 279 func TestAPIEditPull(t *testing.T) { 280 defer tests.PrepareTestEnv(t)() 281 repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) 282 owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) 283 284 session := loginUser(t, owner10.Name) 285 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 286 title := "create a success pr" 287 req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ 288 Head: "develop", 289 Base: "master", 290 Title: title, 291 }).AddTokenAuth(token) 292 apiPull := new(api.PullRequest) 293 resp := MakeRequest(t, req, http.StatusCreated) 294 DecodeJSON(t, resp, apiPull) 295 assert.EqualValues(t, "master", apiPull.Base.Name) 296 297 newTitle := "edit a this pr" 298 newBody := "edited body" 299 req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index), &api.EditPullRequestOption{ 300 Base: "feature/1", 301 Title: newTitle, 302 Body: &newBody, 303 }).AddTokenAuth(token) 304 resp = MakeRequest(t, req, http.StatusCreated) 305 DecodeJSON(t, resp, apiPull) 306 assert.EqualValues(t, "feature/1", apiPull.Base.Name) 307 // check comment history 308 pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) 309 err := pull.LoadIssue(db.DefaultContext) 310 assert.NoError(t, err) 311 unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle}) 312 unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false}) 313 314 req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{ 315 Base: "not-exist", 316 }).AddTokenAuth(token) 317 MakeRequest(t, req, http.StatusNotFound) 318 } 319 320 func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*testing.T, []*api.ChangedFile)) func(*testing.T) { 321 return func(t *testing.T) { 322 req := NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/files", ctx.Username, ctx.Reponame, pr.Index)). 323 AddTokenAuth(ctx.Token) 324 if ctx.ExpectedCode == 0 { 325 ctx.ExpectedCode = http.StatusOK 326 } 327 resp := ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) 328 329 files := make([]*api.ChangedFile, 0, 1) 330 DecodeJSON(t, resp, &files) 331 332 if callback != nil { 333 callback(t, files) 334 } 335 } 336 } 337 338 func TestAPICommitPullRequest(t *testing.T) { 339 defer tests.PrepareTestEnv(t)() 340 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 341 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 342 343 ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) 344 345 mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" 346 req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) 347 ctx.Session.MakeRequest(t, req, http.StatusOK) 348 349 invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" 350 req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) 351 ctx.Session.MakeRequest(t, req, http.StatusNotFound) 352 }