diff options
Diffstat (limited to 'models/issues/issue_test.go')
-rw-r--r-- | models/issues/issue_test.go | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go new file mode 100644 index 0000000000..019e578da8 --- /dev/null +++ b/models/issues/issue_test.go @@ -0,0 +1,562 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package issues_test + +import ( + "context" + "fmt" + "sort" + "strconv" + "sync" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/foreignreference" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "xorm.io/builder" +) + +func TestIssue_ReplaceLabels(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(issueID int64, labelIDs []int64) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID}).(*issues_model.Issue) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + + labels := make([]*issues_model.Label, len(labelIDs)) + for i, labelID := range labelIDs { + labels[i] = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID, RepoID: repo.ID}).(*issues_model.Label) + } + assert.NoError(t, issues_model.ReplaceIssueLabels(issue, labels, doer)) + unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issueID}, len(labelIDs)) + for _, labelID := range labelIDs { + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: labelID}) + } + } + + testSuccess(1, []int64{2}) + testSuccess(1, []int64{1, 2}) + testSuccess(1, []int64{}) +} + +func Test_GetIssueIDsByRepoID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + ids, err := issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1) + assert.NoError(t, err) + assert.Len(t, ids, 5) +} + +func TestIssueAPIURL(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}).(*issues_model.Issue) + err := issue.LoadAttributes(db.DefaultContext) + + assert.NoError(t, err) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL()) +} + +func TestGetIssuesByIDs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) { + issues, err := issues_model.GetIssuesByIDs(db.DefaultContext, append(expectedIssueIDs, nonExistentIssueIDs...)) + assert.NoError(t, err) + actualIssueIDs := make([]int64, len(issues)) + for i, issue := range issues { + actualIssueIDs[i] = issue.ID + } + assert.Equal(t, expectedIssueIDs, actualIssueIDs) + } + testSuccess([]int64{1, 2, 3}, []int64{}) + testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID}) +} + +func TestGetParticipantIDsByIssue(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + checkParticipants := func(issueID int64, userIDs []int) { + issue, err := issues_model.GetIssueByID(db.DefaultContext, issueID) + assert.NoError(t, err) + participants, err := issue.GetParticipantIDsByIssue(db.DefaultContext) + if assert.NoError(t, err) { + participantsIDs := make([]int, len(participants)) + for i, uid := range participants { + participantsIDs[i] = int(uid) + } + sort.Ints(participantsIDs) + sort.Ints(userIDs) + assert.Equal(t, userIDs, participantsIDs) + } + } + + // User 1 is issue1 poster (see fixtures/issue.yml) + // User 2 only labeled issue1 (see fixtures/comment.yml) + // Users 3 and 5 made actual comments (see fixtures/comment.yml) + // User 3 is inactive, thus not active participant + checkParticipants(1, []int{1, 5}) +} + +func TestIssue_ClearLabels(t *testing.T) { + tests := []struct { + issueID int64 + doerID int64 + }{ + {1, 2}, // non-pull-request, has labels + {2, 2}, // pull-request, has labels + {3, 2}, // pull-request, has no labels + } + for _, test := range tests { + assert.NoError(t, unittest.PrepareTestDatabase()) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}).(*issues_model.Issue) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID}).(*user_model.User) + assert.NoError(t, issues_model.ClearIssueLabels(issue, doer)) + unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID}) + } +} + +func TestUpdateIssueCols(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}).(*issues_model.Issue) + + const newTitle = "New Title for unit test" + issue.Title = newTitle + + prevContent := issue.Content + issue.Content = "This should have no effect" + + now := time.Now().Unix() + assert.NoError(t, issues_model.UpdateIssueCols(db.DefaultContext, issue, "name")) + then := time.Now().Unix() + + updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}).(*issues_model.Issue) + assert.EqualValues(t, newTitle, updatedIssue.Title) + assert.EqualValues(t, prevContent, updatedIssue.Content) + unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) +} + +func TestIssues(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + for _, test := range []struct { + Opts issues_model.IssuesOptions + ExpectedIssueIDs []int64 + }{ + { + issues_model.IssuesOptions{ + AssigneeID: 1, + SortType: "oldest", + }, + []int64{1, 6}, + }, + { + issues_model.IssuesOptions{ + RepoCond: builder.In("repo_id", 1, 3), + SortType: "oldest", + ListOptions: db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{1, 2, 3, 5}, + }, + { + issues_model.IssuesOptions{ + LabelIDs: []int64{1}, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{2, 1}, + }, + { + issues_model.IssuesOptions{ + LabelIDs: []int64{1, 2}, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests + }, + } { + issues, err := issues_model.Issues(&test.Opts) + assert.NoError(t, err) + if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { + for i, issue := range issues { + assert.EqualValues(t, test.ExpectedIssueIDs[i], issue.ID) + } + } + } +} + +func TestGetUserIssueStats(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + for _, test := range []struct { + Opts issues_model.UserIssueStatsOptions + ExpectedIssueStats issues_model.IssueStats + }{ + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + RepoIDs: []int64{1}, + FilterMode: issues_model.FilterModeAll, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 1, // 1 + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + RepoIDs: []int64{1}, + FilterMode: issues_model.FilterModeAll, + IsClosed: true, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 0, + CreateCount: 0, + OpenCount: 1, // 6 + ClosedCount: 1, // 1 + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + FilterMode: issues_model.FilterModeAssign, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 0, + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + FilterMode: issues_model.FilterModeCreate, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + OpenCount: 1, // 6 + ClosedCount: 0, + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + FilterMode: issues_model.FilterModeMention, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 6 + AssignCount: 1, // 6 + CreateCount: 1, // 6 + MentionCount: 0, + OpenCount: 0, + ClosedCount: 0, + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 1, + FilterMode: issues_model.FilterModeCreate, + IssueIDs: []int64{1}, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 1, // 1 + AssignCount: 1, // 1 + CreateCount: 1, // 1 + OpenCount: 1, // 1 + ClosedCount: 0, + }, + }, + { + issues_model.UserIssueStatsOptions{ + UserID: 2, + Org: unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}).(*organization.Organization), + Team: unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 7}).(*organization.Team), + FilterMode: issues_model.FilterModeAll, + }, + issues_model.IssueStats{ + YourRepositoriesCount: 2, + AssignCount: 1, + CreateCount: 1, + OpenCount: 2, + }, + }, + } { + t.Run(fmt.Sprintf("%#v", test.Opts), func(t *testing.T) { + stats, err := issues_model.GetUserIssueStats(test.Opts) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, test.ExpectedIssueStats, *stats) + }) + } +} + +func TestIssue_loadTotalTimes(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + ms, err := issues_model.GetIssueByID(db.DefaultContext, 2) + assert.NoError(t, err) + assert.NoError(t, ms.LoadTotalTimes(db.DefaultContext)) + assert.Equal(t, int64(3682), ms.TotalTrackedTime) +} + +func TestIssue_SearchIssueIDsByKeyword(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + total, ids, err := issues_model.SearchIssueIDsByKeyword(context.TODO(), "issue2", []int64{1}, 10, 0) + assert.NoError(t, err) + assert.EqualValues(t, 1, total) + assert.EqualValues(t, []int64{2}, ids) + + total, ids, err = issues_model.SearchIssueIDsByKeyword(context.TODO(), "first", []int64{1}, 10, 0) + assert.NoError(t, err) + assert.EqualValues(t, 1, total) + assert.EqualValues(t, []int64{1}, ids) + + total, ids, err = issues_model.SearchIssueIDsByKeyword(context.TODO(), "for", []int64{1}, 10, 0) + assert.NoError(t, err) + assert.EqualValues(t, 5, total) + assert.ElementsMatch(t, []int64{1, 2, 3, 5, 11}, ids) + + // issue1's comment id 2 + total, ids, err = issues_model.SearchIssueIDsByKeyword(context.TODO(), "good", []int64{1}, 10, 0) + assert.NoError(t, err) + assert.EqualValues(t, 1, total) + assert.EqualValues(t, []int64{1}, ids) +} + +func TestGetRepoIDsForIssuesOptions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + for _, test := range []struct { + Opts issues_model.IssuesOptions + ExpectedRepoIDs []int64 + }{ + { + issues_model.IssuesOptions{ + AssigneeID: 2, + }, + []int64{3, 32}, + }, + { + issues_model.IssuesOptions{ + RepoCond: builder.In("repo_id", 1, 2), + }, + []int64{1, 2}, + }, + } { + repoIDs, err := issues_model.GetRepoIDsForIssuesOptions(&test.Opts, user) + assert.NoError(t, err) + if assert.Len(t, repoIDs, len(test.ExpectedRepoIDs)) { + for i, repoID := range repoIDs { + assert.EqualValues(t, test.ExpectedRepoIDs[i], repoID) + } + } + } +} + +func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue { + var newIssue issues_model.Issue + t.Run(title, func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + + issue := issues_model.Issue{ + RepoID: repo.ID, + PosterID: user.ID, + Poster: user, + Title: title, + Content: content, + } + err := issues_model.NewIssue(repo, &issue, nil, nil) + assert.NoError(t, err) + + has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, issue.Title, newIssue.Title) + assert.EqualValues(t, issue.Content, newIssue.Content) + if expectIndex > 0 { + assert.EqualValues(t, expectIndex, newIssue.Index) + } + }) + return &newIssue +} + +func TestIssue_InsertIssue(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // there are 5 issues and max index is 5 on repository 1, so this one should 6 + issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6) + _, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue)) + assert.NoError(t, err) + + issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7) + _, err = db.GetEngine(db.DefaultContext).ID(issue.ID).Delete(new(issues_model.Issue)) + assert.NoError(t, err) +} + +func TestIssue_ResolveMentions(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) { + o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner}).(*user_model.User) + r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo}).(*repo_model.Repository) + issue := &issues_model.Issue{RepoID: r.ID} + d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer}).(*user_model.User) + resolved, err := issues_model.ResolveIssueMentionsByVisibility(db.DefaultContext, issue, d, mentions) + assert.NoError(t, err) + ids := make([]int64, len(resolved)) + for i, user := range resolved { + ids[i] = user.ID + } + sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) + assert.EqualValues(t, expected, ids) + } + + // Public repo, existing user + testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5}) + // Public repo, non-existing user + testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{}) + // Public repo, doer + testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{}) + // Private repo, team member + testSuccess("user17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) + // Private repo, not a team member + testSuccess("user17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) + // Private repo, whole team + testSuccess("user17", "big_test_private_4", "user15", []string{"user17/owners"}, []int64{18}) +} + +func TestResourceIndex(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func(i int) { + testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) + wg.Done() + }(i) + } + wg.Wait() +} + +func TestCorrectIssueStats(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // Because the condition is to have chunked database look-ups, + // We have to more issues than `maxQueryParameters`, we will insert. + // maxQueryParameters + 10 issues into the testDatabase. + // Each new issues will have a constant description "Bugs are nasty" + // Which will be used later on. + + issueAmount := issues_model.MaxQueryParameters + 10 + + var wg sync.WaitGroup + for i := 0; i < issueAmount; i++ { + wg.Add(1) + go func(i int) { + testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0) + wg.Done() + }(i) + } + wg.Wait() + + // Now we will get all issueID's that match the "Bugs are nasty" query. + total, ids, err := issues_model.SearchIssueIDsByKeyword(context.TODO(), "Bugs are nasty", []int64{1}, issueAmount, 0) + + // Just to be sure. + assert.NoError(t, err) + assert.EqualValues(t, issueAmount, total) + + // Now we will call the GetIssueStats with these IDs and if working, + // get the correct stats back. + issueStats, err := issues_model.GetIssueStats(&issues_model.IssueStatsOptions{ + RepoID: 1, + IssueIDs: ids, + }) + + // Now check the values. + assert.NoError(t, err) + assert.EqualValues(t, issueStats.OpenCount, issueAmount) +} + +func TestIssueForeignReference(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}).(*issues_model.Issue) + assert.NotEqualValues(t, issue.Index, issue.ID) // make sure they are different to avoid false positive + + // it is fine for an issue to not have a foreign reference + err := issue.LoadAttributes(db.DefaultContext) + assert.NoError(t, err) + assert.Nil(t, issue.ForeignReference) + + var foreignIndex int64 = 12345 + _, err = issues_model.GetIssueByForeignIndex(context.Background(), issue.RepoID, foreignIndex) + assert.True(t, foreignreference.IsErrLocalIndexNotExist(err)) + + err = db.Insert(db.DefaultContext, &foreignreference.ForeignReference{ + LocalIndex: issue.Index, + ForeignIndex: strconv.FormatInt(foreignIndex, 10), + RepoID: issue.RepoID, + Type: foreignreference.TypeIssue, + }) + assert.NoError(t, err) + + err = issue.LoadAttributes(db.DefaultContext) + assert.NoError(t, err) + + assert.EqualValues(t, issue.ForeignReference.ForeignIndex, strconv.FormatInt(foreignIndex, 10)) + + found, err := issues_model.GetIssueByForeignIndex(context.Background(), issue.RepoID, foreignIndex) + assert.NoError(t, err) + assert.EqualValues(t, found.Index, issue.Index) +} + +func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + miles := issues_model.MilestoneList{ + unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone), + } + + assert.NoError(t, miles.LoadTotalTrackedTimes()) + + assert.Equal(t, int64(3682), miles[0].TotalTrackedTime) +} + +func TestLoadTotalTrackedTime(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}).(*issues_model.Milestone) + + assert.NoError(t, milestone.LoadTotalTrackedTime()) + + assert.Equal(t, int64(3682), milestone.TotalTrackedTime) +} + +func TestCountIssues(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + count, err := issues_model.CountIssues(&issues_model.IssuesOptions{}) + assert.NoError(t, err) + assert.EqualValues(t, 17, count) +} |