]> source.dussan.org Git - gitea.git/commitdiff
Unit tests for issue_milestone (#878)
authorEthan Koenig <etk39@cornell.edu>
Thu, 9 Feb 2017 06:39:26 +0000 (01:39 -0500)
committerLunny Xiao <xiaolunwen@gmail.com>
Thu, 9 Feb 2017 06:39:26 +0000 (14:39 +0800)
models/fixtures/issue.yml
models/fixtures/milestone.yml [new file with mode: 0644]
models/fixtures/repository.yml
models/issue.go
models/issue_milestone.go [new file with mode: 0644]
models/issue_milestone_test.go [new file with mode: 0644]

index 85bdac397f45ccf5f985e233a83cae8f2b9d5a7f..c2d21089ce25c085da47cc26ab57924820524efb 100644 (file)
@@ -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 (file)
index 0000000..8192c4f
--- /dev/null
@@ -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
index 5e45d58b281bcb872b22b9926c31e9324dd4482e..93463c08554f133399dc7933bc75a9eba873e2e0 100644 (file)
@@ -8,6 +8,7 @@
   num_closed_issues: 1
   num_pulls: 2
   num_closed_pulls: 0
+  num_milestones: 2
   num_watches: 2
 
 -
index 97c633c234313310acbca0ab7d1517d0c8edb1b2..2d57c482698a8b5752b99ebb884e087c85c326c1 100644 (file)
@@ -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 (file)
index 0000000..cfd2ce1
--- /dev/null
@@ -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 (file)
index 0000000..aa4038d
--- /dev/null
@@ -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))
+}