diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 8bcadb0aa..662f2235d 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -3,6 +3,7 @@ repo_id: 1 index: 1 poster_id: 1 + assignee_id: 1 name: issue1 content: content1 is_closed: false diff --git a/models/fixtures/issue_user.yml b/models/fixtures/issue_user.yml new file mode 100644 index 000000000..b3f98a71d --- /dev/null +++ b/models/fixtures/issue_user.yml @@ -0,0 +1,23 @@ +- + id: 1 + uid: 1 + issue_id: 1 + is_read: true + is_assigned: true + is_mentioned: false + +- + id: 2 + uid: 2 + issue_id: 1 + is_read: true + is_assigned: false + is_mentioned: false + +- + id: 3 + uid: 4 + issue_id: 1 + is_read: false + is_assigned: false + is_mentioned: false diff --git a/models/issue.go b/models/issue.go index 7d7591837..2f4c15792 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1106,70 +1106,6 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { return issues, nil } -// .___ ____ ___ -// | | ______ ________ __ ____ | | \______ ___________ -// | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \ -// | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/ -// |___/____ >____ >____/ \___ >______//____ >\___ >__| -// \/ \/ \/ \/ \/ - -// IssueUser represents an issue-user relation. -type IssueUser struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` // User ID. - IssueID int64 - IsRead bool - IsAssigned bool - IsMentioned bool -} - -func newIssueUsers(e *xorm.Session, repo *Repository, issue *Issue) error { - assignees, err := repo.getAssignees(e) - if err != nil { - return fmt.Errorf("getAssignees: %v", err) - } - - // Poster can be anyone, append later if not one of assignees. - isPosterAssignee := false - - // Leave a seat for poster itself to append later, but if poster is one of assignee - // and just waste 1 unit is cheaper than re-allocate memory once. - issueUsers := make([]*IssueUser, 0, len(assignees)+1) - for _, assignee := range assignees { - issueUsers = append(issueUsers, &IssueUser{ - IssueID: issue.ID, - UID: assignee.ID, - IsAssigned: assignee.ID == issue.AssigneeID, - }) - isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID - } - if !isPosterAssignee { - issueUsers = append(issueUsers, &IssueUser{ - IssueID: issue.ID, - UID: issue.PosterID, - }) - } - - if _, err = e.Insert(issueUsers); err != nil { - return err - } - return nil -} - -// NewIssueUsers adds new issue-user relations for new issue of repository. -func NewIssueUsers(repo *Repository, issue *Issue) (err error) { - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - if err = newIssueUsers(sess, repo, issue); err != nil { - return err - } - - return sess.Commit() -} // UpdateIssueMentions extracts mentioned people from content and // updates issue-user relations for them. @@ -1400,67 +1336,6 @@ func UpdateIssue(issue *Issue) error { return updateIssue(x, issue) } -func updateIssueUserByAssignee(e *xorm.Session, issue *Issue) (err error) { - if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { - return err - } - - // Assignee ID equals to 0 means clear assignee. - if issue.AssigneeID > 0 { - if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { - return err - } - } - - return updateIssue(e, issue) -} - -// UpdateIssueUserByAssignee updates issue-user relation for assignee. -func UpdateIssueUserByAssignee(issue *Issue) (err error) { - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - if err = updateIssueUserByAssignee(sess, issue); err != nil { - return err - } - - return sess.Commit() -} - -// UpdateIssueUserByRead updates issue-user relation for reading. -func UpdateIssueUserByRead(uid, issueID int64) error { - _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) - return err -} - -// UpdateIssueUsersByMentions updates issue-user pairs by mentioning. -func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { - for _, uid := range uids { - iu := &IssueUser{ - UID: uid, - IssueID: issueID, - } - has, err := e.Get(iu) - if err != nil { - return err - } - - iu.IsMentioned = true - if has { - _, err = e.Id(iu.ID).AllCols().Update(iu) - } else { - _, err = e.Insert(iu) - } - if err != nil { - return err - } - } - return nil -} - // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/models/issue_user.go b/models/issue_user.go new file mode 100644 index 000000000..cb11a8a07 --- /dev/null +++ b/models/issue_user.go @@ -0,0 +1,113 @@ +// Copyright 2017 The Gogs 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 models + +import ( + "fmt" +) + +// IssueUser represents an issue-user relation. +type IssueUser struct { + ID int64 `xorm:"pk autoincr"` + UID int64 `xorm:"INDEX"` // User ID. + IssueID int64 + IsRead bool + IsAssigned bool + IsMentioned bool +} + +func newIssueUsers(e Engine, repo *Repository, issue *Issue) error { + assignees, err := repo.getAssignees(e) + if err != nil { + return fmt.Errorf("getAssignees: %v", err) + } + + // Poster can be anyone, append later if not one of assignees. + isPosterAssignee := false + + // Leave a seat for poster itself to append later, but if poster is one of assignee + // and just waste 1 unit is cheaper than re-allocate memory once. + issueUsers := make([]*IssueUser, 0, len(assignees)+1) + for _, assignee := range assignees { + issueUsers = append(issueUsers, &IssueUser{ + IssueID: issue.ID, + UID: assignee.ID, + IsAssigned: assignee.ID == issue.AssigneeID, + }) + isPosterAssignee = isPosterAssignee || assignee.ID == issue.PosterID + } + if !isPosterAssignee { + issueUsers = append(issueUsers, &IssueUser{ + IssueID: issue.ID, + UID: issue.PosterID, + }) + } + + if _, err = e.Insert(issueUsers); err != nil { + return err + } + return nil +} + +func updateIssueUserByAssignee(e Engine, issue *Issue) (err error) { + if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?", false, issue.ID); err != nil { + return err + } + + // Assignee ID equals to 0 means clear assignee. + if issue.AssigneeID > 0 { + if _, err = e.Exec("UPDATE `issue_user` SET is_assigned = ? WHERE uid = ? AND issue_id = ?", true, issue.AssigneeID, issue.ID); err != nil { + return err + } + } + + return updateIssue(e, issue) +} + +// UpdateIssueUserByAssignee updates issue-user relation for assignee. +func UpdateIssueUserByAssignee(issue *Issue) (err error) { + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if err = updateIssueUserByAssignee(sess, issue); err != nil { + return err + } + + return sess.Commit() +} + +// UpdateIssueUserByRead updates issue-user relation for reading. +func UpdateIssueUserByRead(uid, issueID int64) error { + _, err := x.Exec("UPDATE `issue_user` SET is_read=? WHERE uid=? AND issue_id=?", true, uid, issueID) + return err +} + +// UpdateIssueUsersByMentions updates issue-user pairs by mentioning. +func UpdateIssueUsersByMentions(e Engine, issueID int64, uids []int64) error { + for _, uid := range uids { + iu := &IssueUser{ + UID: uid, + IssueID: issueID, + } + has, err := e.Get(iu) + if err != nil { + return err + } + + iu.IsMentioned = true + if has { + _, err = e.Id(iu.ID).AllCols().Update(iu) + } else { + _, err = e.Insert(iu) + } + if err != nil { + return err + } + } + return nil +} diff --git a/models/issue_user_test.go b/models/issue_user_test.go new file mode 100644 index 000000000..e3ebfa8fa --- /dev/null +++ b/models/issue_user_test.go @@ -0,0 +1,74 @@ +// Copyright 2017 The Gogs 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 models + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_newIssueUsers(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + newIssue := &Issue{ + RepoID: repo.ID, + PosterID: 4, + Index: 5, + Title: "newTestIssueTitle", + Content: "newTestIssueContent", + } + + // artificially insert new issue + AssertSuccessfulInsert(t, newIssue) + + assert.NoError(t, newIssueUsers(x, repo, newIssue)) + + // issue_user table should now have entries for new issue + AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) + AssertExistsAndLoadBean(t, &IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) +} + +func TestUpdateIssueUserByAssignee(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + + // artificially change assignee in issue_user table + AssertSuccessfulInsert(t, &IssueUser{IssueID: issue.ID, UID: 5, IsAssigned: true}) + _, err := x.Cols("is_assigned"). + Update(&IssueUser{IsAssigned: false}, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}) + assert.NoError(t, err) + + assert.NoError(t, UpdateIssueUserByAssignee(issue)) + + // issue_user table should now be correct again + AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: issue.AssigneeID}, "is_assigned=1") + AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 5}, "is_assigned=0") +} + +func TestUpdateIssueUserByRead(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + + assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) + AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + + assert.NoError(t, UpdateIssueUserByRead(4, issue.ID)) + AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + + assert.NoError(t, UpdateIssueUserByRead(NonexistentID, NonexistentID)) +} + +func TestUpdateIssueUsersByMentions(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + issue := AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue) + + uids := []int64{2, 5} + assert.NoError(t, UpdateIssueUsersByMentions(x, issue.ID, uids)) + for _, uid := range uids { + AssertExistsAndLoadBean(t, &IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") + } +} diff --git a/models/setup_for_test.go b/models/setup_for_test.go index fd5a180ae..651e36e82 100644 --- a/models/setup_for_test.go +++ b/models/setup_for_test.go @@ -51,6 +51,7 @@ func CreateTestEngine() error { if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { return err } + fixtures, err = testfixtures.NewFolder(x.DB().DB, &testfixtures.SQLite{}, "fixtures/") return err } @@ -82,7 +83,8 @@ func BeanExists(t *testing.T, bean interface{}, conditions ...interface{}) bool func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...interface{}) interface{} { exists, err := loadBeanIfExists(bean, conditions...) assert.NoError(t, err) - assert.True(t, exists) + assert.True(t, exists, + "Expected to find %+v (with conditions %+v), but did not", bean, conditions) return bean } @@ -92,3 +94,15 @@ func AssertNotExistsBean(t *testing.T, bean interface{}, conditions ...interface assert.NoError(t, err) assert.False(t, exists) } + +// AssertSuccessfulInsert assert that beans is successfully inserted +func AssertSuccessfulInsert(t *testing.T, beans ...interface{}) { + _, err := x.Insert(beans...) + assert.NoError(t, err) +} + +// AssertSuccessfulUpdate assert that bean is successfully updated +func AssertSuccessfulUpdate(t *testing.T, bean interface{}, conditions ...interface{}) { + _, err := x.Update(bean, conditions...) + assert.NoError(t, err) +}