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.

api_issue_test.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "sync"
  10. "testing"
  11. "time"
  12. auth_model "code.gitea.io/gitea/models/auth"
  13. "code.gitea.io/gitea/models/db"
  14. issues_model "code.gitea.io/gitea/models/issues"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. "code.gitea.io/gitea/models/unittest"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/setting"
  19. api "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/tests"
  21. "github.com/stretchr/testify/assert"
  22. )
  23. func TestAPIListIssues(t *testing.T) {
  24. defer tests.PrepareTestEnv(t)()
  25. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  26. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
  27. session := loginUser(t, owner.Name)
  28. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
  29. link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name))
  30. link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode()
  31. resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  32. var apiIssues []*api.Issue
  33. DecodeJSON(t, resp, &apiIssues)
  34. assert.Len(t, apiIssues, unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}))
  35. for _, apiIssue := range apiIssues {
  36. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: apiIssue.ID, RepoID: repo.ID})
  37. }
  38. // test milestone filter
  39. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode()
  40. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  41. DecodeJSON(t, resp, &apiIssues)
  42. if assert.Len(t, apiIssues, 2) {
  43. assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
  44. assert.EqualValues(t, 1, apiIssues[1].Milestone.ID)
  45. }
  46. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "created_by": {"user2"}}.Encode()
  47. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  48. DecodeJSON(t, resp, &apiIssues)
  49. if assert.Len(t, apiIssues, 1) {
  50. assert.EqualValues(t, 5, apiIssues[0].ID)
  51. }
  52. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "assigned_by": {"user1"}}.Encode()
  53. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  54. DecodeJSON(t, resp, &apiIssues)
  55. if assert.Len(t, apiIssues, 1) {
  56. assert.EqualValues(t, 1, apiIssues[0].ID)
  57. }
  58. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "mentioned_by": {"user4"}}.Encode()
  59. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  60. DecodeJSON(t, resp, &apiIssues)
  61. if assert.Len(t, apiIssues, 1) {
  62. assert.EqualValues(t, 1, apiIssues[0].ID)
  63. }
  64. }
  65. func TestAPICreateIssue(t *testing.T) {
  66. defer tests.PrepareTestEnv(t)()
  67. const body, title = "apiTestBody", "apiTestTitle"
  68. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  69. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  70. session := loginUser(t, owner.Name)
  71. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  72. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token)
  73. req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
  74. Body: body,
  75. Title: title,
  76. Assignee: owner.Name,
  77. })
  78. resp := MakeRequest(t, req, http.StatusCreated)
  79. var apiIssue api.Issue
  80. DecodeJSON(t, resp, &apiIssue)
  81. assert.Equal(t, body, apiIssue.Body)
  82. assert.Equal(t, title, apiIssue.Title)
  83. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
  84. RepoID: repoBefore.ID,
  85. AssigneeID: owner.ID,
  86. Content: body,
  87. Title: title,
  88. })
  89. repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  90. assert.Equal(t, repoBefore.NumIssues+1, repoAfter.NumIssues)
  91. assert.Equal(t, repoBefore.NumClosedIssues, repoAfter.NumClosedIssues)
  92. }
  93. func TestAPICreateIssueParallel(t *testing.T) {
  94. defer tests.PrepareTestEnv(t)()
  95. const body, title = "apiTestBody", "apiTestTitle"
  96. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  97. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  98. session := loginUser(t, owner.Name)
  99. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  100. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token)
  101. var wg sync.WaitGroup
  102. for i := 0; i < 10; i++ {
  103. wg.Add(1)
  104. go func(parentT *testing.T, i int) {
  105. parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
  106. newTitle := title + strconv.Itoa(i)
  107. newBody := body + strconv.Itoa(i)
  108. req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
  109. Body: newBody,
  110. Title: newTitle,
  111. Assignee: owner.Name,
  112. })
  113. resp := MakeRequest(t, req, http.StatusCreated)
  114. var apiIssue api.Issue
  115. DecodeJSON(t, resp, &apiIssue)
  116. assert.Equal(t, newBody, apiIssue.Body)
  117. assert.Equal(t, newTitle, apiIssue.Title)
  118. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
  119. RepoID: repoBefore.ID,
  120. AssigneeID: owner.ID,
  121. Content: newBody,
  122. Title: newTitle,
  123. })
  124. wg.Done()
  125. })
  126. }(t, i)
  127. }
  128. wg.Wait()
  129. }
  130. func TestAPIEditIssue(t *testing.T) {
  131. defer tests.PrepareTestEnv(t)()
  132. issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  133. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
  134. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  135. assert.NoError(t, issueBefore.LoadAttributes(db.DefaultContext))
  136. assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
  137. assert.Equal(t, api.StateOpen, issueBefore.State())
  138. session := loginUser(t, owner.Name)
  139. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  140. // update values of issue
  141. issueState := "closed"
  142. removeDeadline := true
  143. milestone := int64(4)
  144. body := "new content!"
  145. title := "new title from api set"
  146. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d?token=%s", owner.Name, repoBefore.Name, issueBefore.Index, token)
  147. req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
  148. State: &issueState,
  149. RemoveDeadline: &removeDeadline,
  150. Milestone: &milestone,
  151. Body: &body,
  152. Title: title,
  153. // ToDo change more
  154. })
  155. resp := MakeRequest(t, req, http.StatusCreated)
  156. var apiIssue api.Issue
  157. DecodeJSON(t, resp, &apiIssue)
  158. issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  159. repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
  160. // check deleted user
  161. assert.Equal(t, int64(500), issueAfter.PosterID)
  162. assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
  163. assert.Equal(t, int64(-1), issueAfter.PosterID)
  164. assert.Equal(t, int64(-1), issueBefore.PosterID)
  165. assert.Equal(t, int64(-1), apiIssue.Poster.ID)
  166. // check repo change
  167. assert.Equal(t, repoBefore.NumClosedIssues+1, repoAfter.NumClosedIssues)
  168. // API response
  169. assert.Equal(t, api.StateClosed, apiIssue.State)
  170. assert.Equal(t, milestone, apiIssue.Milestone.ID)
  171. assert.Equal(t, body, apiIssue.Body)
  172. assert.True(t, apiIssue.Deadline == nil)
  173. assert.Equal(t, title, apiIssue.Title)
  174. // in database
  175. assert.Equal(t, api.StateClosed, issueAfter.State())
  176. assert.Equal(t, milestone, issueAfter.MilestoneID)
  177. assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix))
  178. assert.Equal(t, body, issueAfter.Content)
  179. assert.Equal(t, title, issueAfter.Title)
  180. }
  181. func TestAPISearchIssues(t *testing.T) {
  182. defer tests.PrepareTestEnv(t)()
  183. token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue)
  184. // as this API was used in the frontend, it uses UI page size
  185. expectedIssueCount := 18 // from the fixtures
  186. if expectedIssueCount > setting.UI.IssuePagingNum {
  187. expectedIssueCount = setting.UI.IssuePagingNum
  188. }
  189. link, _ := url.Parse("/api/v1/repos/issues/search")
  190. query := url.Values{"token": {getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)}}
  191. var apiIssues []*api.Issue
  192. link.RawQuery = query.Encode()
  193. req := NewRequest(t, "GET", link.String())
  194. resp := MakeRequest(t, req, http.StatusOK)
  195. DecodeJSON(t, resp, &apiIssues)
  196. assert.Len(t, apiIssues, expectedIssueCount)
  197. since := "2000-01-01T00:50:01+00:00" // 946687801
  198. before := time.Unix(999307200, 0).Format(time.RFC3339)
  199. query.Add("since", since)
  200. query.Add("before", before)
  201. query.Add("token", token)
  202. link.RawQuery = query.Encode()
  203. req = NewRequest(t, "GET", link.String())
  204. resp = MakeRequest(t, req, http.StatusOK)
  205. DecodeJSON(t, resp, &apiIssues)
  206. assert.Len(t, apiIssues, 11)
  207. query.Del("since")
  208. query.Del("before")
  209. query.Add("state", "closed")
  210. link.RawQuery = query.Encode()
  211. req = NewRequest(t, "GET", link.String())
  212. resp = MakeRequest(t, req, http.StatusOK)
  213. DecodeJSON(t, resp, &apiIssues)
  214. assert.Len(t, apiIssues, 2)
  215. query.Set("state", "all")
  216. link.RawQuery = query.Encode()
  217. req = NewRequest(t, "GET", link.String())
  218. resp = MakeRequest(t, req, http.StatusOK)
  219. DecodeJSON(t, resp, &apiIssues)
  220. assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
  221. assert.Len(t, apiIssues, 20)
  222. query.Add("limit", "10")
  223. link.RawQuery = query.Encode()
  224. req = NewRequest(t, "GET", link.String())
  225. resp = MakeRequest(t, req, http.StatusOK)
  226. DecodeJSON(t, resp, &apiIssues)
  227. assert.EqualValues(t, "20", resp.Header().Get("X-Total-Count"))
  228. assert.Len(t, apiIssues, 10)
  229. query = url.Values{"assigned": {"true"}, "state": {"all"}, "token": {token}}
  230. link.RawQuery = query.Encode()
  231. req = NewRequest(t, "GET", link.String())
  232. resp = MakeRequest(t, req, http.StatusOK)
  233. DecodeJSON(t, resp, &apiIssues)
  234. assert.Len(t, apiIssues, 2)
  235. query = url.Values{"milestones": {"milestone1"}, "state": {"all"}, "token": {token}}
  236. link.RawQuery = query.Encode()
  237. req = NewRequest(t, "GET", link.String())
  238. resp = MakeRequest(t, req, http.StatusOK)
  239. DecodeJSON(t, resp, &apiIssues)
  240. assert.Len(t, apiIssues, 1)
  241. query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}, "token": {token}}
  242. link.RawQuery = query.Encode()
  243. req = NewRequest(t, "GET", link.String())
  244. resp = MakeRequest(t, req, http.StatusOK)
  245. DecodeJSON(t, resp, &apiIssues)
  246. assert.Len(t, apiIssues, 2)
  247. query = url.Values{"owner": {"user2"}, "token": {token}} // user
  248. link.RawQuery = query.Encode()
  249. req = NewRequest(t, "GET", link.String())
  250. resp = MakeRequest(t, req, http.StatusOK)
  251. DecodeJSON(t, resp, &apiIssues)
  252. assert.Len(t, apiIssues, 8)
  253. query = url.Values{"owner": {"org3"}, "token": {token}} // organization
  254. link.RawQuery = query.Encode()
  255. req = NewRequest(t, "GET", link.String())
  256. resp = MakeRequest(t, req, http.StatusOK)
  257. DecodeJSON(t, resp, &apiIssues)
  258. assert.Len(t, apiIssues, 5)
  259. query = url.Values{"owner": {"org3"}, "team": {"team1"}, "token": {token}} // organization + team
  260. link.RawQuery = query.Encode()
  261. req = NewRequest(t, "GET", link.String())
  262. resp = MakeRequest(t, req, http.StatusOK)
  263. DecodeJSON(t, resp, &apiIssues)
  264. assert.Len(t, apiIssues, 2)
  265. }
  266. func TestAPISearchIssuesWithLabels(t *testing.T) {
  267. defer tests.PrepareTestEnv(t)()
  268. // as this API was used in the frontend, it uses UI page size
  269. expectedIssueCount := 18 // from the fixtures
  270. if expectedIssueCount > setting.UI.IssuePagingNum {
  271. expectedIssueCount = setting.UI.IssuePagingNum
  272. }
  273. link, _ := url.Parse("/api/v1/repos/issues/search")
  274. query := url.Values{"token": {getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)}}
  275. var apiIssues []*api.Issue
  276. link.RawQuery = query.Encode()
  277. req := NewRequest(t, "GET", link.String())
  278. resp := MakeRequest(t, req, http.StatusOK)
  279. DecodeJSON(t, resp, &apiIssues)
  280. assert.Len(t, apiIssues, expectedIssueCount)
  281. query.Add("labels", "label1")
  282. link.RawQuery = query.Encode()
  283. req = NewRequest(t, "GET", link.String())
  284. resp = MakeRequest(t, req, http.StatusOK)
  285. DecodeJSON(t, resp, &apiIssues)
  286. assert.Len(t, apiIssues, 2)
  287. // multiple labels
  288. query.Set("labels", "label1,label2")
  289. link.RawQuery = query.Encode()
  290. req = NewRequest(t, "GET", link.String())
  291. resp = MakeRequest(t, req, http.StatusOK)
  292. DecodeJSON(t, resp, &apiIssues)
  293. assert.Len(t, apiIssues, 2)
  294. // an org label
  295. query.Set("labels", "orglabel4")
  296. link.RawQuery = query.Encode()
  297. req = NewRequest(t, "GET", link.String())
  298. resp = MakeRequest(t, req, http.StatusOK)
  299. DecodeJSON(t, resp, &apiIssues)
  300. assert.Len(t, apiIssues, 1)
  301. // org and repo label
  302. query.Set("labels", "label2,orglabel4")
  303. query.Add("state", "all")
  304. link.RawQuery = query.Encode()
  305. req = NewRequest(t, "GET", link.String())
  306. resp = MakeRequest(t, req, http.StatusOK)
  307. DecodeJSON(t, resp, &apiIssues)
  308. assert.Len(t, apiIssues, 2)
  309. // org and repo label which share the same issue
  310. query.Set("labels", "label1,orglabel4")
  311. link.RawQuery = query.Encode()
  312. req = NewRequest(t, "GET", link.String())
  313. resp = MakeRequest(t, req, http.StatusOK)
  314. DecodeJSON(t, resp, &apiIssues)
  315. assert.Len(t, apiIssues, 2)
  316. }