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.

335 lines
12 KiB

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