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 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "context"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "testing"
  13. "time"
  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/indexer/issues"
  20. "code.gitea.io/gitea/modules/references"
  21. "code.gitea.io/gitea/modules/setting"
  22. api "code.gitea.io/gitea/modules/structs"
  23. "code.gitea.io/gitea/modules/test"
  24. "code.gitea.io/gitea/tests"
  25. "github.com/PuerkitoBio/goquery"
  26. "github.com/stretchr/testify/assert"
  27. )
  28. func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection {
  29. issueList := htmlDoc.doc.Find("#issue-list")
  30. assert.EqualValues(t, 1, issueList.Length())
  31. return issueList.Find(".flex-item").Find(".issue-title")
  32. }
  33. func getIssue(t *testing.T, repoID int64, issueSelection *goquery.Selection) *issues_model.Issue {
  34. href, exists := issueSelection.Attr("href")
  35. assert.True(t, exists)
  36. indexStr := href[strings.LastIndexByte(href, '/')+1:]
  37. index, err := strconv.Atoi(indexStr)
  38. assert.NoError(t, err, "Invalid issue href: %s", href)
  39. return unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repoID, Index: int64(index)})
  40. }
  41. func assertMatch(t testing.TB, issue *issues_model.Issue, keyword string) {
  42. matches := strings.Contains(strings.ToLower(issue.Title), keyword) ||
  43. strings.Contains(strings.ToLower(issue.Content), keyword)
  44. for _, comment := range issue.Comments {
  45. matches = matches || strings.Contains(
  46. strings.ToLower(comment.Content),
  47. keyword,
  48. )
  49. }
  50. assert.True(t, matches)
  51. }
  52. func TestNoLoginViewIssues(t *testing.T) {
  53. defer tests.PrepareTestEnv(t)()
  54. req := NewRequest(t, "GET", "/user2/repo1/issues")
  55. MakeRequest(t, req, http.StatusOK)
  56. }
  57. func TestViewIssuesSortByType(t *testing.T) {
  58. defer tests.PrepareTestEnv(t)()
  59. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  60. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  61. session := loginUser(t, user.Name)
  62. req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by")
  63. resp := session.MakeRequest(t, req, http.StatusOK)
  64. htmlDoc := NewHTMLParser(t, resp.Body)
  65. issuesSelection := getIssuesSelection(t, htmlDoc)
  66. expectedNumIssues := unittest.GetCount(t,
  67. &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID},
  68. unittest.Cond("is_closed=?", false),
  69. unittest.Cond("is_pull=?", false),
  70. )
  71. if expectedNumIssues > setting.UI.IssuePagingNum {
  72. expectedNumIssues = setting.UI.IssuePagingNum
  73. }
  74. assert.EqualValues(t, expectedNumIssues, issuesSelection.Length())
  75. issuesSelection.Each(func(_ int, selection *goquery.Selection) {
  76. issue := getIssue(t, repo.ID, selection)
  77. assert.EqualValues(t, user.ID, issue.PosterID)
  78. })
  79. }
  80. func TestViewIssuesKeyword(t *testing.T) {
  81. defer tests.PrepareTestEnv(t)()
  82. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  83. issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
  84. RepoID: repo.ID,
  85. Index: 1,
  86. })
  87. issues.UpdateIssueIndexer(context.Background(), issue.ID)
  88. time.Sleep(time.Second * 1)
  89. const keyword = "first"
  90. req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword)
  91. resp := MakeRequest(t, req, http.StatusOK)
  92. htmlDoc := NewHTMLParser(t, resp.Body)
  93. issuesSelection := getIssuesSelection(t, htmlDoc)
  94. assert.EqualValues(t, 1, issuesSelection.Length())
  95. issuesSelection.Each(func(_ int, selection *goquery.Selection) {
  96. issue := getIssue(t, repo.ID, selection)
  97. assert.False(t, issue.IsClosed)
  98. assert.False(t, issue.IsPull)
  99. assertMatch(t, issue, keyword)
  100. })
  101. }
  102. func TestNoLoginViewIssue(t *testing.T) {
  103. defer tests.PrepareTestEnv(t)()
  104. req := NewRequest(t, "GET", "/user2/repo1/issues/1")
  105. MakeRequest(t, req, http.StatusOK)
  106. }
  107. func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string {
  108. req := NewRequest(t, "GET", path.Join(user, repo, "issues", "new"))
  109. resp := session.MakeRequest(t, req, http.StatusOK)
  110. htmlDoc := NewHTMLParser(t, resp.Body)
  111. link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
  112. assert.True(t, exists, "The template has changed")
  113. req = NewRequestWithValues(t, "POST", link, map[string]string{
  114. "_csrf": htmlDoc.GetCSRF(),
  115. "title": title,
  116. "content": content,
  117. })
  118. resp = session.MakeRequest(t, req, http.StatusOK)
  119. issueURL := test.RedirectURL(resp)
  120. req = NewRequest(t, "GET", issueURL)
  121. resp = session.MakeRequest(t, req, http.StatusOK)
  122. htmlDoc = NewHTMLParser(t, resp.Body)
  123. val := htmlDoc.doc.Find("#issue-title").Text()
  124. assert.Contains(t, val, title)
  125. val = htmlDoc.doc.Find(".comment .render-content p").First().Text()
  126. assert.Equal(t, content, val)
  127. return issueURL
  128. }
  129. func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content, status string) int64 {
  130. req := NewRequest(t, "GET", issueURL)
  131. resp := session.MakeRequest(t, req, http.StatusOK)
  132. htmlDoc := NewHTMLParser(t, resp.Body)
  133. link, exists := htmlDoc.doc.Find("#comment-form").Attr("action")
  134. assert.True(t, exists, "The template has changed")
  135. commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length()
  136. req = NewRequestWithValues(t, "POST", link, map[string]string{
  137. "_csrf": htmlDoc.GetCSRF(),
  138. "content": content,
  139. "status": status,
  140. })
  141. resp = session.MakeRequest(t, req, http.StatusOK)
  142. req = NewRequest(t, "GET", test.RedirectURL(resp))
  143. resp = session.MakeRequest(t, req, http.StatusOK)
  144. htmlDoc = NewHTMLParser(t, resp.Body)
  145. val := htmlDoc.doc.Find(".comment-list .comment .render-content p").Eq(commentCount).Text()
  146. assert.Equal(t, content, val)
  147. idAttr, has := htmlDoc.doc.Find(".comment-list .comment").Eq(commentCount).Attr("id")
  148. idStr := idAttr[strings.LastIndexByte(idAttr, '-')+1:]
  149. assert.True(t, has)
  150. id, err := strconv.Atoi(idStr)
  151. assert.NoError(t, err)
  152. return int64(id)
  153. }
  154. func TestNewIssue(t *testing.T) {
  155. defer tests.PrepareTestEnv(t)()
  156. session := loginUser(t, "user2")
  157. testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  158. }
  159. func TestIssueCommentClose(t *testing.T) {
  160. defer tests.PrepareTestEnv(t)()
  161. session := loginUser(t, "user2")
  162. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  163. testIssueAddComment(t, session, issueURL, "Test comment 1", "")
  164. testIssueAddComment(t, session, issueURL, "Test comment 2", "")
  165. testIssueAddComment(t, session, issueURL, "Test comment 3", "close")
  166. // Validate that issue content has not been updated
  167. req := NewRequest(t, "GET", issueURL)
  168. resp := session.MakeRequest(t, req, http.StatusOK)
  169. htmlDoc := NewHTMLParser(t, resp.Body)
  170. val := htmlDoc.doc.Find(".comment-list .comment .render-content p").First().Text()
  171. assert.Equal(t, "Description", val)
  172. }
  173. func TestIssueCommentDelete(t *testing.T) {
  174. defer tests.PrepareTestEnv(t)()
  175. session := loginUser(t, "user2")
  176. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  177. comment1 := "Test comment 1"
  178. commentID := testIssueAddComment(t, session, issueURL, comment1, "")
  179. comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
  180. assert.Equal(t, comment1, comment.Content)
  181. // Using the ID of a comment that does not belong to the repository must fail
  182. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
  183. "_csrf": GetCSRF(t, session, issueURL),
  184. })
  185. session.MakeRequest(t, req, http.StatusNotFound)
  186. req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
  187. "_csrf": GetCSRF(t, session, issueURL),
  188. })
  189. session.MakeRequest(t, req, http.StatusOK)
  190. unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
  191. }
  192. func TestIssueCommentUpdate(t *testing.T) {
  193. defer tests.PrepareTestEnv(t)()
  194. session := loginUser(t, "user2")
  195. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  196. comment1 := "Test comment 1"
  197. commentID := testIssueAddComment(t, session, issueURL, comment1, "")
  198. comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
  199. assert.Equal(t, comment1, comment.Content)
  200. modifiedContent := comment.Content + "MODIFIED"
  201. // Using the ID of a comment that does not belong to the repository must fail
  202. req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
  203. "_csrf": GetCSRF(t, session, issueURL),
  204. "content": modifiedContent,
  205. })
  206. session.MakeRequest(t, req, http.StatusNotFound)
  207. req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
  208. "_csrf": GetCSRF(t, session, issueURL),
  209. "content": modifiedContent,
  210. })
  211. session.MakeRequest(t, req, http.StatusOK)
  212. comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
  213. assert.Equal(t, modifiedContent, comment.Content)
  214. }
  215. func TestIssueReaction(t *testing.T) {
  216. defer tests.PrepareTestEnv(t)()
  217. session := loginUser(t, "user2")
  218. issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
  219. req := NewRequest(t, "GET", issueURL)
  220. resp := session.MakeRequest(t, req, http.StatusOK)
  221. htmlDoc := NewHTMLParser(t, resp.Body)
  222. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
  223. "_csrf": htmlDoc.GetCSRF(),
  224. "content": "8ball",
  225. })
  226. session.MakeRequest(t, req, http.StatusInternalServerError)
  227. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
  228. "_csrf": htmlDoc.GetCSRF(),
  229. "content": "eyes",
  230. })
  231. session.MakeRequest(t, req, http.StatusOK)
  232. req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/unreact"), map[string]string{
  233. "_csrf": htmlDoc.GetCSRF(),
  234. "content": "eyes",
  235. })
  236. session.MakeRequest(t, req, http.StatusOK)
  237. }
  238. func TestIssueCrossReference(t *testing.T) {
  239. defer tests.PrepareTestEnv(t)()
  240. // Issue that will be referenced
  241. _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description")
  242. // Ref from issue title
  243. issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description")
  244. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
  245. IssueID: issueBase.ID,
  246. RefRepoID: 1,
  247. RefIssueID: issueRef.ID,
  248. RefCommentID: 0,
  249. RefIsPull: false,
  250. RefAction: references.XRefActionNone,
  251. })
  252. // Edit title, neuter ref
  253. testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref")
  254. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
  255. IssueID: issueBase.ID,
  256. RefRepoID: 1,
  257. RefIssueID: issueRef.ID,
  258. RefCommentID: 0,
  259. RefIsPull: false,
  260. RefAction: references.XRefActionNeutered,
  261. })
  262. // Ref from issue content
  263. issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index))
  264. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
  265. IssueID: issueBase.ID,
  266. RefRepoID: 1,
  267. RefIssueID: issueRef.ID,
  268. RefCommentID: 0,
  269. RefIsPull: false,
  270. RefAction: references.XRefActionNone,
  271. })
  272. // Edit content, neuter ref
  273. testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref")
  274. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
  275. IssueID: issueBase.ID,
  276. RefRepoID: 1,
  277. RefIssueID: issueRef.ID,
  278. RefCommentID: 0,
  279. RefIsPull: false,
  280. RefAction: references.XRefActionNeutered,
  281. })
  282. // Ref from a comment
  283. session := loginUser(t, "user2")
  284. commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "")
  285. comment := &issues_model.Comment{
  286. IssueID: issueBase.ID,
  287. RefRepoID: 1,
  288. RefIssueID: issueRef.ID,
  289. RefCommentID: commentID,
  290. RefIsPull: false,
  291. RefAction: references.XRefActionNone,
  292. }
  293. unittest.AssertExistsAndLoadBean(t, comment)
  294. // Ref from a different repository
  295. _, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index))
  296. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
  297. IssueID: issueBase.ID,
  298. RefRepoID: 10,
  299. RefIssueID: issueRef.ID,
  300. RefCommentID: 0,
  301. RefIsPull: false,
  302. RefAction: references.XRefActionNone,
  303. })
  304. }
  305. func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *issues_model.Issue) {
  306. session := loginUser(t, user)
  307. issueURL := testNewIssue(t, session, user, fmt.Sprintf("repo%d", repoID), title, content)
  308. indexStr := issueURL[strings.LastIndexByte(issueURL, '/')+1:]
  309. index, err := strconv.Atoi(indexStr)
  310. assert.NoError(t, err, "Invalid issue href: %s", issueURL)
  311. issue := &issues_model.Issue{RepoID: repoID, Index: int64(index)}
  312. unittest.AssertExistsAndLoadBean(t, issue)
  313. return issueURL, issue
  314. }
  315. func testIssueChangeInfo(t *testing.T, user, issueURL, info, value string) {
  316. session := loginUser(t, user)
  317. req := NewRequest(t, "GET", issueURL)
  318. resp := session.MakeRequest(t, req, http.StatusOK)
  319. htmlDoc := NewHTMLParser(t, resp.Body)
  320. req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
  321. "_csrf": htmlDoc.GetCSRF(),
  322. info: value,
  323. })
  324. _ = session.MakeRequest(t, req, http.StatusOK)
  325. }
  326. func TestIssueRedirect(t *testing.T) {
  327. defer tests.PrepareTestEnv(t)()
  328. session := loginUser(t, "user2")
  329. // Test external tracker where style not set (shall default numeric)
  330. req := NewRequest(t, "GET", path.Join("org26", "repo_external_tracker", "issues", "1"))
  331. resp := session.MakeRequest(t, req, http.StatusSeeOther)
  332. assert.Equal(t, "https://tracker.com/org26/repo_external_tracker/issues/1", test.RedirectURL(resp))
  333. // Test external tracker with numeric style
  334. req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_numeric", "issues", "1"))
  335. resp = session.MakeRequest(t, req, http.StatusSeeOther)
  336. assert.Equal(t, "https://tracker.com/org26/repo_external_tracker_numeric/issues/1", test.RedirectURL(resp))
  337. // Test external tracker with alphanumeric style (for a pull request)
  338. req = NewRequest(t, "GET", path.Join("org26", "repo_external_tracker_alpha", "issues", "1"))
  339. resp = session.MakeRequest(t, req, http.StatusSeeOther)
  340. assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
  341. }
  342. func TestSearchIssues(t *testing.T) {
  343. defer tests.PrepareTestEnv(t)()
  344. session := loginUser(t, "user2")
  345. expectedIssueCount := 18 // from the fixtures
  346. if expectedIssueCount > setting.UI.IssuePagingNum {
  347. expectedIssueCount = setting.UI.IssuePagingNum
  348. }
  349. link, _ := url.Parse("/issues/search")
  350. req := NewRequest(t, "GET", link.String())
  351. resp := session.MakeRequest(t, req, http.StatusOK)
  352. var apiIssues []*api.Issue
  353. DecodeJSON(t, resp, &apiIssues)
  354. assert.Len(t, apiIssues, expectedIssueCount)
  355. since := "2000-01-01T00:50:01+00:00" // 946687801
  356. before := time.Unix(999307200, 0).Format(time.RFC3339)
  357. query := url.Values{}
  358. query.Add("since", since)
  359. query.Add("before", before)
  360. link.RawQuery = query.Encode()
  361. req = NewRequest(t, "GET", link.String())
  362. resp = session.MakeRequest(t, req, http.StatusOK)
  363. DecodeJSON(t, resp, &apiIssues)
  364. assert.Len(t, apiIssues, 11)
  365. query.Del("since")
  366. query.Del("before")
  367. query.Add("state", "closed")
  368. link.RawQuery = query.Encode()
  369. req = NewRequest(t, "GET", link.String())
  370. resp = session.MakeRequest(t, req, http.StatusOK)
  371. DecodeJSON(t, resp, &apiIssues)
  372. assert.Len(t, apiIssues, 2)
  373. query.Set("state", "all")
  374. link.RawQuery = query.Encode()
  375. req = NewRequest(t, "GET", link.String())
  376. resp = session.MakeRequest(t, req, http.StatusOK)
  377. DecodeJSON(t, resp, &apiIssues)
  378. assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
  379. assert.Len(t, apiIssues, 20)
  380. query.Add("limit", "5")
  381. link.RawQuery = query.Encode()
  382. req = NewRequest(t, "GET", link.String())
  383. resp = session.MakeRequest(t, req, http.StatusOK)
  384. DecodeJSON(t, resp, &apiIssues)
  385. assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
  386. assert.Len(t, apiIssues, 5)
  387. query = url.Values{"assigned": {"true"}, "state": {"all"}}
  388. link.RawQuery = query.Encode()
  389. req = NewRequest(t, "GET", link.String())
  390. resp = session.MakeRequest(t, req, http.StatusOK)
  391. DecodeJSON(t, resp, &apiIssues)
  392. assert.Len(t, apiIssues, 2)
  393. query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
  394. link.RawQuery = query.Encode()
  395. req = NewRequest(t, "GET", link.String())
  396. resp = session.MakeRequest(t, req, http.StatusOK)
  397. DecodeJSON(t, resp, &apiIssues)
  398. assert.Len(t, apiIssues, 1)
  399. query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
  400. link.RawQuery = query.Encode()
  401. req = NewRequest(t, "GET", link.String())
  402. resp = session.MakeRequest(t, req, http.StatusOK)
  403. DecodeJSON(t, resp, &apiIssues)
  404. assert.Len(t, apiIssues, 2)
  405. query = url.Values{"owner": {"user2"}} // user
  406. link.RawQuery = query.Encode()
  407. req = NewRequest(t, "GET", link.String())
  408. resp = session.MakeRequest(t, req, http.StatusOK)
  409. DecodeJSON(t, resp, &apiIssues)
  410. assert.Len(t, apiIssues, 8)
  411. query = url.Values{"owner": {"org3"}} // organization
  412. link.RawQuery = query.Encode()
  413. req = NewRequest(t, "GET", link.String())
  414. resp = session.MakeRequest(t, req, http.StatusOK)
  415. DecodeJSON(t, resp, &apiIssues)
  416. assert.Len(t, apiIssues, 5)
  417. query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team
  418. link.RawQuery = query.Encode()
  419. req = NewRequest(t, "GET", link.String())
  420. resp = session.MakeRequest(t, req, http.StatusOK)
  421. DecodeJSON(t, resp, &apiIssues)
  422. assert.Len(t, apiIssues, 2)
  423. }
  424. func TestSearchIssuesWithLabels(t *testing.T) {
  425. defer tests.PrepareTestEnv(t)()
  426. expectedIssueCount := 18 // from the fixtures
  427. if expectedIssueCount > setting.UI.IssuePagingNum {
  428. expectedIssueCount = setting.UI.IssuePagingNum
  429. }
  430. session := loginUser(t, "user1")
  431. link, _ := url.Parse("/issues/search")
  432. query := url.Values{}
  433. var apiIssues []*api.Issue
  434. link.RawQuery = query.Encode()
  435. req := NewRequest(t, "GET", link.String())
  436. resp := session.MakeRequest(t, req, http.StatusOK)
  437. DecodeJSON(t, resp, &apiIssues)
  438. assert.Len(t, apiIssues, expectedIssueCount)
  439. query.Add("labels", "label1")
  440. link.RawQuery = query.Encode()
  441. req = NewRequest(t, "GET", link.String())
  442. resp = session.MakeRequest(t, req, http.StatusOK)
  443. DecodeJSON(t, resp, &apiIssues)
  444. assert.Len(t, apiIssues, 2)
  445. // multiple labels
  446. query.Set("labels", "label1,label2")
  447. link.RawQuery = query.Encode()
  448. req = NewRequest(t, "GET", link.String())
  449. resp = session.MakeRequest(t, req, http.StatusOK)
  450. DecodeJSON(t, resp, &apiIssues)
  451. assert.Len(t, apiIssues, 2)
  452. // an org label
  453. query.Set("labels", "orglabel4")
  454. link.RawQuery = query.Encode()
  455. req = NewRequest(t, "GET", link.String())
  456. resp = session.MakeRequest(t, req, http.StatusOK)
  457. DecodeJSON(t, resp, &apiIssues)
  458. assert.Len(t, apiIssues, 1)
  459. // org and repo label
  460. query.Set("labels", "label2,orglabel4")
  461. query.Add("state", "all")
  462. link.RawQuery = query.Encode()
  463. req = NewRequest(t, "GET", link.String())
  464. resp = session.MakeRequest(t, req, http.StatusOK)
  465. DecodeJSON(t, resp, &apiIssues)
  466. assert.Len(t, apiIssues, 2)
  467. // org and repo label which share the same issue
  468. query.Set("labels", "label1,orglabel4")
  469. link.RawQuery = query.Encode()
  470. req = NewRequest(t, "GET", link.String())
  471. resp = session.MakeRequest(t, req, http.StatusOK)
  472. DecodeJSON(t, resp, &apiIssues)
  473. assert.Len(t, apiIssues, 2)
  474. }
  475. func TestGetIssueInfo(t *testing.T) {
  476. defer tests.PrepareTestEnv(t)()
  477. issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  478. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
  479. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
  480. assert.NoError(t, issue.LoadAttributes(db.DefaultContext))
  481. assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix))
  482. assert.Equal(t, api.StateOpen, issue.State())
  483. session := loginUser(t, owner.Name)
  484. urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index)
  485. req := NewRequest(t, "GET", urlStr)
  486. resp := session.MakeRequest(t, req, http.StatusOK)
  487. var apiIssue api.Issue
  488. DecodeJSON(t, resp, &apiIssue)
  489. assert.EqualValues(t, issue.ID, apiIssue.ID)
  490. }
  491. func TestUpdateIssueDeadline(t *testing.T) {
  492. defer tests.PrepareTestEnv(t)()
  493. issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  494. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
  495. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  496. assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
  497. assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
  498. assert.Equal(t, api.StateOpen, issueBefore.State())
  499. session := loginUser(t, owner.Name)
  500. issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
  501. req := NewRequest(t, "GET", issueURL)
  502. resp := session.MakeRequest(t, req, http.StatusOK)
  503. htmlDoc := NewHTMLParser(t, resp.Body)
  504. urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF()
  505. req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{
  506. "due_date": "2022-04-06T00:00:00.000Z",
  507. })
  508. resp = session.MakeRequest(t, req, http.StatusCreated)
  509. var apiIssue api.IssueDeadline
  510. DecodeJSON(t, resp, &apiIssue)
  511. assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
  512. }
  513. func TestIssueReferenceURL(t *testing.T) {
  514. defer tests.PrepareTestEnv(t)()
  515. session := loginUser(t, "user2")
  516. issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
  517. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
  518. req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.FullName(), issue.Index))
  519. resp := session.MakeRequest(t, req, http.StatusOK)
  520. htmlDoc := NewHTMLParser(t, resp.Body)
  521. // the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains
  522. ref, _ := htmlDoc.Find(`.timeline-item.comment.first .reference-issue`).Attr("data-reference")
  523. assert.EqualValues(t, "/user2/repo1/issues/1#issue-1", ref)
  524. ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.first) .reference-issue`).Attr("data-reference")
  525. assert.EqualValues(t, "/user2/repo1/issues/1#issuecomment-2", ref)
  526. }