summaryrefslogtreecommitdiffstats
path: root/tests/integration/api_pull_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /tests/integration/api_pull_test.go
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'tests/integration/api_pull_test.go')
-rw-r--r--tests/integration/api_pull_test.go349
1 files changed, 349 insertions, 0 deletions
diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go
new file mode 100644
index 0000000..7b95d44
--- /dev/null
+++ b/tests/integration/api_pull_test.go
@@ -0,0 +1,349 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/services/forms"
+ issue_service "code.gitea.io/gitea/services/issue"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAPIViewPulls(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?state=all", owner.Name, repo.Name).
+ AddTokenAuth(ctx.Token)
+ resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
+
+ var pulls []*api.PullRequest
+ DecodeJSON(t, resp, &pulls)
+ expectedLen := unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}, unittest.Cond("is_pull = ?", true))
+ assert.Len(t, pulls, expectedLen)
+
+ pull := pulls[0]
+ if assert.EqualValues(t, 5, pull.ID) {
+ resp = ctx.Session.MakeRequest(t, NewRequest(t, "GET", pull.DiffURL), http.StatusOK)
+ _, err := io.ReadAll(resp.Body)
+ require.NoError(t, err)
+ // TODO: use diff to generate stats to test against
+
+ t.Run(fmt.Sprintf("APIGetPullFiles_%d", pull.ID),
+ doAPIGetPullFiles(ctx, pull, func(t *testing.T, files []*api.ChangedFile) {
+ if assert.Len(t, files, 1) {
+ assert.Equal(t, "File-WoW", files[0].Filename)
+ assert.Empty(t, files[0].PreviousFilename)
+ assert.EqualValues(t, 1, files[0].Additions)
+ assert.EqualValues(t, 1, files[0].Changes)
+ assert.EqualValues(t, 0, files[0].Deletions)
+ assert.Equal(t, "added", files[0].Status)
+ }
+ }))
+ }
+}
+
+func TestAPIViewPullsByBaseHead(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name).
+ AddTokenAuth(ctx.Token)
+ resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
+
+ pull := &api.PullRequest{}
+ DecodeJSON(t, resp, pull)
+ assert.EqualValues(t, 3, pull.Index)
+ assert.EqualValues(t, 2, pull.ID)
+
+ req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name).
+ AddTokenAuth(ctx.Token)
+ ctx.Session.MakeRequest(t, req, http.StatusNotFound)
+}
+
+// TestAPIMergePullWIP ensures that we can't merge a WIP pull request
+func TestAPIMergePullWIP(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{Status: issues_model.PullRequestStatusMergeable}, unittest.Cond("has_merged = ?", false))
+ pr.LoadIssue(db.DefaultContext)
+ issue_service.ChangeTitle(db.DefaultContext, pr.Issue, owner, setting.Repository.PullRequest.WorkInProgressPrefixes[0]+" "+pr.Issue.Title)
+
+ // force reload
+ pr.LoadAttributes(db.DefaultContext)
+
+ assert.Contains(t, pr.Issue.Title, setting.Repository.PullRequest.WorkInProgressPrefixes[0])
+
+ session := loginUser(t, owner.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner.Name, repo.Name, pr.Index), &forms.MergePullRequestForm{
+ MergeMessageField: pr.Issue.Title,
+ Do: string(repo_model.MergeStyleMerge),
+ }).AddTokenAuth(token)
+
+ MakeRequest(t, req, http.StatusMethodNotAllowed)
+}
+
+func TestAPICreatePullSuccess(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+ // repo10 have code, pulls units.
+ repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
+ // repo11 only have code unit but should still create pulls
+ owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID})
+ owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID})
+
+ session := loginUser(t, owner11.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
+ Head: fmt.Sprintf("%s:master", owner11.Name),
+ Base: "master",
+ Title: "create a failure pr",
+ }).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusCreated)
+ MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail
+}
+
+func TestAPICreatePullSameRepoSuccess(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ session := loginUser(t, owner.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner.Name, repo.Name), &api.CreatePullRequestOption{
+ Head: fmt.Sprintf("%s:pr-to-update", owner.Name),
+ Base: "master",
+ Title: "successfully create a PR between branches of the same repository",
+ }).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusCreated)
+ MakeRequest(t, req, http.StatusUnprocessableEntity) // second request should fail
+}
+
+func TestAPICreatePullWithFieldsSuccess(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ // repo10 have code, pulls units.
+ repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+ owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID})
+ // repo11 only have code unit but should still create pulls
+ repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
+ owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID})
+
+ session := loginUser(t, owner11.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+ opts := &api.CreatePullRequestOption{
+ Head: fmt.Sprintf("%s:master", owner11.Name),
+ Base: "master",
+ Title: "create a failure pr",
+ Body: "foobaaar",
+ Milestone: 5,
+ Assignees: []string{owner10.Name},
+ Labels: []int64{5},
+ }
+
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts).
+ AddTokenAuth(token)
+
+ res := MakeRequest(t, req, http.StatusCreated)
+ pull := new(api.PullRequest)
+ DecodeJSON(t, res, pull)
+
+ assert.NotNil(t, pull.Milestone)
+ assert.EqualValues(t, opts.Milestone, pull.Milestone.ID)
+ if assert.Len(t, pull.Assignees, 1) {
+ assert.EqualValues(t, opts.Assignees[0], owner10.Name)
+ }
+ assert.NotNil(t, pull.Labels)
+ assert.EqualValues(t, opts.Labels[0], pull.Labels[0].ID)
+}
+
+func TestAPICreatePullWithFieldsFailure(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ // repo10 have code, pulls units.
+ repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+ owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID})
+ // repo11 only have code unit but should still create pulls
+ repo11 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
+ owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID})
+
+ session := loginUser(t, owner11.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+
+ opts := &api.CreatePullRequestOption{
+ Head: fmt.Sprintf("%s:master", owner11.Name),
+ Base: "master",
+ }
+
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), opts).
+ AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusUnprocessableEntity)
+ opts.Title = "is required"
+
+ opts.Milestone = 666
+ MakeRequest(t, req, http.StatusUnprocessableEntity)
+ opts.Milestone = 5
+
+ opts.Assignees = []string{"qweruqweroiuyqweoiruywqer"}
+ MakeRequest(t, req, http.StatusUnprocessableEntity)
+ opts.Assignees = []string{owner10.LoginName}
+
+ opts.Labels = []int64{55555}
+ MakeRequest(t, req, http.StatusUnprocessableEntity)
+ opts.Labels = []int64{5}
+}
+
+func TestAPIEditPull(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
+ owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID})
+
+ session := loginUser(t, owner10.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
+ title := "create a success pr"
+ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
+ Head: "develop",
+ Base: "master",
+ Title: title,
+ }).AddTokenAuth(token)
+ apiPull := new(api.PullRequest)
+ resp := MakeRequest(t, req, http.StatusCreated)
+ DecodeJSON(t, resp, apiPull)
+ assert.EqualValues(t, "master", apiPull.Base.Name)
+
+ newTitle := "edit a this pr"
+ newBody := "edited body"
+ urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index)
+ req = NewRequestWithJSON(t, http.MethodPatch, urlStr, &api.EditPullRequestOption{
+ Base: "feature/1",
+ Title: newTitle,
+ Body: &newBody,
+ }).AddTokenAuth(token)
+ resp = MakeRequest(t, req, http.StatusCreated)
+ DecodeJSON(t, resp, apiPull)
+ assert.EqualValues(t, "feature/1", apiPull.Base.Name)
+ // check comment history
+ pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
+ err := pull.LoadIssue(db.DefaultContext)
+ require.NoError(t, err)
+ unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle})
+ unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false})
+
+ // verify the idempotency of a state change
+ pullState := string(apiPull.State)
+ req = NewRequestWithJSON(t, http.MethodPatch, urlStr, &api.EditPullRequestOption{
+ State: &pullState,
+ }).AddTokenAuth(token)
+ apiPullIdempotent := new(api.PullRequest)
+ resp = MakeRequest(t, req, http.StatusCreated)
+ DecodeJSON(t, resp, apiPullIdempotent)
+ assert.EqualValues(t, apiPull.State, apiPullIdempotent.State)
+
+ req = NewRequestWithJSON(t, http.MethodPatch, urlStr, &api.EditPullRequestOption{
+ Base: "not-exist",
+ }).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusNotFound)
+}
+
+func TestAPIForkDifferentName(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ // Step 1: get a repo and a user that can fork this repo
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
+
+ // Step 2: fork this repo with another name
+ forkName := "myfork"
+ req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", owner.Name, repo.Name),
+ &api.CreateForkOption{Name: &forkName}).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusAccepted)
+
+ // Step 3: make a PR onto the original repo, it should succeed
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls?state=all", owner.Name, repo.Name),
+ &api.CreatePullRequestOption{Head: user.Name + ":master", Base: "master", Title: "hi"}).AddTokenAuth(token)
+ MakeRequest(t, req, http.StatusCreated)
+}
+
+func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*testing.T, []*api.ChangedFile)) func(*testing.T) {
+ return func(t *testing.T) {
+ req := NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/files", ctx.Username, ctx.Reponame, pr.Index)).
+ AddTokenAuth(ctx.Token)
+ if ctx.ExpectedCode == 0 {
+ ctx.ExpectedCode = http.StatusOK
+ }
+ resp := ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
+
+ files := make([]*api.ChangedFile, 0, 1)
+ DecodeJSON(t, resp, &files)
+
+ if callback != nil {
+ callback(t, files)
+ }
+ }
+}
+
+func TestAPIPullDeleteBranchPerms(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ user2Session := loginUser(t, "user2")
+ user4Session := loginUser(t, "user4")
+ testRepoFork(t, user4Session, "user2", "repo1", "user4", "repo1")
+ testEditFileToNewBranch(t, user2Session, "user2", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - base PR)\n")
+
+ req := NewRequestWithValues(t, "POST", "/user4/repo1/compare/master...user2/repo1:base-pr", map[string]string{
+ "_csrf": GetCSRF(t, user4Session, "/user4/repo1/compare/master...user2/repo1:base-pr"),
+ "title": "Testing PR",
+ })
+ resp := user4Session.MakeRequest(t, req, http.StatusOK)
+ elem := strings.Split(test.RedirectURL(resp), "/")
+
+ token := getTokenForLoggedInUser(t, user4Session, auth_model.AccessTokenScopeWriteRepository)
+ req = NewRequestWithValues(t, "POST", "/api/v1/repos/user4/repo1/pulls/"+elem[4]+"/merge", map[string]string{
+ "do": "merge",
+ "delete_branch_after_merge": "on",
+ }).AddTokenAuth(token)
+ resp = user4Session.MakeRequest(t, req, http.StatusForbidden)
+
+ type userResponse struct {
+ Message string `json:"message"`
+ }
+ var bodyResp userResponse
+ DecodeJSON(t, resp, &bodyResp)
+
+ assert.EqualValues(t, "insufficient permission to delete head branch", bodyResp.Message)
+
+ // Check that the branch still exist.
+ req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/branches/base-pr").AddTokenAuth(token)
+ user4Session.MakeRequest(t, req, http.StatusOK)
+ })
+}