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