From a6751cec049f4a814b590beab641e94ab1bd30c8 Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Thu, 9 Feb 2017 01:39:26 -0500 Subject: [PATCH] Unit tests for issue_milestone (#878) --- models/fixtures/issue.yml | 1 + models/fixtures/milestone.yml | 15 ++ models/fixtures/repository.yml | 1 + models/issue.go | 366 --------------------------------- models/issue_milestone.go | 352 +++++++++++++++++++++++++++++++ models/issue_milestone_test.go | 240 +++++++++++++++++++++ 6 files changed, 609 insertions(+), 366 deletions(-) create mode 100644 models/fixtures/milestone.yml create mode 100644 models/issue_milestone.go create mode 100644 models/issue_milestone_test.go diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index 85bdac397f..c2d21089ce 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -19,6 +19,7 @@ poster_id: 1 name: issue2 content: content2 + milestone_id: 1 is_closed: false is_pull: true created_unix: 946684810 diff --git a/models/fixtures/milestone.yml b/models/fixtures/milestone.yml new file mode 100644 index 0000000000..8192c4fbe1 --- /dev/null +++ b/models/fixtures/milestone.yml @@ -0,0 +1,15 @@ +- + id: 1 + repo_id: 1 + name: milestone1 + content: content1 + is_closed: false + num_issues: 1 + +- + id: 2 + repo_id: 1 + name: milestone2 + content: content2 + is_closed: false + num_issues: 0 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 5e45d58b28..93463c0855 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -8,6 +8,7 @@ num_closed_issues: 1 num_pulls: 2 num_closed_pulls: 0 + num_milestones: 2 num_watches: 2 - diff --git a/models/issue.go b/models/issue.go index 97c633c234..2d57c48269 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1350,369 +1350,3 @@ func updateIssue(e Engine, issue *Issue) error { func UpdateIssue(issue *Issue) error { return updateIssue(x, issue) } - -// _____ .__.__ __ -// / \ |__| | ____ _______/ |_ ____ ____ ____ -// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ -// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ -// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > -// \/ \/ \/ \/ \/ - -// Milestone represents a milestone of repository. -type Milestone struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - Name string - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-"` - IsClosed bool - NumIssues int - NumClosedIssues int - NumOpenIssues int `xorm:"-"` - Completeness int // Percentage(1-100). - IsOverDue bool `xorm:"-"` - - DeadlineString string `xorm:"-"` - Deadline time.Time `xorm:"-"` - DeadlineUnix int64 - ClosedDate time.Time `xorm:"-"` - ClosedDateUnix int64 -} - -// BeforeInsert is invoked from XORM before inserting an object of this type. -func (m *Milestone) BeforeInsert() { - m.DeadlineUnix = m.Deadline.Unix() -} - -// BeforeUpdate is invoked from XORM before updating this object. -func (m *Milestone) BeforeUpdate() { - if m.NumIssues > 0 { - m.Completeness = m.NumClosedIssues * 100 / m.NumIssues - } else { - m.Completeness = 0 - } - - m.DeadlineUnix = m.Deadline.Unix() - m.ClosedDateUnix = m.ClosedDate.Unix() -} - -// AfterSet is invoked from XORM after setting the value of a field of -// this object. -func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "num_closed_issues": - m.NumOpenIssues = m.NumIssues - m.NumClosedIssues - - case "deadline_unix": - m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() - if m.Deadline.Year() == 9999 { - return - } - - m.DeadlineString = m.Deadline.Format("2006-01-02") - if time.Now().Local().After(m.Deadline) { - m.IsOverDue = true - } - - case "closed_date_unix": - m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() - } -} - -// State returns string representation of milestone status. -func (m *Milestone) State() api.StateType { - if m.IsClosed { - return api.StateClosed - } - return api.StateOpen -} - -// APIFormat returns this Milestone in API format. -func (m *Milestone) APIFormat() *api.Milestone { - apiMilestone := &api.Milestone{ - ID: m.ID, - State: m.State(), - Title: m.Name, - Description: m.Content, - OpenIssues: m.NumOpenIssues, - ClosedIssues: m.NumClosedIssues, - } - if m.IsClosed { - apiMilestone.Closed = &m.ClosedDate - } - if m.Deadline.Year() < 9999 { - apiMilestone.Deadline = &m.Deadline - } - return apiMilestone -} - -// NewMilestone creates new milestone of repository. -func NewMilestone(m *Milestone) (err error) { - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Insert(m); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil { - return err - } - return sess.Commit() -} - -func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { - m := &Milestone{ - ID: id, - RepoID: repoID, - } - has, err := e.Get(m) - if err != nil { - return nil, err - } else if !has { - return nil, ErrMilestoneNotExist{id, repoID} - } - return m, nil -} - -// GetMilestoneByRepoID returns the milestone in a repository. -func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { - return getMilestoneByRepoID(x, repoID, id) -} - -// GetMilestonesByRepoID returns all milestones of a repository. -func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { - miles := make([]*Milestone, 0, 10) - return miles, x.Where("repo_id = ?", repoID).Find(&miles) -} - -// GetMilestones returns a list of milestones of given repository and status. -func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*Milestone, error) { - miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) - sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) - if page > 0 { - sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) - } - - switch sortType { - case "furthestduedate": - sess.Desc("deadline_unix") - case "leastcomplete": - sess.Asc("completeness") - case "mostcomplete": - sess.Desc("completeness") - case "leastissues": - sess.Asc("num_issues") - case "mostissues": - sess.Desc("num_issues") - default: - sess.Asc("deadline_unix") - } - - return miles, sess.Find(&miles) -} - -func updateMilestone(e Engine, m *Milestone) error { - _, err := e.Id(m.ID).AllCols().Update(m) - return err -} - -// UpdateMilestone updates information of given milestone. -func UpdateMilestone(m *Milestone) error { - return updateMilestone(x, m) -} - -func countRepoMilestones(e Engine, repoID int64) int64 { - count, _ := e. - Where("repo_id=?", repoID). - Count(new(Milestone)) - return count -} - -// CountRepoMilestones returns number of milestones in given repository. -func CountRepoMilestones(repoID int64) int64 { - return countRepoMilestones(x, repoID) -} - -func countRepoClosedMilestones(e Engine, repoID int64) int64 { - closed, _ := e. - Where("repo_id=? AND is_closed=?", repoID, true). - Count(new(Milestone)) - return closed -} - -// CountRepoClosedMilestones returns number of closed milestones in given repository. -func CountRepoClosedMilestones(repoID int64) int64 { - return countRepoClosedMilestones(x, repoID) -} - -// MilestoneStats returns number of open and closed milestones of given repository. -func MilestoneStats(repoID int64) (open int64, closed int64) { - open, _ = x. - Where("repo_id=? AND is_closed=?", repoID, false). - Count(new(Milestone)) - return open, CountRepoClosedMilestones(repoID) -} - -// ChangeMilestoneStatus changes the milestone open/closed status. -func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { - repo, err := GetRepositoryByID(m.RepoID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - m.IsClosed = isClosed - if err = updateMilestone(sess, m); err != nil { - return err - } - - repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) - repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) - if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { - return err - } - return sess.Commit() -} - -func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error { - if issue.MilestoneID == 0 { - return nil - } - - m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil { - return err - } - - if issue.IsClosed { - m.NumOpenIssues-- - m.NumClosedIssues++ - } else { - m.NumOpenIssues++ - m.NumClosedIssues-- - } - - return updateMilestone(e, m) -} - -// ChangeMilestoneIssueStats updates the open/closed issues counter and progress -// for the milestone associated with the given issue. -func ChangeMilestoneIssueStats(issue *Issue) (err error) { - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - if err = changeMilestoneIssueStats(sess, issue); err != nil { - return err - } - - return sess.Commit() -} - -func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error { - if oldMilestoneID > 0 { - m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) - if err != nil { - return err - } - - m.NumIssues-- - if issue.IsClosed { - m.NumClosedIssues-- - } - - if err = updateMilestone(e, m); err != nil { - return err - } - } - - if issue.MilestoneID > 0 { - m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) - if err != nil { - return err - } - - m.NumIssues++ - if issue.IsClosed { - m.NumClosedIssues++ - } - - if err = updateMilestone(e, m); err != nil { - return err - } - } - - if err := issue.loadRepo(e); err != nil { - return err - } - - if oldMilestoneID > 0 || issue.MilestoneID > 0 { - if _, err := createMilestoneComment(e, doer, issue.Repo, issue, oldMilestoneID, issue.MilestoneID); err != nil { - return err - } - } - - return updateIssue(e, issue) -} - -// ChangeMilestoneAssign changes assignment of milestone for issue. -func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) { - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil { - return err - } - return sess.Commit() -} - -// DeleteMilestoneByRepoID deletes a milestone from a repository. -func DeleteMilestoneByRepoID(repoID, id int64) error { - m, err := GetMilestoneByRepoID(repoID, id) - if err != nil { - if IsErrMilestoneNotExist(err) { - return nil - } - return err - } - - repo, err := GetRepositoryByID(m.RepoID) - if err != nil { - return err - } - - sess := x.NewSession() - defer sessionRelease(sess) - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil { - return err - } - - repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) - repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) - if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { - return err - } - - if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { - return err - } - return sess.Commit() -} diff --git a/models/issue_milestone.go b/models/issue_milestone.go new file mode 100644 index 0000000000..cfd2ce1707 --- /dev/null +++ b/models/issue_milestone.go @@ -0,0 +1,352 @@ +// 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 models + +import ( + "time" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/sdk/gitea" + + "github.com/go-xorm/xorm" +) + +// Milestone represents a milestone of repository. +type Milestone struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + Name string + Content string `xorm:"TEXT"` + RenderedContent string `xorm:"-"` + IsClosed bool + NumIssues int + NumClosedIssues int + NumOpenIssues int `xorm:"-"` + Completeness int // Percentage(1-100). + IsOverDue bool `xorm:"-"` + + DeadlineString string `xorm:"-"` + Deadline time.Time `xorm:"-"` + DeadlineUnix int64 + ClosedDate time.Time `xorm:"-"` + ClosedDateUnix int64 +} + +// BeforeInsert is invoked from XORM before inserting an object of this type. +func (m *Milestone) BeforeInsert() { + m.DeadlineUnix = m.Deadline.Unix() +} + +// BeforeUpdate is invoked from XORM before updating this object. +func (m *Milestone) BeforeUpdate() { + if m.NumIssues > 0 { + m.Completeness = m.NumClosedIssues * 100 / m.NumIssues + } else { + m.Completeness = 0 + } + + m.DeadlineUnix = m.Deadline.Unix() + m.ClosedDateUnix = m.ClosedDate.Unix() +} + +// AfterSet is invoked from XORM after setting the value of a field of +// this object. +func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { + switch colName { + case "num_closed_issues": + m.NumOpenIssues = m.NumIssues - m.NumClosedIssues + + case "deadline_unix": + m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() + if m.Deadline.Year() == 9999 { + return + } + + m.DeadlineString = m.Deadline.Format("2006-01-02") + if time.Now().Local().After(m.Deadline) { + m.IsOverDue = true + } + + case "closed_date_unix": + m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() + } +} + +// State returns string representation of milestone status. +func (m *Milestone) State() api.StateType { + if m.IsClosed { + return api.StateClosed + } + return api.StateOpen +} + +// APIFormat returns this Milestone in API format. +func (m *Milestone) APIFormat() *api.Milestone { + apiMilestone := &api.Milestone{ + ID: m.ID, + State: m.State(), + Title: m.Name, + Description: m.Content, + OpenIssues: m.NumOpenIssues, + ClosedIssues: m.NumClosedIssues, + } + if m.IsClosed { + apiMilestone.Closed = &m.ClosedDate + } + if m.Deadline.Year() < 9999 { + apiMilestone.Deadline = &m.Deadline + } + return apiMilestone +} + +// NewMilestone creates new milestone of repository. +func NewMilestone(m *Milestone) (err error) { + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Insert(m); err != nil { + return err + } + + if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil { + return err + } + return sess.Commit() +} + +func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { + m := &Milestone{ + ID: id, + RepoID: repoID, + } + has, err := e.Get(m) + if err != nil { + return nil, err + } else if !has { + return nil, ErrMilestoneNotExist{id, repoID} + } + return m, nil +} + +// GetMilestoneByRepoID returns the milestone in a repository. +func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { + return getMilestoneByRepoID(x, repoID, id) +} + +// GetMilestonesByRepoID returns all milestones of a repository. +func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { + miles := make([]*Milestone, 0, 10) + return miles, x.Where("repo_id = ?", repoID).Find(&miles) +} + +// GetMilestones returns a list of milestones of given repository and status. +func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*Milestone, error) { + miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) + sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) + if page > 0 { + sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) + } + + switch sortType { + case "furthestduedate": + sess.Desc("deadline_unix") + case "leastcomplete": + sess.Asc("completeness") + case "mostcomplete": + sess.Desc("completeness") + case "leastissues": + sess.Asc("num_issues") + case "mostissues": + sess.Desc("num_issues") + default: + sess.Asc("deadline_unix") + } + + return miles, sess.Find(&miles) +} + +func updateMilestone(e Engine, m *Milestone) error { + _, err := e.Id(m.ID).AllCols().Update(m) + return err +} + +// UpdateMilestone updates information of given milestone. +func UpdateMilestone(m *Milestone) error { + return updateMilestone(x, m) +} + +func countRepoMilestones(e Engine, repoID int64) int64 { + count, _ := e. + Where("repo_id=?", repoID). + Count(new(Milestone)) + return count +} + +func countRepoClosedMilestones(e Engine, repoID int64) int64 { + closed, _ := e. + Where("repo_id=? AND is_closed=?", repoID, true). + Count(new(Milestone)) + return closed +} + +// CountRepoClosedMilestones returns number of closed milestones in given repository. +func CountRepoClosedMilestones(repoID int64) int64 { + return countRepoClosedMilestones(x, repoID) +} + +// MilestoneStats returns number of open and closed milestones of given repository. +func MilestoneStats(repoID int64) (open int64, closed int64) { + open, _ = x. + Where("repo_id=? AND is_closed=?", repoID, false). + Count(new(Milestone)) + return open, CountRepoClosedMilestones(repoID) +} + +// ChangeMilestoneStatus changes the milestone open/closed status. +func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { + repo, err := GetRepositoryByID(m.RepoID) + if err != nil { + return err + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + m.IsClosed = isClosed + if err = updateMilestone(sess, m); err != nil { + return err + } + + repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) + repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) + if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { + return err + } + return sess.Commit() +} + +func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error { + if issue.MilestoneID == 0 { + return nil + } + + m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) + if err != nil { + return err + } + + if issue.IsClosed { + m.NumOpenIssues-- + m.NumClosedIssues++ + } else { + m.NumOpenIssues++ + m.NumClosedIssues-- + } + + return updateMilestone(e, m) +} + +func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error { + if oldMilestoneID > 0 { + m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) + if err != nil { + return err + } + + m.NumIssues-- + if issue.IsClosed { + m.NumClosedIssues-- + } + + if err = updateMilestone(e, m); err != nil { + return err + } + } + + if issue.MilestoneID > 0 { + m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) + if err != nil { + return err + } + + m.NumIssues++ + if issue.IsClosed { + m.NumClosedIssues++ + } + + if err = updateMilestone(e, m); err != nil { + return err + } + } + + if err := issue.loadRepo(e); err != nil { + return err + } + + if oldMilestoneID > 0 || issue.MilestoneID > 0 { + if _, err := createMilestoneComment(e, doer, issue.Repo, issue, oldMilestoneID, issue.MilestoneID); err != nil { + return err + } + } + + return updateIssue(e, issue) +} + +// ChangeMilestoneAssign changes assignment of milestone for issue. +func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil { + return err + } + return sess.Commit() +} + +// DeleteMilestoneByRepoID deletes a milestone from a repository. +func DeleteMilestoneByRepoID(repoID, id int64) error { + m, err := GetMilestoneByRepoID(repoID, id) + if err != nil { + if IsErrMilestoneNotExist(err) { + return nil + } + return err + } + + repo, err := GetRepositoryByID(m.RepoID) + if err != nil { + return err + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil { + return err + } + + repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) + repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) + if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { + return err + } + + if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { + return err + } + return sess.Commit() +} diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go new file mode 100644 index 0000000000..aa4038dc6e --- /dev/null +++ b/models/issue_milestone_test.go @@ -0,0 +1,240 @@ +// 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 models + +import ( + "testing" + + api "code.gitea.io/sdk/gitea" + + "github.com/stretchr/testify/assert" + "sort" + "time" +) + +func TestMilestone_State(t *testing.T) { + assert.Equal(t, api.StateOpen, (&Milestone{IsClosed: false}).State()) + assert.Equal(t, api.StateClosed, (&Milestone{IsClosed: true}).State()) +} + +func TestMilestone_APIFormat(t *testing.T) { + milestone := &Milestone{ + ID: 3, + RepoID: 4, + Name: "milestoneName", + Content: "milestoneContent", + IsClosed: false, + NumOpenIssues: 5, + NumClosedIssues: 6, + Deadline: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), + } + assert.Equal(t, api.Milestone{ + ID: milestone.ID, + State: api.StateOpen, + Title: milestone.Name, + Description: milestone.Content, + OpenIssues: milestone.NumOpenIssues, + ClosedIssues: milestone.NumClosedIssues, + Deadline: &milestone.Deadline, + }, *milestone.APIFormat()) +} + +func TestNewMilestone(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + milestone := &Milestone{ + RepoID: 1, + Name: "milestoneName", + Content: "milestoneContent", + } + + assert.NoError(t, NewMilestone(milestone)) + AssertExistsAndLoadBean(t, milestone) + CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) +} + +func TestGetMilestoneByRepoID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + milestone, err := GetMilestoneByRepoID(1, 1) + assert.NoError(t, err) + assert.EqualValues(t, 1, milestone.ID) + assert.EqualValues(t, 1, milestone.RepoID) + + _, err = GetMilestoneByRepoID(NonexistentID, NonexistentID) + assert.True(t, IsErrMilestoneNotExist(err)) +} + +func TestGetMilestonesByRepoID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + test := func(repoID int64) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + milestones, err := GetMilestonesByRepoID(repo.ID) + assert.NoError(t, err) + assert.Len(t, milestones, repo.NumMilestones) + for _, milestone := range milestones { + assert.EqualValues(t, repoID, milestone.RepoID) + } + } + test(1) + test(2) + test(3) + + milestones, err := GetMilestonesByRepoID(NonexistentID) + assert.NoError(t, err) + assert.Len(t, milestones, 0) +} + +func TestGetMilestones(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) + test := func(sortType string, sortCond func(*Milestone) int) { + for _, page := range []int{0, 1} { + milestones, err := GetMilestones(repo.ID, page, false, sortType) + assert.NoError(t, err) + assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones) + values := make([]int, len(milestones)) + for i, milestone := range milestones { + values[i] = sortCond(milestone) + } + assert.True(t, sort.IntsAreSorted(values)) + + milestones, err = GetMilestones(repo.ID, page, true, sortType) + assert.NoError(t, err) + assert.Len(t, milestones, repo.NumClosedMilestones) + values = make([]int, len(milestones)) + for i, milestone := range milestones { + values[i] = sortCond(milestone) + } + assert.True(t, sort.IntsAreSorted(values)) + } + } + test("furthestduedate", func(milestone *Milestone) int { + return -int(milestone.DeadlineUnix) + }) + test("leastcomplete", func(milestone *Milestone) int { + return milestone.Completeness + }) + test("mostcomplete", func(milestone *Milestone) int { + return -milestone.Completeness + }) + test("leastissues", func(milestone *Milestone) int { + return milestone.NumIssues + }) + test("mostissues", func(milestone *Milestone) int { + return -milestone.NumIssues + }) + test("soonestduedate", func(milestone *Milestone) int { + return int(milestone.DeadlineUnix) + }) +} + +func TestUpdateMilestone(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + milestone.Name = "newMilestoneName" + milestone.Content = "newMilestoneContent" + assert.NoError(t, UpdateMilestone(milestone)) + AssertExistsAndLoadBean(t, milestone) + CheckConsistencyFor(t, &Milestone{}) +} + +func TestCountRepoMilestones(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + test := func(repoID int64) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + assert.EqualValues(t, repo.NumMilestones, countRepoMilestones(x, repoID)) + } + test(1) + test(2) + test(3) + assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID)) +} + +func TestCountRepoClosedMilestones(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + test := func(repoID int64) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + assert.EqualValues(t, repo.NumClosedMilestones, CountRepoClosedMilestones(repoID)) + } + test(1) + test(2) + test(3) + assert.EqualValues(t, 0, countRepoMilestones(x, NonexistentID)) +} + +func TestMilestoneStats(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + test := func(repoID int64) { + repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository) + open, closed := MilestoneStats(repoID) + assert.EqualValues(t, repo.NumMilestones-repo.NumClosedMilestones, open) + assert.EqualValues(t, repo.NumClosedMilestones, closed) + } + test(1) + test(2) + test(3) + + open, closed := MilestoneStats(NonexistentID) + assert.EqualValues(t, 0, open) + assert.EqualValues(t, 0, closed) +} + +func TestChangeMilestoneStatus(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone) + + assert.NoError(t, ChangeMilestoneStatus(milestone, true)) + AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=1") + CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) + + assert.NoError(t, ChangeMilestoneStatus(milestone, false)) + AssertExistsAndLoadBean(t, &Milestone{ID: 1}, "is_closed=0") + CheckConsistencyFor(t, &Repository{ID: milestone.RepoID}, &Milestone{}) +} + +func TestChangeMilestoneIssueStats(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + issue := AssertExistsAndLoadBean(t, &Issue{MilestoneID: 1}, + "is_closed=0").(*Issue) + + issue.IsClosed = true + _, err := x.Cols("is_closed").Update(issue) + assert.NoError(t, err) + assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue)) + CheckConsistencyFor(t, &Milestone{}) + + issue.IsClosed = false + _, err = x.Cols("is_closed").Update(issue) + assert.NoError(t, err) + assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue)) + CheckConsistencyFor(t, &Milestone{}) +} + +func TestChangeMilestoneAssign(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + issue := AssertExistsAndLoadBean(t, &Issue{RepoID: 1}).(*Issue) + doer := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) + + oldMilestoneID := issue.MilestoneID + issue.MilestoneID = 2 + assert.NoError(t, ChangeMilestoneAssign(issue, doer, oldMilestoneID)) + AssertExistsAndLoadBean(t, &Comment{ + IssueID: issue.ID, + Type: CommentTypeMilestone, + MilestoneID: issue.MilestoneID, + OldMilestoneID: oldMilestoneID, + }) + CheckConsistencyFor(t, &Milestone{}, &Issue{}) +} + +func TestDeleteMilestoneByRepoID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, DeleteMilestoneByRepoID(1, 1)) + AssertNotExistsBean(t, &Milestone{ID: 1}) + CheckConsistencyFor(t, &Repository{ID: 1}) + + assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID)) +}