You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

issue_test.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package integrations
  5. import (
  6. "fmt"
  7. "net/http"
  8. "path"
  9. "strconv"
  10. "strings"
  11. "testing"
  12. "time"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/models/unittest"
  15. "code.gitea.io/gitea/modules/indexer/issues"
  16. "code.gitea.io/gitea/modules/references"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/test"
  19. "github.com/PuerkitoBio/goquery"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection {
  23. issueList := htmlDoc.doc.Find(".issue.list")
  24. assert.EqualValues(t, 1, issueList.Length())
  25. return issueList.Find("li").Find(".title")
  26. }
  27. func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *models.Issue {
  28. href, exists := issueSelection.Attr("href")
  29. assert.True(t, exists)
  30. indexStr := href[strings.LastIndexByte(href, '/')+1:]
  31. index, err := strconv.Atoi(indexStr)
  32. assert.NoError(t, err, "Invalid issue href: %s", href)
  33. return unittest.AssertExistsAndLoadBean(t, &models.Issue{RepoID: repoID, Index: int64(index)}).(*models.Issue)
  34. }
  35. func assertMatch(t testing.TB, issue *models.Issue, keyword string) {
  36. matches := strings.Contains(strings.ToLower(issue.Title), keyword) ||
  37. strings.Contains(strings.ToLower(issue.Content), keyword)
  38. for _, comment := range issue.Comments {
  39. matches = matches || strings.Contains(
  40. strings.ToLower(comment.Content),
  41. keyword,
  42. )
  43. }
  44. assert.True(t, matches)
  45. }
  46. func TestNoLoginViewIssues(t *testing.T) {
  47. defer prepareTestEnv(t)()
  48. req := NewRequest(t, "GET", "/user2/repo1/issues")
  49. MakeRequest(t, req, http.StatusOK)
  50. }
  51. func TestViewIssuesSortByType(t *testing.T) {
  52. defer prepareTestEnv(t)()
  53. user := unittest.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
  54. repo := unittest.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  55. session := loginUser(t, user.Name)
  56. req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by")
  57. resp := session.MakeRequest(t, req, http.StatusOK)
  58. htmlDoc := NewHTMLParser(t, resp.Body)
  59. issuesSelection := getIssuesSelection(t, htmlDoc)
  60. expectedNumIssues := unittest.GetCount(t,
  61. &models.Issue{RepoID: repo.ID, PosterID: user.ID},
  62. unittest.Cond("is_closed=?", false),
  63. unittest.Cond("is_pull=?", false),
  64. )
  65. if expectedNumIssues > setting.UI.IssuePagingNum {
  66. expectedNumIssues = setting.UI.IssuePagingNum
  67. }
  68. assert.EqualValues(t, expectedNumIssues, issuesSelection.Length())
  69. issuesSelection.Each(func(_ int, selection *goquery.Selection) {
  70. issue := getIssue(t, repo.ID, selection)
  71. assert.EqualValues(t, user.ID, issue.PosterID)
  72. })
  73. }
  74. func TestViewIssuesKeyword(t *testing.T) {
  75. defer prepareTestEnv(t)()
  76. repo := unittest.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
  77. issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{
  78. RepoID: repo.ID,
  79. Index: 1,
  80. }).(*models.Issue)
  81. issues.UpdateIssueIndexer(issue)
  82. time.Sleep(time.Second * 1)
  83. const keyword = "first"
  84. req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword)
  85. resp := MakeRequest(t, req, http.StatusOK)
  86. htmlDoc := NewHTMLParser(t, resp.Body)
  87. issuesSelection := getIssuesSelection(t, htmlDoc)
  88. assert.EqualValues(t, 1, issuesSelection.Length())
  89. issuesSelection.Each(func(_ int, selection *goquery.Selection) {
  90. issue := getIssue(t, repo.ID, selection)
  91. assert.False(t, issue.IsClosed)
  92. assert.False(t, issue.IsPull)
  93. assertMatch(t, issue, keyword)
  94. })
  95. }
  96. func TestNoLoginViewIssue(t *testing.T) {
  97. defer prepareTestEnv(t)()
  98. req := NewRequest(t, "GET", "/user2/repo1/issues/1")
  99. MakeRequest(t, req, http.StatusOK)
  100. }
  101. func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string {
  102. req := NewRequest(t, "GET", path.Join(user, repo, "issues", "new"))
  103. resp := session.MakeRequest(t, req, http.StatusOK)
  104. htmlDoc := NewHTMLParser(t, resp.Body)
  105. link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
  106. assert.True(t, exists, "The template has changed")
  107. req = NewRequestWithValues(t, "POST", link, map[string]string{
  108. "_csrf": htmlDoc.GetCSRF(),
  109. "title": title,
  110. "content": content,
  111. })
  112. resp = session.MakeRequest(t, req, http.StatusFound)
  113. issueURL := test.RedirectURL(resp)
  114. req = NewRequest(t, "GET", issueURL)
  115. resp = session.MakeRequest(t, req, http.StatusOK)
  116. htmlDoc = NewHTMLParser(t, resp.Body)
  117. val := htmlDoc.doc.Find("#issue-title").Text()
  118. assert.Equal(t, title, val)
  119. val = htmlDoc.doc.Find(".comment .render-content p").First().Text()
  120. assert.Equal(t, content, val)
  121. return issueURL
  122. }
  123. func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
  124. req := NewRequest(t, "GET", issueURL)
  125. resp := session.MakeRequest(t, req, http.StatusOK)
  126. htmlDoc := NewHTMLParser(t, resp.Body)
  127. link, exists := htmlDoc.doc.Find("#comment-form").Attr("action")
  128. assert.True(t, exists, "The template has changed")
  129. commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length()
  130. req = NewRequestWithValues(t, "POST", link, map[string]string{
  131. "_csrf": htmlDoc.GetCSRF(),
  132. "content": content,
  133. "status": status,
  134. })
  135. resp = session.MakeRequest(t, req, http.StatusFound)
  136. req = NewRequest(t, "GET", test.RedirectURL(resp))
  137. resp = session.MakeRequest(t, req, http.StatusOK)
  138. htmlDoc = NewHTMLParser(t, resp.Body)
  139. val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text()
  140. assert.Equal(t, content, val)
  141. idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id")
  142. idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:]
  143. assert.True(t, has)
  144. id, err := strconv.Atoi(idStr)
  145. assert.NoError(t, err)
  146. return int64(id)
  147. }
  148. func TestNewIssue(t *testing.T) {
  149. defer prepareTestEnv(t)()
  150. session := loginUser(t, "user2")
  151. testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  152. }
  153. func TestIssueCommentClose(t *testing.T) {
  154. defer prepareTestEnv(t)()
  155. session := loginUser(t, "user2")
  156. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  157. testIssueAddComment(t, session, issueURL, "Test comment 1", "")
  158. testIssueAddComment(t, session, issueURL, "Test comment 2", "")
  159. testIssueAddComment(t, session, issueURL, "Test comment 3", "close")
  160. // Validate that issue content has not been updated
  161. req := NewRequest(t, "GET", issueURL)
  162. resp := session.MakeRequest(t, req, http.StatusOK)
  163. htmlDoc := NewHTMLParser(t, resp.Body)
  164. val := htmlDoc.doc.Find(".comment-list .comment .render-content p").First().Text()
  165. assert.Equal(t, "Description", val)
  166. }
  167. func TestIssueReaction(t *testing.T) {
  168. defer prepareTestEnv(t)()
  169. session := loginUser(t, "user2")
  170. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  171. req := NewRequest(t, "GET", issueURL)
  172. resp := session.MakeRequest(t, req, http.StatusOK)
  173. htmlDoc := NewHTMLParser(t, resp.Body)
  174. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
  175. "_csrf": htmlDoc.GetCSRF(),
  176. "content": "8ball",
  177. })
  178. session.MakeRequest(t, req, http.StatusInternalServerError)
  179. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
  180. "_csrf": htmlDoc.GetCSRF(),
  181. "content": "eyes",
  182. })
  183. session.MakeRequest(t, req, http.StatusOK)
  184. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/unreact"), map[string]string{
  185. "_csrf": htmlDoc.GetCSRF(),
  186. "content": "eyes",
  187. })
  188. session.MakeRequest(t, req, http.StatusOK)
  189. }
  190. func TestIssueCrossReference(t *testing.T) {
  191. defer prepareTestEnv(t)()
  192. // Issue that will be referenced
  193. _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description")
  194. // Ref from issue title
  195. issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description")
  196. unittest.AssertExistsAndLoadBean(t, &models.Comment{
  197. IssueID: issueBase.ID,
  198. RefRepoID: 1,
  199. RefIssueID: issueRef.ID,
  200. RefCommentID: 0,
  201. RefIsPull: false,
  202. RefAction: references.XRefActionNone})
  203. // Edit title, neuter ref
  204. testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref")
  205. unittest.AssertExistsAndLoadBean(t, &models.Comment{
  206. IssueID: issueBase.ID,
  207. RefRepoID: 1,
  208. RefIssueID: issueRef.ID,
  209. RefCommentID: 0,
  210. RefIsPull: false,
  211. RefAction: references.XRefActionNeutered})
  212. // Ref from issue content
  213. issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index))
  214. unittest.AssertExistsAndLoadBean(t, &models.Comment{
  215. IssueID: issueBase.ID,
  216. RefRepoID: 1,
  217. RefIssueID: issueRef.ID,
  218. RefCommentID: 0,
  219. RefIsPull: false,
  220. RefAction: references.XRefActionNone})
  221. // Edit content, neuter ref
  222. testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref")
  223. unittest.AssertExistsAndLoadBean(t, &models.Comment{
  224. IssueID: issueBase.ID,
  225. RefRepoID: 1,
  226. RefIssueID: issueRef.ID,
  227. RefCommentID: 0,
  228. RefIsPull: false,
  229. RefAction: references.XRefActionNeutered})
  230. // Ref from a comment
  231. session := loginUser(t, "user2")
  232. commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "")
  233. comment := &models.Comment{
  234. IssueID: issueBase.ID,
  235. RefRepoID: 1,
  236. RefIssueID: issueRef.ID,
  237. RefCommentID: commentID,
  238. RefIsPull: false,
  239. RefAction: references.XRefActionNone}
  240. unittest.AssertExistsAndLoadBean(t, comment)
  241. // Ref from a different repository
  242. _, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index))
  243. unittest.AssertExistsAndLoadBean(t, &models.Comment{
  244. IssueID: issueBase.ID,
  245. RefRepoID: 10,
  246. RefIssueID: issueRef.ID,
  247. RefCommentID: 0,
  248. RefIsPull: false,
  249. RefAction: references.XRefActionNone})
  250. }
  251. func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *models.Issue) {
  252. session := loginUser(t, user)
  253. issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content)
  254. indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:]
  255. index, err := strconv.Atoi(indexStr)
  256. assert.NoError(t, err, "Invalid issue href: %s", issueURL)
  257. issue := &models.Issue{RepoID: repoID, Index: int64(index)}
  258. unittest.AssertExistsAndLoadBean(t, issue)
  259. return issueURL, issue
  260. }
  261. func testIssueChangeInfo(t *testing.T, user, issueURL, info string, value string) {
  262. session := loginUser(t, user)
  263. req := NewRequest(t, "GET", issueURL)
  264. resp := session.MakeRequest(t, req, http.StatusOK)
  265. htmlDoc := NewHTMLParser(t, resp.Body)
  266. req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
  267. "_csrf": htmlDoc.GetCSRF(),
  268. info: value,
  269. })
  270. _ = session.MakeRequest(t, req, http.StatusOK)
  271. }
  272. func TestIssueRedirect(t *testing.T) {
  273. defer prepareTestEnv(t)()
  274. session := loginUser(t, "user2")
  275. // Test external tracker where style not set (shall default numeric)
  276. req := NewRequest(t, "GET", path.Join("org26", "repo_external_tracker", "issues", "1"))
  277. resp := session.MakeRequest(t, req, http.StatusFound)
  278. assert.Equal(t, "https://tracker.com/org26/repo_external_tracker/issues/1", test.RedirectURL(resp))
  279. // Test external tracker with numeric style
  280. req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_numeric", "issues", "1"))
  281. resp = session.MakeRequest(t, req, http.StatusFound)
  282. assert.Equal(t, "https://tracker.com/org26/repo_external_tracker_numeric/issues/1", test.RedirectURL(resp))
  283. // Test external tracker with alphanumeric style (for a pull request)
  284. req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_alpha", "issues", "1"))
  285. resp = session.MakeRequest(t, req, http.StatusFound)
  286. assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
  287. }