summaryrefslogtreecommitdiffstats
path: root/tests/integration/pull_review_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/pull_review_test.go')
-rw-r--r--tests/integration/pull_review_test.go511
1 files changed, 511 insertions, 0 deletions
diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go
new file mode 100644
index 00000000..e61bf15b
--- /dev/null
+++ b/tests/integration/pull_review_test.go
@@ -0,0 +1,511 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "path"
+ "strconv"
+ "strings"
+ "testing"
+
+ "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/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/test"
+ issue_service "code.gitea.io/gitea/services/issue"
+ repo_service "code.gitea.io/gitea/services/repository"
+ files_service "code.gitea.io/gitea/services/repository/files"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestPullView_ReviewerMissed(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ session := loginUser(t, "user1")
+
+ req := NewRequest(t, "GET", "/pulls")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
+
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
+
+ // if some reviews are missing, the page shouldn't fail
+ reviews, err := issues_model.FindReviews(db.DefaultContext, issues_model.FindReviewOptions{
+ IssueID: 2,
+ })
+ require.NoError(t, err)
+ for _, r := range reviews {
+ require.NoError(t, issues_model.DeleteReview(db.DefaultContext, r))
+ }
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/2")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ assert.True(t, test.IsNormalPageCompleted(resp.Body.String()))
+}
+
+func loadComment(t *testing.T, commentID string) *issues_model.Comment {
+ t.Helper()
+ id, err := strconv.ParseInt(commentID, 10, 64)
+ require.NoError(t, err)
+ return unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: id})
+}
+
+func TestPullView_ResolveInvalidatedReviewComment(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ session := loginUser(t, "user1")
+
+ req := NewRequest(t, "GET", "/user2/repo1/pulls/3/files")
+ session.MakeRequest(t, req, http.StatusOK)
+
+ t.Run("single outdated review (line 1)", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ req := NewRequest(t, "GET", "/user2/repo1/pulls/3/files/reviews/new_comment")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/comments", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "origin": doc.GetInputValueByName("origin"),
+ "latest_commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "side": "proposed",
+ "line": "1",
+ "path": "iso-8859-1.txt",
+ "diff_start_cid": doc.GetInputValueByName("diff_start_cid"),
+ "diff_end_cid": doc.GetInputValueByName("diff_end_cid"),
+ "diff_base_cid": doc.GetInputValueByName("diff_base_cid"),
+ "content": "nitpicking comment",
+ "pending_review": "",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/submit", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "content": "looks good",
+ "type": "comment",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // retrieve comment_id by reloading the comment page
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ doc = NewHTMLParser(t, resp.Body)
+ commentID, ok := doc.Find(`[data-action="Resolve"]`).Attr("data-comment-id")
+ assert.True(t, ok)
+
+ // adjust the database to mark the comment as invalidated
+ // (to invalidate it properly, one should push a commit which should trigger this logic,
+ // in the meantime, use this quick-and-dirty trick)
+ comment := loadComment(t, commentID)
+ require.NoError(t, issues_model.UpdateCommentInvalidate(context.Background(), &issues_model.Comment{
+ ID: comment.ID,
+ Invalidated: true,
+ }))
+
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/resolve_conversation", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "origin": "timeline",
+ "action": "Resolve",
+ "comment_id": commentID,
+ })
+ resp = session.MakeRequest(t, req, http.StatusOK)
+
+ // even on template error, the page returns HTTP 200
+ // count the comments to ensure success.
+ doc = NewHTMLParser(t, resp.Body)
+ assert.Len(t, doc.Find(`.comments > .comment`).Nodes, 1)
+ })
+
+ t.Run("outdated and newer review (line 2)", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ req := NewRequest(t, "GET", "/user2/repo1/pulls/3/files/reviews/new_comment")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ doc := NewHTMLParser(t, resp.Body)
+
+ var firstReviewID int64
+ {
+ // first (outdated) review
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/comments", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "origin": doc.GetInputValueByName("origin"),
+ "latest_commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "side": "proposed",
+ "line": "2",
+ "path": "iso-8859-1.txt",
+ "diff_start_cid": doc.GetInputValueByName("diff_start_cid"),
+ "diff_end_cid": doc.GetInputValueByName("diff_end_cid"),
+ "diff_base_cid": doc.GetInputValueByName("diff_base_cid"),
+ "content": "nitpicking comment",
+ "pending_review": "",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/submit", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "content": "looks good",
+ "type": "comment",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // retrieve comment_id by reloading the comment page
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ doc = NewHTMLParser(t, resp.Body)
+ commentID, ok := doc.Find(`[data-action="Resolve"]`).Attr("data-comment-id")
+ assert.True(t, ok)
+
+ // adjust the database to mark the comment as invalidated
+ // (to invalidate it properly, one should push a commit which should trigger this logic,
+ // in the meantime, use this quick-and-dirty trick)
+ comment := loadComment(t, commentID)
+ require.NoError(t, issues_model.UpdateCommentInvalidate(context.Background(), &issues_model.Comment{
+ ID: comment.ID,
+ Invalidated: true,
+ }))
+ firstReviewID = comment.ReviewID
+ assert.NotZero(t, firstReviewID)
+ }
+
+ // ID of the first comment for the second (up-to-date) review
+ var commentID string
+
+ {
+ // second (up-to-date) review on the same line
+ // make a second review
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/comments", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "origin": doc.GetInputValueByName("origin"),
+ "latest_commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "side": "proposed",
+ "line": "2",
+ "path": "iso-8859-1.txt",
+ "diff_start_cid": doc.GetInputValueByName("diff_start_cid"),
+ "diff_end_cid": doc.GetInputValueByName("diff_end_cid"),
+ "diff_base_cid": doc.GetInputValueByName("diff_base_cid"),
+ "content": "nitpicking comment",
+ "pending_review": "",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/pulls/3/files/reviews/submit", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "commit_id": doc.GetInputValueByName("latest_commit_id"),
+ "content": "looks better",
+ "type": "comment",
+ })
+ session.MakeRequest(t, req, http.StatusOK)
+
+ // retrieve comment_id by reloading the comment page
+ req = NewRequest(t, "GET", "/user2/repo1/pulls/3")
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ doc = NewHTMLParser(t, resp.Body)
+
+ commentIDs := doc.Find(`[data-action="Resolve"]`).Map(func(i int, elt *goquery.Selection) string {
+ v, _ := elt.Attr("data-comment-id")
+ return v
+ })
+ assert.Len(t, commentIDs, 2) // 1 for the outdated review, 1 for the current review
+
+ // check that the first comment is for the previous review
+ comment := loadComment(t, commentIDs[0])
+ assert.Equal(t, comment.ReviewID, firstReviewID)
+
+ // check that the second comment is for a different review
+ comment = loadComment(t, commentIDs[1])
+ assert.NotZero(t, comment.ReviewID)
+ assert.NotEqual(t, comment.ReviewID, firstReviewID)
+
+ commentID = commentIDs[1] // save commentID for later
+ }
+
+ req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/resolve_conversation", map[string]string{
+ "_csrf": doc.GetInputValueByName("_csrf"),
+ "origin": "timeline",
+ "action": "Resolve",
+ "comment_id": commentID,
+ })
+ resp = session.MakeRequest(t, req, http.StatusOK)
+
+ // even on template error, the page returns HTTP 200
+ // count the comments to ensure success.
+ doc = NewHTMLParser(t, resp.Body)
+ comments := doc.Find(`.comments > .comment`)
+ assert.Len(t, comments.Nodes, 1) // the outdated comment belongs to another review and should not be shown
+ })
+
+ t.Run("Files Changed tab", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ for _, c := range []struct {
+ style, outdated string
+ expectedCount int
+ }{
+ {"unified", "true", 3}, // 1 comment on line 1 + 2 comments on line 3
+ {"unified", "false", 1}, // 1 comment on line 3 is not outdated
+ {"split", "true", 3}, // 1 comment on line 1 + 2 comments on line 3
+ {"split", "false", 1}, // 1 comment on line 3 is not outdated
+ } {
+ t.Run(c.style+"+"+c.outdated, func(t *testing.T) {
+ req := NewRequest(t, "GET", "/user2/repo1/pulls/3/files?style="+c.style+"&show-outdated="+c.outdated)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ doc := NewHTMLParser(t, resp.Body)
+ comments := doc.Find(`.comments > .comment`)
+ assert.Len(t, comments.Nodes, c.expectedCount)
+ })
+ }
+ })
+
+ t.Run("Conversation tab", func(t *testing.T) {
+ defer tests.PrintCurrentTest(t)()
+ req := NewRequest(t, "GET", "/user2/repo1/pulls/3")
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ doc := NewHTMLParser(t, resp.Body)
+ comments := doc.Find(`.comments > .comment`)
+ assert.Len(t, comments.Nodes, 3) // 1 comment on line 1 + 2 comments on line 3
+ })
+}
+
+func TestPullView_CodeOwner(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ // Create the repo.
+ repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user2, user2, repo_service.CreateRepoOptions{
+ Name: "test_codeowner",
+ Readme: "Default",
+ AutoInit: true,
+ ObjectFormatName: git.Sha1ObjectFormat.Name(),
+ DefaultBranch: "master",
+ })
+ require.NoError(t, err)
+
+ // add CODEOWNERS to default branch
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ OldBranch: repo.DefaultBranch,
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "create",
+ TreePath: "CODEOWNERS",
+ ContentReader: strings.NewReader("README.md @user5\n"),
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ t.Run("First Pull Request", func(t *testing.T) {
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new project\n"),
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ // Create a pull request.
+ session := loginUser(t, "user2")
+ testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch", "Test Pull Request")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadRepoID: repo.ID, HeadBranch: "codeowner-basebranch"})
+ unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 5})
+ require.NoError(t, pr.LoadIssue(db.DefaultContext))
+
+ err := issue_service.ChangeTitle(db.DefaultContext, pr.Issue, user2, "[WIP] Test Pull Request")
+ require.NoError(t, err)
+ prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ require.NoError(t, prUpdated1.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title)
+
+ err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2")
+ require.NoError(t, err)
+ prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID})
+ require.NoError(t, prUpdated2.LoadIssue(db.DefaultContext))
+ assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title)
+ })
+
+ // change the default branch CODEOWNERS file to change README.md's codeowner
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "CODEOWNERS",
+ ContentReader: strings.NewReader("README.md @user8\n"),
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ t.Run("Second Pull Request", func(t *testing.T) {
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch2",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new project2\n"),
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ // Create a pull request.
+ session := loginUser(t, "user2")
+ testPullCreate(t, session, "user2", "test_codeowner", false, repo.DefaultBranch, "codeowner-basebranch2", "Test Pull Request2")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch2"})
+ unittest.AssertExistsIf(t, true, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
+ })
+
+ t.Run("Forked Repo Pull Request", func(t *testing.T) {
+ user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
+ forkedRepo, err := repo_service.ForkRepository(db.DefaultContext, user2, user5, repo_service.ForkRepoOptions{
+ BaseRepo: repo,
+ Name: "test_codeowner_fork",
+ })
+ require.NoError(t, err)
+
+ // create a new branch to prepare for pull request
+ _, err = files_service.ChangeRepoFiles(db.DefaultContext, forkedRepo, user5, &files_service.ChangeRepoFilesOptions{
+ NewBranch: "codeowner-basebranch-forked",
+ Files: []*files_service.ChangeRepoFile{
+ {
+ Operation: "update",
+ TreePath: "README.md",
+ ContentReader: strings.NewReader("# This is a new forked project\n"),
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ session := loginUser(t, "user5")
+ testPullCreate(t, session, "user5", "test_codeowner_fork", false, forkedRepo.DefaultBranch, "codeowner-basebranch-forked", "Test Pull Request2")
+
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, HeadBranch: "codeowner-basebranch-forked"})
+ unittest.AssertExistsIf(t, false, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest, ReviewerID: 8})
+ })
+ })
+}
+
+func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+ user1Session := loginUser(t, "user1")
+ user2Session := loginUser(t, "user2")
+
+ // Have user1 create a fork of repo1.
+ testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1")
+
+ baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
+ forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
+ baseGitRepo, err := gitrepo.OpenRepository(db.DefaultContext, baseRepo)
+ require.NoError(t, err)
+ defer baseGitRepo.Close()
+
+ t.Run("Submit approve/reject review on merged PR", func(t *testing.T) {
+ // Create a merged PR (made by user1) in the upstream repo1.
+ testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
+ resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title")
+ elem := strings.Split(test.RedirectURL(resp), "/")
+ assert.EqualValues(t, "pulls", elem[3])
+ testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false)
+
+ // Get the commit SHA
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ BaseRepoID: baseRepo.ID,
+ BaseBranch: "master",
+ HeadRepoID: forkedRepo.ID,
+ HeadBranch: "master",
+ })
+ sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
+ require.NoError(t, err)
+
+ // Grab the CSRF token.
+ req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
+ resp = user2Session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ // Submit an approve review on the PR.
+ testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], sha, "approve", http.StatusOK)
+
+ // Submit a reject review on the PR.
+ testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], sha, "reject", http.StatusOK)
+ })
+
+ t.Run("Submit approve/reject review on closed PR", func(t *testing.T) {
+ // Created a closed PR (made by user1) in the upstream repo1.
+ testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Edited...again)\n")
+ resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title")
+ elem := strings.Split(test.RedirectURL(resp), "/")
+ assert.EqualValues(t, "pulls", elem[3])
+ testIssueClose(t, user1Session, elem[1], elem[2], elem[4])
+
+ // Get the commit SHA
+ pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
+ BaseRepoID: baseRepo.ID,
+ BaseBranch: "master",
+ HeadRepoID: forkedRepo.ID,
+ HeadBranch: "a-test-branch",
+ })
+ sha, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
+ require.NoError(t, err)
+
+ // Grab the CSRF token.
+ req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4]))
+ resp = user2Session.MakeRequest(t, req, http.StatusOK)
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ // Submit an approve review on the PR.
+ testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], sha, "approve", http.StatusOK)
+
+ // Submit a reject review on the PR.
+ testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], sha, "reject", http.StatusOK)
+ })
+ })
+}
+
+func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, commitID, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder {
+ options := map[string]string{
+ "_csrf": csrf,
+ "commit_id": commitID,
+ "content": "test",
+ "type": reviewType,
+ }
+
+ submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit")
+ req := NewRequestWithValues(t, "POST", submitURL, options)
+ return session.MakeRequest(t, req, expectedSubmitStatus)
+}
+
+func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder {
+ req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ closeURL := path.Join(owner, repo, "issues", issueNumber, "comments")
+
+ options := map[string]string{
+ "_csrf": htmlDoc.GetCSRF(),
+ "status": "close",
+ }
+
+ req = NewRequestWithValues(t, "POST", closeURL, options)
+ return session.MakeRequest(t, req, http.StatusOK)
+}