gitea源码

pull_review_test.go 11KB

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