gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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. issues_model "code.gitea.io/gitea/models/issues"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. "code.gitea.io/gitea/models/unittest"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/setting"
  18. api "code.gitea.io/gitea/modules/structs"
  19. "code.gitea.io/gitea/tests"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. func TestAPIListIssues(t *testing.T) {
  23. defer tests.PrepareTestEnv(t)()
  24. repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  25. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
  26. session := loginUser(t, owner.Name)
  27. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
  28. link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name))
  29. link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode()
  30. resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  31. var apiIssues []*api.Issue
  32. DecodeJSON(t, resp, &apiIssues)
  33. assert.Len(t, apiIssues, unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID}))
  34. for _, apiIssue := range apiIssues {
  35. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: apiIssue.ID, RepoID: repo.ID})
  36. }
  37. // test milestone filter
  38. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "type": {"all"}, "milestones": {"ignore,milestone1,3,4"}}.Encode()
  39. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  40. DecodeJSON(t, resp, &apiIssues)
  41. if assert.Len(t, apiIssues, 2) {
  42. assert.EqualValues(t, 3, apiIssues[0].Milestone.ID)
  43. assert.EqualValues(t, 1, apiIssues[1].Milestone.ID)
  44. }
  45. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "created_by": {"user2"}}.Encode()
  46. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  47. DecodeJSON(t, resp, &apiIssues)
  48. if assert.Len(t, apiIssues, 1) {
  49. assert.EqualValues(t, 5, apiIssues[0].ID)
  50. }
  51. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "assigned_by": {"user1"}}.Encode()
  52. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  53. DecodeJSON(t, resp, &apiIssues)
  54. if assert.Len(t, apiIssues, 1) {
  55. assert.EqualValues(t, 1, apiIssues[0].ID)
  56. }
  57. link.RawQuery = url.Values{"token": {token}, "state": {"all"}, "mentioned_by": {"user4"}}.Encode()
  58. resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK)
  59. DecodeJSON(t, resp, &apiIssues)
  60. if assert.Len(t, apiIssues, 1) {
  61. assert.EqualValues(t, 1, apiIssues[0].ID)
  62. }
  63. }
  64. func TestAPIListIssuesPublicOnly(t *testing.T) {
  65. defer tests.PrepareTestEnv(t)()
  66. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  67. owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID})
  68. session := loginUser(t, owner1.Name)
  69. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
  70. link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name))
  71. link.RawQuery = url.Values{"state": {"all"}}.Encode()
  72. req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  73. MakeRequest(t, req, http.StatusOK)
  74. repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
  75. owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
  76. session = loginUser(t, owner2.Name)
  77. token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
  78. link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name))
  79. link.RawQuery = url.Values{"state": {"all"}}.Encode()
  80. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  81. MakeRequest(t, req, http.StatusOK)
  82. publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
  83. req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
  84. MakeRequest(t, req, http.StatusForbidden)
  85. }
  86. func TestAPICreateIssue(t *testing.T) {
  87. defer tests.PrepareTestEnv(t)()
  88. const body, title = "apiTestBody", "apiTestTitle"
  89. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  90. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  91. session := loginUser(t, owner.Name)
  92. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  93. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name)
  94. req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
  95. Body: body,
  96. Title: title,
  97. Assignee: owner.Name,
  98. }).AddTokenAuth(token)
  99. resp := MakeRequest(t, req, http.StatusCreated)
  100. var apiIssue api.Issue
  101. DecodeJSON(t, resp, &apiIssue)
  102. assert.Equal(t, body, apiIssue.Body)
  103. assert.Equal(t, title, apiIssue.Title)
  104. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
  105. RepoID: repoBefore.ID,
  106. AssigneeID: owner.ID,
  107. Content: body,
  108. Title: title,
  109. })
  110. repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  111. assert.Equal(t, repoBefore.NumIssues+1, repoAfter.NumIssues)
  112. assert.Equal(t, repoBefore.NumClosedIssues, repoAfter.NumClosedIssues)
  113. user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34})
  114. req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
  115. Title: title,
  116. }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue))
  117. MakeRequest(t, req, http.StatusForbidden)
  118. }
  119. func TestAPICreateIssueParallel(t *testing.T) {
  120. defer tests.PrepareTestEnv(t)()
  121. // FIXME: There seems to be a bug in github.com/mattn/go-sqlite3 with sqlite_unlock_notify, when doing concurrent writes to the same database,
  122. // some requests may get stuck in "go-sqlite3.(*SQLiteRows).Next", "go-sqlite3.(*SQLiteStmt).exec" and "go-sqlite3.unlock_notify_wait",
  123. // because the "unlock_notify_wait" never returns and the internal lock never gets releases.
  124. //
  125. // The trigger is: a previous test created issues and made the real issue indexer queue start processing, then this test does concurrent writing.
  126. // Adding this "Sleep" makes go-sqlite3 "finish" some internal operations before concurrent writes and then won't get stuck.
  127. // To reproduce: make a new test run these 2 tests enough times:
  128. // > func TestBug() { for i := 0; i < 100; i++ { testAPICreateIssue(t); testAPICreateIssueParallel(t) } }
  129. // Usually the test gets stuck in fewer than 10 iterations without this "sleep".
  130. time.Sleep(time.Second)
  131. const body, title = "apiTestBody", "apiTestTitle"
  132. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
  133. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  134. session := loginUser(t, owner.Name)
  135. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  136. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name)
  137. var wg sync.WaitGroup
  138. for i := range 10 {
  139. wg.Add(1)
  140. go func(parentT *testing.T, i int) {
  141. parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) {
  142. newTitle := title + strconv.Itoa(i)
  143. newBody := body + strconv.Itoa(i)
  144. req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{
  145. Body: newBody,
  146. Title: newTitle,
  147. Assignee: owner.Name,
  148. }).AddTokenAuth(token)
  149. resp := MakeRequest(t, req, http.StatusCreated)
  150. var apiIssue api.Issue
  151. DecodeJSON(t, resp, &apiIssue)
  152. assert.Equal(t, newBody, apiIssue.Body)
  153. assert.Equal(t, newTitle, apiIssue.Title)
  154. unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{
  155. RepoID: repoBefore.ID,
  156. AssigneeID: owner.ID,
  157. Content: newBody,
  158. Title: newTitle,
  159. })
  160. wg.Done()
  161. })
  162. }(t, i)
  163. }
  164. wg.Wait()
  165. }
  166. func TestAPIEditIssue(t *testing.T) {
  167. defer tests.PrepareTestEnv(t)()
  168. issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  169. repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
  170. owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID})
  171. assert.NoError(t, issueBefore.LoadAttributes(t.Context()))
  172. assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
  173. assert.Equal(t, api.StateOpen, issueBefore.State())
  174. session := loginUser(t, owner.Name)
  175. token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
  176. // update values of issue
  177. issueState := "closed"
  178. removeDeadline := true
  179. milestone := int64(4)
  180. body := "new content!"
  181. title := "new title from api set"
  182. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
  183. req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditIssueOption{
  184. State: &issueState,
  185. RemoveDeadline: &removeDeadline,
  186. Milestone: &milestone,
  187. Body: &body,
  188. Title: title,
  189. // ToDo change more
  190. }).AddTokenAuth(token)
  191. resp := MakeRequest(t, req, http.StatusCreated)
  192. var apiIssue api.Issue
  193. DecodeJSON(t, resp, &apiIssue)
  194. issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
  195. repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
  196. // check comment history
  197. unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issueAfter.ID, OldTitle: issueBefore.Title, NewTitle: title})
  198. unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: issueAfter.ID, ContentText: body, IsFirstCreated: false})
  199. // check deleted user
  200. assert.Equal(t, int64(500), issueAfter.PosterID)
  201. assert.NoError(t, issueAfter.LoadAttributes(t.Context()))
  202. assert.Equal(t, int64(-1), issueAfter.PosterID)
  203. assert.Equal(t, int64(-1), issueBefore.PosterID)
  204. assert.Equal(t, int64(-1), apiIssue.Poster.ID)
  205. // check repo change
  206. assert.Equal(t, repoBefore.NumClosedIssues+1, repoAfter.NumClosedIssues)
  207. // API response
  208. assert.Equal(t, api.StateClosed, apiIssue.State)
  209. assert.Equal(t, milestone, apiIssue.Milestone.ID)
  210. assert.Equal(t, body, apiIssue.Body)
  211. assert.Nil(t, apiIssue.Deadline)
  212. assert.Equal(t, title, apiIssue.Title)
  213. // in database
  214. assert.Equal(t, api.StateClosed, issueAfter.State())
  215. assert.Equal(t, milestone, issueAfter.MilestoneID)
  216. assert.Equal(t, int64(0), int64(issueAfter.DeadlineUnix))
  217. assert.Equal(t, body, issueAfter.Content)
  218. assert.Equal(t, title, issueAfter.Title)
  219. }
  220. func TestAPISearchIssues(t *testing.T) {
  221. defer tests.PrepareTestEnv(t)()
  222. // as this API was used in the frontend, it uses UI page size
  223. expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
  224. link, _ := url.Parse("/api/v1/repos/issues/search")
  225. token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
  226. query := url.Values{}
  227. var apiIssues []*api.Issue
  228. link.RawQuery = query.Encode()
  229. req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  230. resp := MakeRequest(t, req, http.StatusOK)
  231. DecodeJSON(t, resp, &apiIssues)
  232. assert.Len(t, apiIssues, expectedIssueCount)
  233. publicOnlyToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
  234. req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
  235. resp = MakeRequest(t, req, http.StatusOK)
  236. DecodeJSON(t, resp, &apiIssues)
  237. assert.Len(t, apiIssues, 15) // 15 public issues
  238. since := "2000-01-01T00:50:01+00:00" // 946687801
  239. before := time.Unix(999307200, 0).Format(time.RFC3339)
  240. query.Add("since", since)
  241. query.Add("before", before)
  242. link.RawQuery = query.Encode()
  243. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  244. resp = MakeRequest(t, req, http.StatusOK)
  245. DecodeJSON(t, resp, &apiIssues)
  246. assert.Len(t, apiIssues, 11)
  247. query.Del("since")
  248. query.Del("before")
  249. query.Add("state", "closed")
  250. link.RawQuery = query.Encode()
  251. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  252. resp = MakeRequest(t, req, http.StatusOK)
  253. DecodeJSON(t, resp, &apiIssues)
  254. assert.Len(t, apiIssues, 2)
  255. query.Set("state", "all")
  256. link.RawQuery = query.Encode()
  257. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  258. resp = MakeRequest(t, req, http.StatusOK)
  259. DecodeJSON(t, resp, &apiIssues)
  260. assert.Equal(t, "22", resp.Header().Get("X-Total-Count"))
  261. assert.Len(t, apiIssues, 20)
  262. query.Add("limit", "10")
  263. link.RawQuery = query.Encode()
  264. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  265. resp = MakeRequest(t, req, http.StatusOK)
  266. DecodeJSON(t, resp, &apiIssues)
  267. assert.Equal(t, "22", resp.Header().Get("X-Total-Count"))
  268. assert.Len(t, apiIssues, 10)
  269. query = url.Values{"assigned": {"true"}, "state": {"all"}}
  270. link.RawQuery = query.Encode()
  271. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  272. resp = MakeRequest(t, req, http.StatusOK)
  273. DecodeJSON(t, resp, &apiIssues)
  274. assert.Len(t, apiIssues, 2)
  275. query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
  276. link.RawQuery = query.Encode()
  277. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  278. resp = MakeRequest(t, req, http.StatusOK)
  279. DecodeJSON(t, resp, &apiIssues)
  280. assert.Len(t, apiIssues, 1)
  281. query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
  282. link.RawQuery = query.Encode()
  283. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  284. resp = MakeRequest(t, req, http.StatusOK)
  285. DecodeJSON(t, resp, &apiIssues)
  286. assert.Len(t, apiIssues, 2)
  287. query = url.Values{"owner": {"user2"}} // user
  288. link.RawQuery = query.Encode()
  289. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  290. resp = MakeRequest(t, req, http.StatusOK)
  291. DecodeJSON(t, resp, &apiIssues)
  292. assert.Len(t, apiIssues, 8)
  293. query = url.Values{"owner": {"org3"}} // organization
  294. link.RawQuery = query.Encode()
  295. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  296. resp = MakeRequest(t, req, http.StatusOK)
  297. DecodeJSON(t, resp, &apiIssues)
  298. assert.Len(t, apiIssues, 5)
  299. query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team
  300. link.RawQuery = query.Encode()
  301. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  302. resp = MakeRequest(t, req, http.StatusOK)
  303. DecodeJSON(t, resp, &apiIssues)
  304. assert.Len(t, apiIssues, 2)
  305. }
  306. func TestAPISearchIssuesWithLabels(t *testing.T) {
  307. defer tests.PrepareTestEnv(t)()
  308. // as this API was used in the frontend, it uses UI page size
  309. expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures
  310. link, _ := url.Parse("/api/v1/repos/issues/search")
  311. token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)
  312. query := url.Values{}
  313. var apiIssues []*api.Issue
  314. link.RawQuery = query.Encode()
  315. req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  316. resp := MakeRequest(t, req, http.StatusOK)
  317. DecodeJSON(t, resp, &apiIssues)
  318. assert.Len(t, apiIssues, expectedIssueCount)
  319. query.Add("labels", "label1")
  320. link.RawQuery = query.Encode()
  321. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  322. resp = MakeRequest(t, req, http.StatusOK)
  323. DecodeJSON(t, resp, &apiIssues)
  324. assert.Len(t, apiIssues, 2)
  325. // multiple labels
  326. query.Set("labels", "label1,label2")
  327. link.RawQuery = query.Encode()
  328. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  329. resp = MakeRequest(t, req, http.StatusOK)
  330. DecodeJSON(t, resp, &apiIssues)
  331. assert.Len(t, apiIssues, 2)
  332. // an org label
  333. query.Set("labels", "orglabel4")
  334. link.RawQuery = query.Encode()
  335. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  336. resp = MakeRequest(t, req, http.StatusOK)
  337. DecodeJSON(t, resp, &apiIssues)
  338. assert.Len(t, apiIssues, 1)
  339. // org and repo label
  340. query.Set("labels", "label2,orglabel4")
  341. query.Add("state", "all")
  342. link.RawQuery = query.Encode()
  343. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  344. resp = MakeRequest(t, req, http.StatusOK)
  345. DecodeJSON(t, resp, &apiIssues)
  346. assert.Len(t, apiIssues, 2)
  347. // org and repo label which share the same issue
  348. query.Set("labels", "label1,orglabel4")
  349. link.RawQuery = query.Encode()
  350. req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
  351. resp = MakeRequest(t, req, http.StatusOK)
  352. DecodeJSON(t, resp, &apiIssues)
  353. assert.Len(t, apiIssues, 2)
  354. }