code.gitea.io/gitea@v1.22.3/models/issues/issue_test.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package issues_test 5 6 import ( 7 "context" 8 "fmt" 9 "sort" 10 "sync" 11 "testing" 12 "time" 13 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/setting" 20 21 "github.com/stretchr/testify/assert" 22 "xorm.io/builder" 23 ) 24 25 func TestIssue_ReplaceLabels(t *testing.T) { 26 assert.NoError(t, unittest.PrepareTestDatabase()) 27 28 testSuccess := func(issueID int64, labelIDs, expectedLabelIDs []int64) { 29 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID}) 30 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) 31 doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 32 33 labels := make([]*issues_model.Label, len(labelIDs)) 34 for i, labelID := range labelIDs { 35 labels[i] = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID, RepoID: repo.ID}) 36 } 37 assert.NoError(t, issues_model.ReplaceIssueLabels(db.DefaultContext, issue, labels, doer)) 38 unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issueID}, len(expectedLabelIDs)) 39 for _, labelID := range expectedLabelIDs { 40 unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: labelID}) 41 } 42 } 43 44 testSuccess(1, []int64{2}, []int64{2}) 45 testSuccess(1, []int64{1, 2}, []int64{1, 2}) 46 testSuccess(1, []int64{}, []int64{}) 47 48 // mutually exclusive scoped labels 7 and 8 49 testSuccess(18, []int64{6, 7}, []int64{6, 7}) 50 testSuccess(18, []int64{7, 8}, []int64{8}) 51 testSuccess(18, []int64{6, 8, 7}, []int64{6, 7}) 52 } 53 54 func Test_GetIssueIDsByRepoID(t *testing.T) { 55 assert.NoError(t, unittest.PrepareTestDatabase()) 56 57 ids, err := issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1) 58 assert.NoError(t, err) 59 assert.Len(t, ids, 5) 60 } 61 62 func TestIssueAPIURL(t *testing.T) { 63 assert.NoError(t, unittest.PrepareTestDatabase()) 64 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) 65 err := issue.LoadAttributes(db.DefaultContext) 66 67 assert.NoError(t, err) 68 assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(db.DefaultContext)) 69 } 70 71 func TestGetIssuesByIDs(t *testing.T) { 72 assert.NoError(t, unittest.PrepareTestDatabase()) 73 testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) { 74 issues, err := issues_model.GetIssuesByIDs(db.DefaultContext, append(expectedIssueIDs, nonExistentIssueIDs...), true) 75 assert.NoError(t, err) 76 actualIssueIDs := make([]int64, len(issues)) 77 for i, issue := range issues { 78 actualIssueIDs[i] = issue.ID 79 } 80 assert.Equal(t, expectedIssueIDs, actualIssueIDs) 81 } 82 testSuccess([]int64{1, 2, 3}, []int64{}) 83 testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID}) 84 testSuccess([]int64{3, 2, 1}, []int64{}) 85 } 86 87 func TestGetParticipantIDsByIssue(t *testing.T) { 88 assert.NoError(t, unittest.PrepareTestDatabase()) 89 90 checkParticipants := func(issueID int64, userIDs []int) { 91 issue, err := issues_model.GetIssueByID(db.DefaultContext, issueID) 92 assert.NoError(t, err) 93 participants, err := issue.GetParticipantIDsByIssue(db.DefaultContext) 94 if assert.NoError(t, err) { 95 participantsIDs := make([]int, len(participants)) 96 for i, uid := range participants { 97 participantsIDs[i] = int(uid) 98 } 99 sort.Ints(participantsIDs) 100 sort.Ints(userIDs) 101 assert.Equal(t, userIDs, participantsIDs) 102 } 103 } 104 105 // User 1 is issue1 poster (see fixtures/issue.yml) 106 // User 2 only labeled issue1 (see fixtures/comment.yml) 107 // Users 3 and 5 made actual comments (see fixtures/comment.yml) 108 // User 3 is inactive, thus not active participant 109 checkParticipants(1, []int{1, 5}) 110 } 111 112 func TestIssue_ClearLabels(t *testing.T) { 113 tests := []struct { 114 issueID int64 115 doerID int64 116 }{ 117 {1, 2}, // non-pull-request, has labels 118 {2, 2}, // pull-request, has labels 119 {3, 2}, // pull-request, has no labels 120 } 121 for _, test := range tests { 122 assert.NoError(t, unittest.PrepareTestDatabase()) 123 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}) 124 doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID}) 125 assert.NoError(t, issues_model.ClearIssueLabels(db.DefaultContext, issue, doer)) 126 unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID}) 127 } 128 } 129 130 func TestUpdateIssueCols(t *testing.T) { 131 assert.NoError(t, unittest.PrepareTestDatabase()) 132 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) 133 134 const newTitle = "New Title for unit test" 135 issue.Title = newTitle 136 137 prevContent := issue.Content 138 issue.Content = "This should have no effect" 139 140 now := time.Now().Unix() 141 assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "name")) 142 then := time.Now().Unix() 143 144 updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}) 145 assert.EqualValues(t, newTitle, updatedIssue.Title) 146 assert.EqualValues(t, prevContent, updatedIssue.Content) 147 unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) 148 } 149 150 func TestIssues(t *testing.T) { 151 assert.NoError(t, unittest.PrepareTestDatabase()) 152 for _, test := range []struct { 153 Opts issues_model.IssuesOptions 154 ExpectedIssueIDs []int64 155 }{ 156 { 157 issues_model.IssuesOptions{ 158 AssigneeID: 1, 159 SortType: "oldest", 160 }, 161 []int64{1, 6}, 162 }, 163 { 164 issues_model.IssuesOptions{ 165 RepoCond: builder.In("repo_id", 1, 3), 166 SortType: "oldest", 167 Paginator: &db.ListOptions{ 168 Page: 1, 169 PageSize: 4, 170 }, 171 }, 172 []int64{1, 2, 3, 5}, 173 }, 174 { 175 issues_model.IssuesOptions{ 176 LabelIDs: []int64{1}, 177 Paginator: &db.ListOptions{ 178 Page: 1, 179 PageSize: 4, 180 }, 181 }, 182 []int64{2, 1}, 183 }, 184 { 185 issues_model.IssuesOptions{ 186 LabelIDs: []int64{1, 2}, 187 Paginator: &db.ListOptions{ 188 Page: 1, 189 PageSize: 4, 190 }, 191 }, 192 []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests 193 }, 194 { 195 issues_model.IssuesOptions{ 196 MilestoneIDs: []int64{1}, 197 }, 198 []int64{2}, 199 }, 200 } { 201 issues, err := issues_model.Issues(db.DefaultContext, &test.Opts) 202 assert.NoError(t, err) 203 if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { 204 for i, issue := range issues { 205 assert.EqualValues(t, test.ExpectedIssueIDs[i], issue.ID) 206 } 207 } 208 } 209 } 210 211 func TestIssue_loadTotalTimes(t *testing.T) { 212 assert.NoError(t, unittest.PrepareTestDatabase()) 213 ms, err := issues_model.GetIssueByID(db.DefaultContext, 2) 214 assert.NoError(t, err) 215 assert.NoError(t, ms.LoadTotalTimes(db.DefaultContext)) 216 assert.Equal(t, int64(3682), ms.TotalTrackedTime) 217 } 218 219 func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue { 220 var newIssue issues_model.Issue 221 t.Run(title, func(t *testing.T) { 222 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 223 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 224 225 issue := issues_model.Issue{ 226 RepoID: repo.ID, 227 PosterID: user.ID, 228 Poster: user, 229 Title: title, 230 Content: content, 231 } 232 err := issues_model.NewIssue(db.DefaultContext, repo, &issue, nil, nil) 233 assert.NoError(t, err) 234 235 has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) 236 assert.NoError(t, err) 237 assert.True(t, has) 238 assert.EqualValues(t, issue.Title, newIssue.Title) 239 assert.EqualValues(t, issue.Content, newIssue.Content) 240 if expectIndex > 0 { 241 assert.EqualValues(t, expectIndex, newIssue.Index) 242 } 243 }) 244 return &newIssue 245 } 246 247 func TestIssue_InsertIssue(t *testing.T) { 248 assert.NoError(t, unittest.PrepareTestDatabase()) 249 250 // there are 5 issues and max index is 5 on repository 1, so this one should 6 251 issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6) 252 _, err := db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID) 253 assert.NoError(t, err) 254 255 issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7) 256 _, err = db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID) 257 assert.NoError(t, err) 258 } 259 260 func TestIssue_ResolveMentions(t *testing.T) { 261 assert.NoError(t, unittest.PrepareTestDatabase()) 262 263 testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) { 264 o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner}) 265 r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo}) 266 issue := &issues_model.Issue{RepoID: r.ID} 267 d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer}) 268 resolved, err := issues_model.ResolveIssueMentionsByVisibility(db.DefaultContext, issue, d, mentions) 269 assert.NoError(t, err) 270 ids := make([]int64, len(resolved)) 271 for i, user := range resolved { 272 ids[i] = user.ID 273 } 274 sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) 275 assert.EqualValues(t, expected, ids) 276 } 277 278 // Public repo, existing user 279 testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5}) 280 // Public repo, non-existing user 281 testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{}) 282 // Public repo, doer 283 testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{}) 284 // Private repo, team member 285 testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) 286 // Private repo, not a team member 287 testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) 288 // Private repo, whole team 289 testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18}) 290 } 291 292 func TestResourceIndex(t *testing.T) { 293 assert.NoError(t, unittest.PrepareTestDatabase()) 294 295 var wg sync.WaitGroup 296 for i := 0; i < 100; i++ { 297 wg.Add(1) 298 go func(i int) { 299 testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) 300 wg.Done() 301 }(i) 302 } 303 wg.Wait() 304 } 305 306 func TestCorrectIssueStats(t *testing.T) { 307 assert.NoError(t, unittest.PrepareTestDatabase()) 308 309 // Because the condition is to have chunked database look-ups, 310 // We have to more issues than `maxQueryParameters`, we will insert. 311 // maxQueryParameters + 10 issues into the testDatabase. 312 // Each new issues will have a constant description "Bugs are nasty" 313 // Which will be used later on. 314 315 issueAmount := issues_model.MaxQueryParameters + 10 316 317 var wg sync.WaitGroup 318 for i := 0; i < issueAmount; i++ { 319 wg.Add(1) 320 go func(i int) { 321 testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0) 322 wg.Done() 323 }(i) 324 } 325 wg.Wait() 326 327 // Now we will get all issueID's that match the "Bugs are nasty" query. 328 issues, err := issues_model.Issues(context.TODO(), &issues_model.IssuesOptions{ 329 Paginator: &db.ListOptions{ 330 PageSize: issueAmount, 331 }, 332 RepoIDs: []int64{1}, 333 }) 334 total := int64(len(issues)) 335 var ids []int64 336 for _, issue := range issues { 337 if issue.Content == "Bugs are nasty" { 338 ids = append(ids, issue.ID) 339 } 340 } 341 342 // Just to be sure. 343 assert.NoError(t, err) 344 assert.EqualValues(t, issueAmount, total) 345 346 // Now we will call the GetIssueStats with these IDs and if working, 347 // get the correct stats back. 348 issueStats, err := issues_model.GetIssueStats(db.DefaultContext, &issues_model.IssuesOptions{ 349 RepoIDs: []int64{1}, 350 IssueIDs: ids, 351 }) 352 353 // Now check the values. 354 assert.NoError(t, err) 355 assert.EqualValues(t, issueStats.OpenCount, issueAmount) 356 } 357 358 func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { 359 assert.NoError(t, unittest.PrepareTestDatabase()) 360 miles := issues_model.MilestoneList{ 361 unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}), 362 } 363 364 assert.NoError(t, miles.LoadTotalTrackedTimes(db.DefaultContext)) 365 366 assert.Equal(t, int64(3682), miles[0].TotalTrackedTime) 367 } 368 369 func TestLoadTotalTrackedTime(t *testing.T) { 370 assert.NoError(t, unittest.PrepareTestDatabase()) 371 milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) 372 373 assert.NoError(t, milestone.LoadTotalTrackedTime(db.DefaultContext)) 374 375 assert.Equal(t, int64(3682), milestone.TotalTrackedTime) 376 } 377 378 func TestCountIssues(t *testing.T) { 379 assert.NoError(t, unittest.PrepareTestDatabase()) 380 count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{}) 381 assert.NoError(t, err) 382 assert.EqualValues(t, 22, count) 383 } 384 385 func TestIssueLoadAttributes(t *testing.T) { 386 assert.NoError(t, unittest.PrepareTestDatabase()) 387 setting.Service.EnableTimetracking = true 388 389 issueList := issues_model.IssueList{ 390 unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), 391 unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), 392 } 393 394 for _, issue := range issueList { 395 assert.NoError(t, issue.LoadAttributes(db.DefaultContext)) 396 assert.EqualValues(t, issue.RepoID, issue.Repo.ID) 397 for _, label := range issue.Labels { 398 assert.EqualValues(t, issue.RepoID, label.RepoID) 399 unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) 400 } 401 if issue.PosterID > 0 { 402 assert.EqualValues(t, issue.PosterID, issue.Poster.ID) 403 } 404 if issue.AssigneeID > 0 { 405 assert.EqualValues(t, issue.AssigneeID, issue.Assignee.ID) 406 } 407 if issue.MilestoneID > 0 { 408 assert.EqualValues(t, issue.MilestoneID, issue.Milestone.ID) 409 } 410 if issue.IsPull { 411 assert.EqualValues(t, issue.ID, issue.PullRequest.IssueID) 412 } 413 for _, attachment := range issue.Attachments { 414 assert.EqualValues(t, issue.ID, attachment.IssueID) 415 } 416 for _, comment := range issue.Comments { 417 assert.EqualValues(t, issue.ID, comment.IssueID) 418 } 419 if issue.ID == int64(1) { 420 assert.Equal(t, int64(400), issue.TotalTrackedTime) 421 assert.NotNil(t, issue.Project) 422 assert.Equal(t, int64(1), issue.Project.ID) 423 } else { 424 assert.Nil(t, issue.Project) 425 } 426 } 427 } 428 429 func assertCreateIssues(t *testing.T, isPull bool) { 430 assert.NoError(t, unittest.PrepareTestDatabase()) 431 reponame := "repo1" 432 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) 433 owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 434 label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) 435 milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) 436 assert.EqualValues(t, milestone.ID, 1) 437 reaction := &issues_model.Reaction{ 438 Type: "heart", 439 UserID: owner.ID, 440 } 441 442 title := "issuetitle1" 443 is := &issues_model.Issue{ 444 RepoID: repo.ID, 445 MilestoneID: milestone.ID, 446 Repo: repo, 447 Title: title, 448 Content: "issuecontent1", 449 IsPull: isPull, 450 PosterID: owner.ID, 451 Poster: owner, 452 IsClosed: true, 453 Labels: []*issues_model.Label{label}, 454 Reactions: []*issues_model.Reaction{reaction}, 455 } 456 err := issues_model.InsertIssues(db.DefaultContext, is) 457 assert.NoError(t, err) 458 459 i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{Title: title}) 460 unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: owner.ID, IssueID: i.ID}) 461 } 462 463 func TestMigrate_CreateIssuesIsPullFalse(t *testing.T) { 464 assertCreateIssues(t, false) 465 } 466 467 func TestMigrate_CreateIssuesIsPullTrue(t *testing.T) { 468 assertCreateIssues(t, true) 469 }