code.gitea.io/gitea@v1.22.3/tests/integration/pull_review_test.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"path"
    11  	"strings"
    12  	"testing"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	issues_model "code.gitea.io/gitea/models/issues"
    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/git"
    20  	"code.gitea.io/gitea/modules/test"
    21  	issue_service "code.gitea.io/gitea/services/issue"
    22  	repo_service "code.gitea.io/gitea/services/repository"
    23  	files_service "code.gitea.io/gitea/services/repository/files"
    24  	"code.gitea.io/gitea/tests"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  )
    28  
    29  func TestPullView_ReviewerMissed(t *testing.T) {
    30  	defer tests.PrepareTestEnv(t)()
    31  	session := loginUser(t, "user1")
    32  
    33  	req := NewRequest(t, "GET", "/pulls")
    34  	resp := session.MakeRequest(t, req, http.StatusOK)
    35  	assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
    36  
    37  	req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
    38  	resp = session.MakeRequest(t, req, http.StatusOK)
    39  	assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
    40  
    41  	// if some reviews are missing, the page shouldn't fail
    42  	err := db.TruncateBeans(db.DefaultContext, &issues_model.Review{})
    43  	assert.NoError(t, err)
    44  	req = NewRequest(t, "GET", "/user2/repo1/pulls/2")
    45  	resp = session.MakeRequest(t, req, http.StatusOK)
    46  	assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
    47  }
    48  
    49  func TestPullView_CodeOwner(t *testing.T) {
    50  	onGiteaRun(t, func(t *testing.T, u *url.URL) {
    51  		user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    52  
    53  		// Create the repo.
    54  		repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
    55  			Name:             "test_codeowner",
    56  			Readme:           "Default",
    57  			AutoInit:         true,
    58  			ObjectFormatName: git.Sha1ObjectFormat.Name(),
    59  			DefaultBranch:    "master",
    60  		})
    61  		assert.NoError(t, err)
    62  
    63  		// add CODEOWNERS to default branch
    64  		_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
    65  			OldBranch: repo.DefaultBranch,
    66  			Files: []*files_service.ChangeRepoFile{
    67  				{
    68  					Operation:     "create",
    69  					TreePath:      "CODEOWNERS",
    70  					ContentReader: strings.NewReader("README.md @user5\n"),
    71  				},
    72  			},
    73  		})
    74  		assert.NoError(t, err)
    75  
    76  		t.Run("First Pull Request", func(t *testing.T) {
    77  			// create a new branch to prepare for pull request
    78  			_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
    79  				NewBranch: "codeowner-basebranch",
    80  				Files: []*files_service.ChangeRepoFile{
    81  					{
    82  						Operation:     "update",
    83  						TreePath:      "README.md",
    84  						ContentReader: strings.NewReader("# This is a new project\n"),
    85  					},
    86  				},
    87  			})
    88  			assert.NoError(t, err)
    89  
    90  			// Create a pull request.
    91  			session := loginUser(t, "user2")
    92  			testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch", "Test Pull Request")
    93  
    94  			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: repo.ID, HeadBranch: "codeowner-basebranch"})
    95  			unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5})
    96  			assert.NoError(t, pr.LoadIssue(db.DefaultContext))
    97  
    98  			err := issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request")
    99  			assert.NoError(t, err)
   100  			prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
   101  			assert.NoError(t, prUpdated1.LoadIssue(db.DefaultContext))
   102  			assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title)
   103  
   104  			err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2")
   105  			assert.NoError(t, err)
   106  			prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
   107  			assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext))
   108  			assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title)
   109  		})
   110  
   111  		// change the default branch CODEOWNERS file to change README.md's codeowner
   112  		_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
   113  			Files: []*files_service.ChangeRepoFile{
   114  				{
   115  					Operation:     "update",
   116  					TreePath:      "CODEOWNERS",
   117  					ContentReader: strings.NewReader("README.md @user8\n"),
   118  				},
   119  			},
   120  		})
   121  		assert.NoError(t, err)
   122  
   123  		t.Run("Second Pull Request", func(t *testing.T) {
   124  			// create a new branch to prepare for pull request
   125  			_, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
   126  				NewBranch: "codeowner-basebranch2",
   127  				Files: []*files_service.ChangeRepoFile{
   128  					{
   129  						Operation:     "update",
   130  						TreePath:      "README.md",
   131  						ContentReader: strings.NewReader("# This is a new project2\n"),
   132  					},
   133  				},
   134  			})
   135  			assert.NoError(t, err)
   136  
   137  			// Create a pull request.
   138  			session := loginUser(t, "user2")
   139  			testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch2", "Test Pull Request2")
   140  
   141  			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
   142  			unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
   143  		})
   144  
   145  		t.Run("Forked Repo Pull Request", func(t *testing.T) {
   146  			user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
   147  			forkedRepo, err := repo_service.ForkRepository(db.DefaultContext, user2, user5, repo_service.ForkRepoOptions{
   148  				BaseRepo: repo,
   149  				Name:     "test_codeowner",
   150  			})
   151  			assert.NoError(t, err)
   152  
   153  			// create a new branch to prepare for pull request
   154  			_, err = files_service.ChangeRepoFiles(db.DefaultContext, forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
   155  				NewBranch: "codeowner-basebranch-forked",
   156  				Files: []*files_service.ChangeRepoFile{
   157  					{
   158  						Operation:     "update",
   159  						TreePath:      "README.md",
   160  						ContentReader: strings.NewReader("# This is a new forked project\n"),
   161  					},
   162  				},
   163  			})
   164  			assert.NoError(t, err)
   165  
   166  			session := loginUser(t, "user5")
   167  
   168  			// create a pull request on the forked repository, code reviewers should not be mentioned
   169  			testPullCreateDirectly(t, session, "user5", "test_codeowner", forkedRepo.DefaultBranch, "", "", "codeowner-basebranch-forked", "Test Pull Request on Forked Repository")
   170  
   171  			pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
   172  			unittest.AssertExistsIf(t, false, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
   173  
   174  			// create a pull request to base repository, code reviewers should be mentioned
   175  			testPullCreateDirectly(t, session, repo.OwnerName, repo.Name, repo.DefaultBranch, forkedRepo.OwnerName, forkedRepo.Name, "codeowner-basebranch-forked", "Test Pull Request3")
   176  
   177  			pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: forkedRepo.ID, HeadBranch: "codeowner-basebranch-forked"})
   178  			unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
   179  		})
   180  	})
   181  }
   182  
   183  func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
   184  	onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
   185  		user1Session := loginUser(t, "user1")
   186  		user2Session := loginUser(t, "user2")
   187  
   188  		// Have user1 create a fork of repo1.
   189  		testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1", "")
   190  
   191  		t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
   192  			// Create a merged PR (made by user1) in the upstream repo1.
   193  			testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
   194  			resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title")
   195  			elem := strings.Split(test.RedirectURL(resp), "/")
   196  			assert.EqualValues(t, "pulls", elem[3])
   197  			testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
   198  
   199  			// Grab the CSRF token.
   200  			req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
   201  			resp = user2Session.MakeRequest(t, req, http.StatusOK)
   202  			htmlDoc := NewHTMLParser(t, resp.Body)
   203  
   204  			// Submit an approve review on the PR.
   205  			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "approve", http.StatusUnprocessableEntity)
   206  
   207  			// Submit a reject review on the PR.
   208  			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "reject", http.StatusUnprocessableEntity)
   209  		})
   210  
   211  		t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
   212  			// Created a closed PR (made by user1) in the upstream repo1.
   213  			testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n")
   214  			resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title")
   215  			elem := strings.Split(test.RedirectURL(resp), "/")
   216  			assert.EqualValues(t, "pulls", elem[3])
   217  			testIssueClose(t, user1Session, elem[1], elem[2], elem[4])
   218  
   219  			// Grab the CSRF token.
   220  			req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
   221  			resp = user2Session.MakeRequest(t, req, http.StatusOK)
   222  			htmlDoc := NewHTMLParser(t, resp.Body)
   223  
   224  			// Submit an approve review on the PR.
   225  			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "approve", http.StatusUnprocessableEntity)
   226  
   227  			// Submit a reject review on the PR.
   228  			testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "", "reject", http.StatusUnprocessableEntity)
   229  		})
   230  	})
   231  }
   232  
   233  func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, commitID, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
   234  	options := map[string]string{
   235  		"_csrf":     csrf,
   236  		"commit_id": commitID,
   237  		"content":   "test",
   238  		"type":      reviewType,
   239  	}
   240  
   241  	submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit")
   242  	req := NewRequestWithValues(t, "POST", submitURL, options)
   243  	return session.MakeRequest(t, req, expectedSubmitStatus)
   244  }
   245  
   246  func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder {
   247  	req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber))
   248  	resp := session.MakeRequest(t, req, http.StatusOK)
   249  
   250  	htmlDoc := NewHTMLParser(t, resp.Body)
   251  	closeURL := path.Join(owner, repo, "issues", issueNumber, "comments")
   252  
   253  	options := map[string]string{
   254  		"_csrf":  htmlDoc.GetCSRF(),
   255  		"status": "close",
   256  	}
   257  
   258  	req = NewRequestWithValues(t, "POST", closeURL, options)
   259  	return session.MakeRequest(t, req, http.StatusOK)
   260  }