diff options
author | ᴜɴᴋɴᴡᴏɴ <u@gogs.io> | 2020-04-17 10:14:18 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-17 10:14:18 +0800 |
commit | fa497b16332c24bc4d9e788c64bda94e3c1499a7 (patch) | |
tree | 1dcf1b13f702a07dc30da791bf0e76e1ffba4027 /internal/db | |
parent | e131a4564680a3257a2d4795af7669e4260b33b9 (diff) |
db: add tests for repos (#6112)
* Add Repos.create method
* Fix repo name error handling
* Fix all compile errors
* Update github.com/go-macaron/captcha to fix http issue
* Add repos tests
Diffstat (limited to 'internal/db')
-rw-r--r-- | internal/db/error.go | 40 | ||||
-rw-r--r-- | internal/db/errors/user.go | 11 | ||||
-rw-r--r-- | internal/db/org_team.go | 2 | ||||
-rw-r--r-- | internal/db/repo.go | 85 | ||||
-rw-r--r-- | internal/db/repos.go | 100 | ||||
-rw-r--r-- | internal/db/repos_test.go | 102 | ||||
-rw-r--r-- | internal/db/user.go | 39 |
7 files changed, 267 insertions, 112 deletions
diff --git a/internal/db/error.go b/internal/db/error.go index 46e7dde5..ba07ca87 100644 --- a/internal/db/error.go +++ b/internal/db/error.go @@ -8,32 +8,6 @@ import ( "fmt" ) -type ErrNameReserved struct { - Name string -} - -func IsErrNameReserved(err error) bool { - _, ok := err.(ErrNameReserved) - return ok -} - -func (err ErrNameReserved) Error() string { - return fmt.Sprintf("name is reserved [name: %s]", err.Name) -} - -type ErrNamePatternNotAllowed struct { - Pattern string -} - -func IsErrNamePatternNotAllowed(err error) bool { - _, ok := err.(ErrNamePatternNotAllowed) - return ok -} - -func (err ErrNamePatternNotAllowed) Error() string { - return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern) -} - // ____ ___ // | | \______ ___________ // | | / ___// __ \_ __ \ @@ -245,20 +219,6 @@ func (err ErrLastOrgOwner) Error() string { // |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____| // \/ \/|__| \/ \/ -type ErrRepoAlreadyExist struct { - Uname string - Name string -} - -func IsErrRepoAlreadyExist(err error) bool { - _, ok := err.(ErrRepoAlreadyExist) - return ok -} - -func (err ErrRepoAlreadyExist) Error() string { - return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name) -} - type ErrInvalidCloneAddr struct { IsURLError bool IsInvalidPath bool diff --git a/internal/db/errors/user.go b/internal/db/errors/user.go index 54a6ad50..09572af9 100644 --- a/internal/db/errors/user.go +++ b/internal/db/errors/user.go @@ -8,17 +8,6 @@ import ( "fmt" ) -type EmptyName struct{} - -func IsEmptyName(err error) bool { - _, ok := err.(EmptyName) - return ok -} - -func (err EmptyName) Error() string { - return "empty name" -} - type UserNotKeyOwner struct { KeyID int64 } diff --git a/internal/db/org_team.go b/internal/db/org_team.go index 6e141d25..9daca9e7 100644 --- a/internal/db/org_team.go +++ b/internal/db/org_team.go @@ -217,7 +217,7 @@ var reservedTeamNames = []string{"new"} // IsUsableTeamName return an error if given name is a reserved name or pattern. func IsUsableTeamName(name string) error { - return isUsableName(reservedTeamNames, nil, name) + return isNameAllowed(reservedTeamNames, nil, name) } // NewTeam creates a record of new team. diff --git a/internal/db/repo.go b/internal/db/repo.go index 150942c1..66cecdd3 100644 --- a/internal/db/repo.go +++ b/internal/db/repo.go @@ -148,14 +148,14 @@ func NewRepoContext() { // Repository contains information of a repository. type Repository struct { ID int64 - OwnerID int64 `xorm:"UNIQUE(s)"` - Owner *User `xorm:"-" json:"-"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string `xorm:"VARCHAR(512)"` + OwnerID int64 `xorm:"UNIQUE(s)" gorm:"UNIQUE_INDEX:s"` + Owner *User `xorm:"-" gorm:"-" json:"-"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL" gorm:"UNIQUE_INDEX:s"` + Name string `xorm:"INDEX NOT NULL" gorm:"NOT NULL"` + Description string `xorm:"VARCHAR(512)" gorm:"TYPE:VARCHAR(512)"` Website string DefaultBranch string - Size int64 `xorm:"NOT NULL DEFAULT 0"` + Size int64 `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"` UseCustomAvatar bool // Counters @@ -164,44 +164,44 @@ type Repository struct { NumForks int NumIssues int NumClosedIssues int - NumOpenIssues int `xorm:"-" json:"-"` + NumOpenIssues int `xorm:"-" gorm:"-" json:"-"` NumPulls int NumClosedPulls int - NumOpenPulls int `xorm:"-" json:"-"` - NumMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumOpenMilestones int `xorm:"-" json:"-"` - NumTags int `xorm:"-" json:"-"` + NumOpenPulls int `xorm:"-" gorm:"-" json:"-"` + NumMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"` + NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"` + NumOpenMilestones int `xorm:"-" gorm:"-" json:"-"` + NumTags int `xorm:"-" gorm:"-" json:"-"` IsPrivate bool IsBare bool IsMirror bool - *Mirror `xorm:"-" json:"-"` + *Mirror `xorm:"-" gorm:"-" json:"-"` // Advanced settings - EnableWiki bool `xorm:"NOT NULL DEFAULT true"` + EnableWiki bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"` AllowPublicWiki bool EnableExternalWiki bool ExternalWikiURL string - EnableIssues bool `xorm:"NOT NULL DEFAULT true"` + EnableIssues bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"` AllowPublicIssues bool EnableExternalTracker bool ExternalTrackerURL string ExternalTrackerFormat string ExternalTrackerStyle string - ExternalMetas map[string]string `xorm:"-" json:"-"` - EnablePulls bool `xorm:"NOT NULL DEFAULT true"` - PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false"` - PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false"` + ExternalMetas map[string]string `xorm:"-" gorm:"-" json:"-"` + EnablePulls bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"` + PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"` + PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"` - IsFork bool `xorm:"NOT NULL DEFAULT false"` + IsFork bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"` ForkID int64 - BaseRepo *Repository `xorm:"-" json:"-"` + BaseRepo *Repository `xorm:"-" gorm:"-" json:"-"` - Created time.Time `xorm:"-" json:"-"` + Created time.Time `xorm:"-" gorm:"-" json:"-"` CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-"` + Updated time.Time `xorm:"-" gorm:"-" json:"-"` UpdatedUnix int64 } @@ -210,10 +210,6 @@ func (repo *Repository) BeforeInsert() { repo.UpdatedUnix = repo.CreatedUnix } -func (repo *Repository) BeforeUpdate() { - repo.UpdatedUnix = time.Now().Unix() -} - func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { switch colName { case "default_branch": @@ -1057,13 +1053,13 @@ var ( reservedRepoPatterns = []string{"*.git", "*.wiki"} ) -// IsUsableRepoName return an error if given name is a reserved name or pattern. -func IsUsableRepoName(name string) error { - return isUsableName(reservedRepoNames, reservedRepoPatterns, name) +// isRepoNameAllowed return an error if given name is a reserved name or pattern for repositories. +func isRepoNameAllowed(name string) error { + return isNameAllowed(reservedRepoNames, reservedRepoPatterns, name) } func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err error) { - if err = IsUsableRepoName(repo.Name); err != nil { + if err = isRepoNameAllowed(repo.Name); err != nil { return err } @@ -1071,7 +1067,7 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{owner.Name, repo.Name} + return ErrRepoAlreadyExist{args: errutil.Args{"ownerID": owner.ID, "name": repo.Name}} } if _, err = e.Insert(repo); err != nil { @@ -1266,7 +1262,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{newOwnerName, repo.Name} + return ErrRepoAlreadyExist{args: errutil.Args{"ownerName": newOwnerName, "name": repo.Name}} } sess := x.NewSession() @@ -1384,7 +1380,7 @@ func deleteRepoLocalCopy(repo *Repository) { func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { oldRepoName = strings.ToLower(oldRepoName) newRepoName = strings.ToLower(newRepoName) - if err = IsUsableRepoName(newRepoName); err != nil { + if err = isRepoNameAllowed(newRepoName); err != nil { return err } @@ -1392,7 +1388,7 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{u.Name, newRepoName} + return ErrRepoAlreadyExist{args: errutil.Args{"ownerID": u.ID, "name": newRepoName}} } repo, err := GetRepositoryByName(u.ID, oldRepoName) @@ -1647,25 +1643,6 @@ func GetRepositoryByRef(ref string) (*Repository, error) { return GetRepositoryByName(user.ID, repoName) } -var _ errutil.NotFound = (*ErrRepoNotExist)(nil) - -type ErrRepoNotExist struct { - args map[string]interface{} -} - -func IsErrRepoNotExist(err error) bool { - _, ok := err.(ErrRepoNotExist) - return ok -} - -func (err ErrRepoNotExist) Error() string { - return fmt.Sprintf("repository does not exist: %v", err.args) -} - -func (ErrRepoNotExist) NotFound() bool { - return true -} - // GetRepositoryByName returns the repository by given name under user if exists. // Deprecated: Use Repos.GetByName instead. func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { diff --git a/internal/db/repos.go b/internal/db/repos.go index cac50b76..f97cf28d 100644 --- a/internal/db/repos.go +++ b/internal/db/repos.go @@ -5,9 +5,13 @@ package db import ( + "fmt" "strings" + "time" "github.com/jinzhu/gorm" + + "gogs.io/gogs/internal/errutil" ) // ReposStore is the persistent interface for repositories. @@ -21,10 +25,106 @@ type ReposStore interface { var Repos ReposStore +// NOTE: This is a GORM create hook. +func (r *Repository) BeforeCreate() { + r.CreatedUnix = gorm.NowFunc().Unix() +} + +// NOTE: This is a GORM update hook. +func (r *Repository) BeforeUpdate() { + r.UpdatedUnix = gorm.NowFunc().Unix() +} + +// NOTE: This is a GORM query hook. +func (r *Repository) AfterFind() { + r.Created = time.Unix(r.CreatedUnix, 0).Local() + r.Updated = time.Unix(r.UpdatedUnix, 0).Local() +} + +var _ ReposStore = (*repos)(nil) + type repos struct { *gorm.DB } +type ErrRepoAlreadyExist struct { + args errutil.Args +} + +func IsErrRepoAlreadyExist(err error) bool { + _, ok := err.(ErrRepoAlreadyExist) + return ok +} + +func (err ErrRepoAlreadyExist) Error() string { + return fmt.Sprintf("repository already exists: %v", err.args) +} + +type createRepoOpts struct { + Name string + Description string + DefaultBranch string + Private bool + Mirror bool + EnableWiki bool + EnableIssues bool + EnablePulls bool + Fork bool + ForkID int64 +} + +// create creates a new repository record in the database. Fields of "repo" will be updated +// in place upon insertion. It returns ErrNameNotAllowed when the repository name is not allowed, +// or returns ErrRepoAlreadyExist when a repository with same name already exists for the owner. +func (db *repos) create(ownerID int64, opts createRepoOpts) (*Repository, error) { + err := isRepoNameAllowed(opts.Name) + if err != nil { + return nil, err + } + + _, err = db.GetByName(ownerID, opts.Name) + if err == nil { + return nil, ErrRepoAlreadyExist{args: errutil.Args{"ownerID": ownerID, "name": opts.Name}} + } else if !IsErrRepoNotExist(err) { + return nil, err + } + + repo := &Repository{ + OwnerID: ownerID, + LowerName: strings.ToLower(opts.Name), + Name: opts.Name, + Description: opts.Description, + DefaultBranch: opts.DefaultBranch, + IsPrivate: opts.Private, + IsMirror: opts.Mirror, + EnableWiki: opts.EnableWiki, + EnableIssues: opts.EnableIssues, + EnablePulls: opts.EnablePulls, + IsFork: opts.Fork, + ForkID: opts.ForkID, + } + return repo, db.DB.Create(repo).Error +} + +var _ errutil.NotFound = (*ErrRepoNotExist)(nil) + +type ErrRepoNotExist struct { + args map[string]interface{} +} + +func IsErrRepoNotExist(err error) bool { + _, ok := err.(ErrRepoNotExist) + return ok +} + +func (err ErrRepoNotExist) Error() string { + return fmt.Sprintf("repository does not exist: %v", err.args) +} + +func (ErrRepoNotExist) NotFound() bool { + return true +} + func (db *repos) GetByName(ownerID int64, name string) (*Repository, error) { repo := new(Repository) err := db.Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)).First(repo).Error diff --git a/internal/db/repos_test.go b/internal/db/repos_test.go new file mode 100644 index 00000000..6a39c4ee --- /dev/null +++ b/internal/db/repos_test.go @@ -0,0 +1,102 @@ +// Copyright 2020 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 db + +import ( + "testing" + "time" + + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + + "gogs.io/gogs/internal/errutil" +) + +func Test_repos(t *testing.T) { + if testing.Short() { + t.Skip() + } + + t.Parallel() + + tables := []interface{}{new(Repository)} + db := &repos{ + DB: initTestDB(t, "repos", tables...), + } + + for _, tc := range []struct { + name string + test func(*testing.T, *repos) + }{ + {"create", test_repos_create}, + {"GetByName", test_repos_GetByName}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Cleanup(func() { + err := clearTables(db.DB, tables...) + if err != nil { + t.Fatal(err) + } + }) + tc.test(t, db) + }) + } +} + +func test_repos_create(t *testing.T, db *repos) { + t.Run("name not allowed", func(t *testing.T) { + _, err := db.create(1, createRepoOpts{ + Name: "my.git", + }) + expErr := ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "pattern": "*.git"}} + assert.Equal(t, expErr, err) + }) + + t.Run("already exists", func(t *testing.T) { + _, err := db.create(1, createRepoOpts{ + Name: "repo1", + }) + if err != nil { + t.Fatal(err) + } + + _, err = db.create(1, createRepoOpts{ + Name: "repo1", + }) + expErr := ErrRepoAlreadyExist{args: errutil.Args{"ownerID": int64(1), "name": "repo1"}} + assert.Equal(t, expErr, err) + }) + + repo, err := db.create(1, createRepoOpts{ + Name: "repo2", + }) + if err != nil { + t.Fatal(err) + } + + repo, err = db.GetByName(repo.OwnerID, repo.Name) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, gorm.NowFunc().Format(time.RFC3339), repo.Created.Format(time.RFC3339)) +} + +func test_repos_GetByName(t *testing.T, db *repos) { + repo, err := db.create(1, createRepoOpts{ + Name: "repo1", + }) + if err != nil { + t.Fatal(err) + } + + _, err = db.GetByName(repo.OwnerID, repo.Name) + if err != nil { + t.Fatal(err) + } + + _, err = db.GetByName(1, "bad_name") + expErr := ErrRepoNotExist{args: errutil.Args{"ownerID": int64(1), "name": "bad_name"}} + assert.Equal(t, expErr, err) +} diff --git a/internal/db/user.go b/internal/db/user.go index b650fa14..c225bd1a 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -503,25 +503,52 @@ var ( reservedUserPatterns = []string{"*.keys"} ) -// isUsableName checks if name is reserved or pattern of name is not allowed +type ErrNameNotAllowed struct { + args errutil.Args +} + +func IsErrNameNotAllowed(err error) bool { + _, ok := err.(ErrNameNotAllowed) + return ok +} + +func (err ErrNameNotAllowed) Value() string { + val, ok := err.args["name"].(string) + if ok { + return val + } + + val, ok = err.args["pattern"].(string) + if ok { + return val + } + + return "<value not found>" +} + +func (err ErrNameNotAllowed) Error() string { + return fmt.Sprintf("name is not allowed: %v", err.args) +} + +// isNameAllowed checks if name is reserved or pattern of name is not allowed // based on given reserved names and patterns. // Names are exact match, patterns can be prefix or suffix match with placeholder '*'. -func isUsableName(names, patterns []string, name string) error { +func isNameAllowed(names, patterns []string, name string) error { name = strings.TrimSpace(strings.ToLower(name)) if utf8.RuneCountInString(name) == 0 { - return errors.EmptyName{} + return ErrNameNotAllowed{args: errutil.Args{"reason": "empty name"}} } for i := range names { if name == names[i] { - return ErrNameReserved{name} + return ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "name": name}} } } for _, pat := range patterns { if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) || (pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) { - return ErrNamePatternNotAllowed{pat} + return ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "pattern": pat}} } } @@ -529,7 +556,7 @@ func isUsableName(names, patterns []string, name string) error { } func IsUsableUsername(name string) error { - return isUsableName(reservedUsernames, reservedUserPatterns, name) + return isNameAllowed(reservedUsernames, reservedUserPatterns, name) } // CreateUser creates record of a new user. |