Pārlūkot izejas kodu

db: use `context` and go-mockgen for `AccessTokensStore` (#7013)

Joe Chen 2 gadi atpakaļ
vecāks
revīzija
0a92ad27ef

+ 2 - 0
.deepsource.toml

@@ -1,5 +1,7 @@
 version = 1
 
+exclude_patterns = ["**/mocks.go"]
+
 [[analyzers]]
 name = "docker"
 enabled = true

+ 5 - 2
.github/workflows/go.yml

@@ -38,17 +38,20 @@ jobs:
           args: --timeout=30m
       - name: Install Task
         uses: arduino/setup-task@v1
+      - name: Install goimports and go-mockgen
+        run: |
+          go install github.com/derision-test/go-mockgen/cmd/go-mockgen@latest
+          go install golang.org/x/tools/cmd/goimports@latest
       - name: Check Go module tidiness and generated files
         shell: bash
         run: |
           go mod tidy
           task generate
-          task generate-schemadoc
           STATUS=$(git status --porcelain)
           if [ ! -z "$STATUS" ]; then
             echo "Unstaged files:"
             echo $STATUS
-            echo "Run 'go mod tidy', 'task generate' or 'task generate-schemadoc' and commit them"
+            echo "Run 'go mod tidy' or 'task generate' commit them"
             exit 1
           fi
 

+ 2 - 1
Taskfile.yml

@@ -44,7 +44,8 @@ tasks:
 
   generate:
     desc: Run all go:generate commands
-    deps: [generate-schemadoc]
+    cmds:
+      - go generate ./...
 
   test:
     desc: Run all tests.

+ 6 - 0
docs/dev/local_development.md

@@ -24,6 +24,8 @@ Gogs has the following dependencies:
 - [Go](https://golang.org/doc/install) (v1.16 or higher)
 - [Less.js](http://lesscss.org/usage/#command-line-usage-installing)
 - [Task](https://github.com/go-task/task) (v3)
+- [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports)
+- [go-mockgen](https://github.com/derision-test/go-mockgen)
 - Database upon your choice (pick one, we choose PostgreSQL in this document):
     - [PostgreSQL](https://wiki.postgresql.org/wiki/Detailed_installation_guides) (v9.6 or higher)
     - [MySQL](https://dev.mysql.com/downloads/mysql/) with `ENGINE=InnoDB` (v5.7 or higher)
@@ -39,6 +41,8 @@ Gogs has the following dependencies:
     brew install go postgresql git npm go-task/tap/go-task
     npm install -g less
     npm install -g less-plugin-clean-css
+    go install github.com/derision-test/go-mockgen/cmd/go-mockgen@latest
+    go install golang.org/x/tools/cmd/goimports@latest
     ```
 
 1. Configure PostgreSQL to start automatically:
@@ -75,6 +79,8 @@ Gogs has the following dependencies:
     sudo apt install -y make git-all postgresql postgresql-contrib golang-go nodejs
     npm install -g less
     go install github.com/go-task/task/v3/cmd/task@latest
+    go install github.com/derision-test/go-mockgen/cmd/go-mockgen@latest
+    go install golang.org/x/tools/cmd/goimports@latest
     ```
 
 1. Configure startup services:

+ 2 - 2
internal/context/auth.go

@@ -130,14 +130,14 @@ func authenticatedUserID(c *macaron.Context, sess session.Store) (_ int64, isTok
 
 		// Let's see if token is valid.
 		if len(tokenSHA) > 0 {
-			t, err := db.AccessTokens.GetBySHA1(tokenSHA)
+			t, err := db.AccessTokens.GetBySHA1(c.Req.Context(), tokenSHA)
 			if err != nil {
 				if !db.IsErrAccessTokenNotExist(err) {
 					log.Error("GetAccessTokenBySHA: %v", err)
 				}
 				return 0, false
 			}
-			if err = db.AccessTokens.Save(t); err != nil {
+			if err = db.AccessTokens.Save(c.Req.Context(), t); err != nil {
 				log.Error("UpdateAccessToken: %v", err)
 			}
 			return t.UserID, true

+ 33 - 31
internal/db/access_tokens.go

@@ -5,6 +5,7 @@
 package db
 
 import (
+	"context"
 	"fmt"
 	"time"
 
@@ -19,22 +20,23 @@ import (
 //
 // NOTE: All methods are sorted in alphabetical order.
 type AccessTokensStore interface {
-	// Create creates a new access token and persist to database.
-	// It returns ErrAccessTokenAlreadyExist when an access token
-	// with same name already exists for the user.
-	Create(userID int64, name string) (*AccessToken, error)
+	// Create creates a new access token and persist to database. It returns
+	// ErrAccessTokenAlreadyExist when an access token with same name already exists
+	// for the user.
+	Create(ctx context.Context, userID int64, name string) (*AccessToken, error)
 	// DeleteByID deletes the access token by given ID.
-	// 🚨 SECURITY: The "userID" is required to prevent attacker
-	// deletes arbitrary access token that belongs to another user.
-	DeleteByID(userID, id int64) error
-	// GetBySHA1 returns the access token with given SHA1.
-	// It returns ErrAccessTokenNotExist when not found.
-	GetBySHA1(sha1 string) (*AccessToken, error)
+	//
+	// 🚨 SECURITY: The "userID" is required to prevent attacker deletes arbitrary
+	// access token that belongs to another user.
+	DeleteByID(ctx context.Context, userID, id int64) error
+	// GetBySHA1 returns the access token with given SHA1. It returns
+	// ErrAccessTokenNotExist when not found.
+	GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error)
 	// List returns all access tokens belongs to given user.
-	List(userID int64) ([]*AccessToken, error)
-	// Save persists all values of given access token.
-	// The Updated field is set to current time automatically.
-	Save(t *AccessToken) error
+	List(ctx context.Context, userID int64) ([]*AccessToken, error)
+	// Save persists all values of given access token. The Updated field is set to
+	// current time automatically.
+	Save(ctx context.Context, t *AccessToken) error
 }
 
 var AccessTokens AccessTokensStore
@@ -42,17 +44,17 @@ var AccessTokens AccessTokensStore
 // AccessToken is a personal access token.
 type AccessToken struct {
 	ID     int64
-	UserID int64 `xorm:"uid INDEX" gorm:"COLUMN:uid;INDEX"`
+	UserID int64 `gorm:"column:uid;index"`
 	Name   string
-	Sha1   string `xorm:"UNIQUE VARCHAR(40)" gorm:"TYPE:VARCHAR(40);UNIQUE"`
+	Sha1   string `gorm:"type:VARCHAR(40);unique"`
 	SHA256 string `gorm:"type:VARCHAR(64);unique;not null"`
 
-	Created           time.Time `xorm:"-" gorm:"-" json:"-"`
+	Created           time.Time `gorm:"-" json:"-"`
 	CreatedUnix       int64
-	Updated           time.Time `xorm:"-" gorm:"-" json:"-"`
+	Updated           time.Time `gorm:"-" json:"-"`
 	UpdatedUnix       int64
-	HasRecentActivity bool `xorm:"-" gorm:"-" json:"-"`
-	HasUsed           bool `xorm:"-" gorm:"-" json:"-"`
+	HasRecentActivity bool `gorm:"-" json:"-"`
+	HasUsed           bool `gorm:"-" json:"-"`
 }
 
 // BeforeCreate implements the GORM create hook.
@@ -97,8 +99,8 @@ func (err ErrAccessTokenAlreadyExist) Error() string {
 	return fmt.Sprintf("access token already exists: %v", err.args)
 }
 
-func (db *accessTokens) Create(userID int64, name string) (*AccessToken, error) {
-	err := db.Where("uid = ? AND name = ?", userID, name).First(new(AccessToken)).Error
+func (db *accessTokens) Create(ctx context.Context, userID int64, name string) (*AccessToken, error) {
+	err := db.WithContext(ctx).Where("uid = ? AND name = ?", userID, name).First(new(AccessToken)).Error
 	if err == nil {
 		return nil, ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": userID, "name": name}}
 	} else if err != gorm.ErrRecordNotFound {
@@ -114,7 +116,7 @@ func (db *accessTokens) Create(userID int64, name string) (*AccessToken, error)
 		Sha1:   sha256[:40], // To pass the column unique constraint, keep the length of SHA1.
 		SHA256: sha256,
 	}
-	if err = db.DB.Create(accessToken).Error; err != nil {
+	if err = db.DB.WithContext(ctx).Create(accessToken).Error; err != nil {
 		return nil, err
 	}
 
@@ -123,8 +125,8 @@ func (db *accessTokens) Create(userID int64, name string) (*AccessToken, error)
 	return accessToken, nil
 }
 
-func (db *accessTokens) DeleteByID(userID, id int64) error {
-	return db.Where("id = ? AND uid = ?", id, userID).Delete(new(AccessToken)).Error
+func (db *accessTokens) DeleteByID(ctx context.Context, userID, id int64) error {
+	return db.WithContext(ctx).Where("id = ? AND uid = ?", id, userID).Delete(new(AccessToken)).Error
 }
 
 var _ errutil.NotFound = (*ErrAccessTokenNotExist)(nil)
@@ -146,10 +148,10 @@ func (ErrAccessTokenNotExist) NotFound() bool {
 	return true
 }
 
-func (db *accessTokens) GetBySHA1(sha1 string) (*AccessToken, error) {
+func (db *accessTokens) GetBySHA1(ctx context.Context, sha1 string) (*AccessToken, error) {
 	sha256 := cryptoutil.SHA256(sha1)
 	token := new(AccessToken)
-	err := db.Where("sha256 = ?", sha256).First(token).Error
+	err := db.WithContext(ctx).Where("sha256 = ?", sha256).First(token).Error
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
 			return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha1}}
@@ -159,11 +161,11 @@ func (db *accessTokens) GetBySHA1(sha1 string) (*AccessToken, error) {
 	return token, nil
 }
 
-func (db *accessTokens) List(userID int64) ([]*AccessToken, error) {
+func (db *accessTokens) List(ctx context.Context, userID int64) ([]*AccessToken, error) {
 	var tokens []*AccessToken
-	return tokens, db.Where("uid = ?", userID).Order("id ASC").Find(&tokens).Error
+	return tokens, db.WithContext(ctx).Where("uid = ?", userID).Order("id ASC").Find(&tokens).Error
 }
 
-func (db *accessTokens) Save(t *AccessToken) error {
-	return db.DB.Save(t).Error
+func (db *accessTokens) Save(ctx context.Context, t *AccessToken) error {
+	return db.DB.WithContext(ctx).Save(t).Error
 }

+ 49 - 69
internal/db/access_tokens_test.go

@@ -5,10 +5,12 @@
 package db
 
 import (
+	"context"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	"gorm.io/gorm"
 
 	"gogs.io/gogs/internal/errutil"
@@ -77,105 +79,87 @@ func TestAccessTokens(t *testing.T) {
 }
 
 func accessTokensCreate(t *testing.T, db *accessTokens) {
+	ctx := context.Background()
+
 	// Create first access token with name "Test"
-	token, err := db.Create(1, "Test")
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err := db.Create(ctx, 1, "Test")
+	require.NoError(t, err)
 
 	assert.Equal(t, int64(1), token.UserID)
 	assert.Equal(t, "Test", token.Name)
 	assert.Equal(t, 40, len(token.Sha1), "sha1 length")
 
 	// Get it back and check the Created field
-	token, err = db.GetBySHA1(token.Sha1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err = db.GetBySHA1(ctx, token.Sha1)
+	require.NoError(t, err)
 	assert.Equal(t, db.NowFunc().Format(time.RFC3339), token.Created.UTC().Format(time.RFC3339))
 
 	// Try create second access token with same name should fail
-	_, err = db.Create(token.UserID, token.Name)
-	expErr := ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": token.UserID, "name": token.Name}}
-	assert.Equal(t, expErr, err)
+	_, err = db.Create(ctx, token.UserID, token.Name)
+	wantErr := ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": token.UserID, "name": token.Name}}
+	assert.Equal(t, wantErr, err)
 }
 
 func accessTokensDeleteByID(t *testing.T, db *accessTokens) {
+	ctx := context.Background()
+
 	// Create an access token with name "Test"
-	token, err := db.Create(1, "Test")
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err := db.Create(ctx, 1, "Test")
+	require.NoError(t, err)
 
 	// Delete a token with mismatched user ID is noop
-	err = db.DeleteByID(2, token.ID)
-	if err != nil {
-		t.Fatal(err)
-	}
+	err = db.DeleteByID(ctx, 2, token.ID)
+	require.NoError(t, err)
 
 	// We should be able to get it back
-	_, err = db.GetBySHA1(token.Sha1)
-	if err != nil {
-		t.Fatal(err)
-	}
-	_, err = db.GetBySHA1(token.Sha1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	_, err = db.GetBySHA1(ctx, token.Sha1)
+	require.NoError(t, err)
+	_, err = db.GetBySHA1(ctx, token.Sha1)
+	require.NoError(t, err)
 
 	// Now delete this token with correct user ID
-	err = db.DeleteByID(token.UserID, token.ID)
-	if err != nil {
-		t.Fatal(err)
-	}
+	err = db.DeleteByID(ctx, token.UserID, token.ID)
+	require.NoError(t, err)
 
 	// We should get token not found error
-	_, err = db.GetBySHA1(token.Sha1)
+	_, err = db.GetBySHA1(ctx, token.Sha1)
 	expErr := ErrAccessTokenNotExist{args: errutil.Args{"sha": token.Sha1}}
 	assert.Equal(t, expErr, err)
 }
 
 func accessTokensGetBySHA(t *testing.T, db *accessTokens) {
+	ctx := context.Background()
+
 	// Create an access token with name "Test"
-	token, err := db.Create(1, "Test")
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err := db.Create(ctx, 1, "Test")
+	require.NoError(t, err)
 
 	// We should be able to get it back
-	_, err = db.GetBySHA1(token.Sha1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	_, err = db.GetBySHA1(ctx, token.Sha1)
+	require.NoError(t, err)
 
 	// Try to get a non-existent token
-	_, err = db.GetBySHA1("bad_sha")
+	_, err = db.GetBySHA1(ctx, "bad_sha")
 	expErr := ErrAccessTokenNotExist{args: errutil.Args{"sha": "bad_sha"}}
 	assert.Equal(t, expErr, err)
 }
 
 func accessTokensList(t *testing.T, db *accessTokens) {
+	ctx := context.Background()
+
 	// Create two access tokens for user 1
-	_, err := db.Create(1, "user1_1")
-	if err != nil {
-		t.Fatal(err)
-	}
-	_, err = db.Create(1, "user1_2")
-	if err != nil {
-		t.Fatal(err)
-	}
+	_, err := db.Create(ctx, 1, "user1_1")
+	require.NoError(t, err)
+	_, err = db.Create(ctx, 1, "user1_2")
+	require.NoError(t, err)
 
 	// Create one access token for user 2
-	_, err = db.Create(2, "user2_1")
-	if err != nil {
-		t.Fatal(err)
-	}
+	_, err = db.Create(ctx, 2, "user2_1")
+	require.NoError(t, err)
 
 	// List all access tokens for user 1
-	tokens, err := db.List(1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	tokens, err := db.List(ctx, 1)
+	require.NoError(t, err)
 	assert.Equal(t, 2, len(tokens), "number of tokens")
 
 	assert.Equal(t, int64(1), tokens[0].UserID)
@@ -186,24 +170,20 @@ func accessTokensList(t *testing.T, db *accessTokens) {
 }
 
 func accessTokensSave(t *testing.T, db *accessTokens) {
+	ctx := context.Background()
+
 	// Create an access token with name "Test"
-	token, err := db.Create(1, "Test")
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err := db.Create(ctx, 1, "Test")
+	require.NoError(t, err)
 
 	// Updated field is zero now
 	assert.True(t, token.Updated.IsZero())
 
-	err = db.Save(token)
-	if err != nil {
-		t.Fatal(err)
-	}
+	err = db.Save(ctx, token)
+	require.NoError(t, err)
 
 	// Get back from DB should have Updated set
-	token, err = db.GetBySHA1(token.Sha1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	token, err = db.GetBySHA1(ctx, token.Sha1)
+	require.NoError(t, err)
 	assert.Equal(t, db.NowFunc().Format(time.RFC3339), token.Updated.UTC().Format(time.RFC3339))
 }

+ 214 - 0
internal/db/mock_gen.go

@@ -0,0 +1,214 @@
+// 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"
+
+	"gogs.io/gogs/internal/lfsutil"
+)
+
+//go:generate go-mockgen -f gogs.io/gogs/internal/db -i AccessTokensStore -o mocks.go
+
+func SetMockAccessTokensStore(t *testing.T, mock AccessTokensStore) {
+	before := AccessTokens
+	AccessTokens = mock
+	t.Cleanup(func() {
+		AccessTokens = before
+	})
+}
+
+var _ LFSStore = (*MockLFSStore)(nil)
+
+type MockLFSStore struct {
+	MockCreateObject     func(repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error
+	MockGetObjectByOID   func(repoID int64, oid lfsutil.OID) (*LFSObject, error)
+	MockGetObjectsByOIDs func(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error)
+}
+
+func (m *MockLFSStore) CreateObject(repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error {
+	return m.MockCreateObject(repoID, oid, size, storage)
+}
+
+func (m *MockLFSStore) GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error) {
+	return m.MockGetObjectByOID(repoID, oid)
+}
+
+func (m *MockLFSStore) GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
+	return m.MockGetObjectsByOIDs(repoID, oids...)
+}
+
+func SetMockLFSStore(t *testing.T, mock LFSStore) {
+	before := LFS
+	LFS = mock
+	t.Cleanup(func() {
+		LFS = before
+	})
+}
+
+var _ loginSourceFilesStore = (*mockLoginSourceFilesStore)(nil)
+
+type mockLoginSourceFilesStore struct {
+	MockGetByID func(id int64) (*LoginSource, error)
+	MockLen     func() int
+	MockList    func(opts ListLoginSourceOpts) []*LoginSource
+	MockUpdate  func(source *LoginSource)
+}
+
+func (m *mockLoginSourceFilesStore) GetByID(id int64) (*LoginSource, error) {
+	return m.MockGetByID(id)
+}
+
+func (m *mockLoginSourceFilesStore) Len() int {
+	return m.MockLen()
+}
+
+func (m *mockLoginSourceFilesStore) List(opts ListLoginSourceOpts) []*LoginSource {
+	return m.MockList(opts)
+}
+
+func (m *mockLoginSourceFilesStore) Update(source *LoginSource) {
+	m.MockUpdate(source)
+}
+
+func setMockLoginSourceFilesStore(t *testing.T, db *loginSources, mock loginSourceFilesStore) {
+	before := db.files
+	db.files = mock
+	t.Cleanup(func() {
+		db.files = before
+	})
+}
+
+var _ loginSourceFileStore = (*mockLoginSourceFileStore)(nil)
+
+type mockLoginSourceFileStore struct {
+	MockSetGeneral func(name, value string)
+	MockSetConfig  func(cfg interface{}) error
+	MockSave       func() error
+}
+
+func (m *mockLoginSourceFileStore) SetGeneral(name, value string) {
+	m.MockSetGeneral(name, value)
+}
+
+func (m *mockLoginSourceFileStore) SetConfig(cfg interface{}) error {
+	return m.MockSetConfig(cfg)
+}
+
+func (m *mockLoginSourceFileStore) Save() error {
+	return m.MockSave()
+}
+
+var _ PermsStore = (*MockPermsStore)(nil)
+
+type MockPermsStore struct {
+	MockAccessMode   func(userID, repoID int64, opts AccessModeOptions) AccessMode
+	MockAuthorize    func(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool
+	MockSetRepoPerms func(repoID int64, accessMap map[int64]AccessMode) error
+}
+
+func (m *MockPermsStore) AccessMode(userID, repoID int64, opts AccessModeOptions) AccessMode {
+	return m.MockAccessMode(userID, repoID, opts)
+}
+
+func (m *MockPermsStore) Authorize(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool {
+	return m.MockAuthorize(userID, repoID, desired, opts)
+}
+
+func (m *MockPermsStore) SetRepoPerms(repoID int64, accessMap map[int64]AccessMode) error {
+	return m.MockSetRepoPerms(repoID, accessMap)
+}
+
+func SetMockPermsStore(t *testing.T, mock PermsStore) {
+	before := Perms
+	Perms = mock
+	t.Cleanup(func() {
+		Perms = before
+	})
+}
+
+var _ ReposStore = (*MockReposStore)(nil)
+
+type MockReposStore struct {
+	MockGetByName func(ownerID int64, name string) (*Repository, error)
+}
+
+func (m *MockReposStore) GetByName(ownerID int64, name string) (*Repository, error) {
+	return m.MockGetByName(ownerID, name)
+}
+
+func SetMockReposStore(t *testing.T, mock ReposStore) {
+	before := Repos
+	Repos = mock
+	t.Cleanup(func() {
+		Repos = before
+	})
+}
+
+var _ TwoFactorsStore = (*MockTwoFactorsStore)(nil)
+
+type MockTwoFactorsStore struct {
+	MockCreate        func(userID int64, key, secret string) error
+	MockGetByUserID   func(userID int64) (*TwoFactor, error)
+	MockIsUserEnabled func(userID int64) bool
+}
+
+func (m *MockTwoFactorsStore) Create(userID int64, key, secret string) error {
+	return m.MockCreate(userID, key, secret)
+}
+
+func (m *MockTwoFactorsStore) GetByUserID(userID int64) (*TwoFactor, error) {
+	return m.MockGetByUserID(userID)
+}
+
+func (m *MockTwoFactorsStore) IsUserEnabled(userID int64) bool {
+	return m.MockIsUserEnabled(userID)
+}
+
+func SetMockTwoFactorsStore(t *testing.T, mock TwoFactorsStore) {
+	before := TwoFactors
+	TwoFactors = mock
+	t.Cleanup(func() {
+		TwoFactors = before
+	})
+}
+
+var _ UsersStore = (*MockUsersStore)(nil)
+
+type MockUsersStore struct {
+	MockAuthenticate  func(username, password string, loginSourceID int64) (*User, error)
+	MockCreate        func(username, email string, opts CreateUserOpts) (*User, error)
+	MockGetByEmail    func(email string) (*User, error)
+	MockGetByID       func(id int64) (*User, error)
+	MockGetByUsername func(username string) (*User, error)
+}
+
+func (m *MockUsersStore) Authenticate(username, password string, loginSourceID int64) (*User, error) {
+	return m.MockAuthenticate(username, password, loginSourceID)
+}
+
+func (m *MockUsersStore) Create(username, email string, opts CreateUserOpts) (*User, error) {
+	return m.MockCreate(username, email, opts)
+}
+
+func (m *MockUsersStore) GetByEmail(email string) (*User, error) {
+	return m.MockGetByEmail(email)
+}
+
+func (m *MockUsersStore) GetByID(id int64) (*User, error) {
+	return m.MockGetByID(id)
+}
+
+func (m *MockUsersStore) GetByUsername(username string) (*User, error) {
+	return m.MockGetByUsername(username)
+}
+
+func SetMockUsersStore(t *testing.T, mock UsersStore) {
+	before := Users
+	Users = mock
+	t.Cleanup(func() {
+		Users = before
+	})
+}

+ 562 - 147
internal/db/mocks.go

@@ -1,244 +1,659 @@
-// 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.
+// Code generated by go-mockgen 1.2.0; DO NOT EDIT.
 
 package db
 
 import (
-	"testing"
-
-	"gogs.io/gogs/internal/lfsutil"
+	"context"
+	"sync"
 )
 
-// NOTE: Mocks are sorted in alphabetical order.
-
-var _ AccessTokensStore = (*MockAccessTokensStore)(nil)
-
+// MockAccessTokensStore is a mock implementation of the AccessTokensStore
+// interface (from the package gogs.io/gogs/internal/db) used for unit
+// testing.
 type MockAccessTokensStore struct {
-	MockCreate     func(userID int64, name string) (*AccessToken, error)
-	MockDeleteByID func(userID, id int64) error
-	MockGetBySHA1  func(sha string) (*AccessToken, error)
-	MockList       func(userID int64) ([]*AccessToken, error)
-	MockSave       func(t *AccessToken) error
+	// CreateFunc is an instance of a mock function object controlling the
+	// behavior of the method Create.
+	CreateFunc *AccessTokensStoreCreateFunc
+	// DeleteByIDFunc is an instance of a mock function object controlling
+	// the behavior of the method DeleteByID.
+	DeleteByIDFunc *AccessTokensStoreDeleteByIDFunc
+	// GetBySHA1Func is an instance of a mock function object controlling
+	// the behavior of the method GetBySHA1.
+	GetBySHA1Func *AccessTokensStoreGetBySHA1Func
+	// ListFunc is an instance of a mock function object controlling the
+	// behavior of the method List.
+	ListFunc *AccessTokensStoreListFunc
+	// SaveFunc is an instance of a mock function object controlling the
+	// behavior of the method Save.
+	SaveFunc *AccessTokensStoreSaveFunc
+}
+
+// NewMockAccessTokensStore creates a new mock of the AccessTokensStore
+// interface. All methods return zero values for all results, unless
+// overwritten.
+func NewMockAccessTokensStore() *MockAccessTokensStore {
+	return &MockAccessTokensStore{
+		CreateFunc: &AccessTokensStoreCreateFunc{
+			defaultHook: func(context.Context, int64, string) (r0 *AccessToken, r1 error) {
+				return
+			},
+		},
+		DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
+			defaultHook: func(context.Context, int64, int64) (r0 error) {
+				return
+			},
+		},
+		GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
+			defaultHook: func(context.Context, string) (r0 *AccessToken, r1 error) {
+				return
+			},
+		},
+		ListFunc: &AccessTokensStoreListFunc{
+			defaultHook: func(context.Context, int64) (r0 []*AccessToken, r1 error) {
+				return
+			},
+		},
+		SaveFunc: &AccessTokensStoreSaveFunc{
+			defaultHook: func(context.Context, *AccessToken) (r0 error) {
+				return
+			},
+		},
+	}
+}
+
+// NewStrictMockAccessTokensStore creates a new mock of the
+// AccessTokensStore interface. All methods panic on invocation, unless
+// overwritten.
+func NewStrictMockAccessTokensStore() *MockAccessTokensStore {
+	return &MockAccessTokensStore{
+		CreateFunc: &AccessTokensStoreCreateFunc{
+			defaultHook: func(context.Context, int64, string) (*AccessToken, error) {
+				panic("unexpected invocation of MockAccessTokensStore.Create")
+			},
+		},
+		DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
+			defaultHook: func(context.Context, int64, int64) error {
+				panic("unexpected invocation of MockAccessTokensStore.DeleteByID")
+			},
+		},
+		GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
+			defaultHook: func(context.Context, string) (*AccessToken, error) {
+				panic("unexpected invocation of MockAccessTokensStore.GetBySHA1")
+			},
+		},
+		ListFunc: &AccessTokensStoreListFunc{
+			defaultHook: func(context.Context, int64) ([]*AccessToken, error) {
+				panic("unexpected invocation of MockAccessTokensStore.List")
+			},
+		},
+		SaveFunc: &AccessTokensStoreSaveFunc{
+			defaultHook: func(context.Context, *AccessToken) error {
+				panic("unexpected invocation of MockAccessTokensStore.Save")
+			},
+		},
+	}
+}
+
+// NewMockAccessTokensStoreFrom creates a new mock of the
+// MockAccessTokensStore interface. All methods delegate to the given
+// implementation, unless overwritten.
+func NewMockAccessTokensStoreFrom(i AccessTokensStore) *MockAccessTokensStore {
+	return &MockAccessTokensStore{
+		CreateFunc: &AccessTokensStoreCreateFunc{
+			defaultHook: i.Create,
+		},
+		DeleteByIDFunc: &AccessTokensStoreDeleteByIDFunc{
+			defaultHook: i.DeleteByID,
+		},
+		GetBySHA1Func: &AccessTokensStoreGetBySHA1Func{
+			defaultHook: i.GetBySHA1,
+		},
+		ListFunc: &AccessTokensStoreListFunc{
+			defaultHook: i.List,
+		},
+		SaveFunc: &AccessTokensStoreSaveFunc{
+			defaultHook: i.Save,
+		},
+	}
+}
+
+// AccessTokensStoreCreateFunc describes the behavior when the Create method
+// of the parent MockAccessTokensStore instance is invoked.
+type AccessTokensStoreCreateFunc struct {
+	defaultHook func(context.Context, int64, string) (*AccessToken, error)
+	hooks       []func(context.Context, int64, string) (*AccessToken, error)
+	history     []AccessTokensStoreCreateFuncCall
+	mutex       sync.Mutex
+}
+
+// Create delegates to the next hook function in the queue and stores the
+// parameter and result values of this invocation.
+func (m *MockAccessTokensStore) Create(v0 context.Context, v1 int64, v2 string) (*AccessToken, error) {
+	r0, r1 := m.CreateFunc.nextHook()(v0, v1, v2)
+	m.CreateFunc.appendCall(AccessTokensStoreCreateFuncCall{v0, v1, v2, r0, r1})
+	return r0, r1
+}
+
+// SetDefaultHook sets function that is called when the Create method of the
+// parent MockAccessTokensStore instance is invoked and the hook queue is
+// empty.
+func (f *AccessTokensStoreCreateFunc) SetDefaultHook(hook func(context.Context, int64, string) (*AccessToken, error)) {
+	f.defaultHook = hook
+}
+
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// Create method of the parent MockAccessTokensStore instance invokes the
+// hook at the front of the queue and discards it. After the queue is empty,
+// the default hook function is invoked for any future action.
+func (f *AccessTokensStoreCreateFunc) PushHook(hook func(context.Context, int64, string) (*AccessToken, error)) {
+	f.mutex.Lock()
+	f.hooks = append(f.hooks, hook)
+	f.mutex.Unlock()
+}
+
+// SetDefaultReturn calls SetDefaultHook with a function that returns the
+// given values.
+func (f *AccessTokensStoreCreateFunc) SetDefaultReturn(r0 *AccessToken, r1 error) {
+	f.SetDefaultHook(func(context.Context, int64, string) (*AccessToken, error) {
+		return r0, r1
+	})
 }
 
-func (m *MockAccessTokensStore) Create(userID int64, name string) (*AccessToken, error) {
-	return m.MockCreate(userID, name)
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *AccessTokensStoreCreateFunc) PushReturn(r0 *AccessToken, r1 error) {
+	f.PushHook(func(context.Context, int64, string) (*AccessToken, error) {
+		return r0, r1
+	})
 }
 
-func (m *MockAccessTokensStore) DeleteByID(userID, id int64) error {
-	return m.MockDeleteByID(userID, id)
+func (f *AccessTokensStoreCreateFunc) nextHook() func(context.Context, int64, string) (*AccessToken, error) {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	if len(f.hooks) == 0 {
+		return f.defaultHook
+	}
+
+	hook := f.hooks[0]
+	f.hooks = f.hooks[1:]
+	return hook
+}
+
+func (f *AccessTokensStoreCreateFunc) appendCall(r0 AccessTokensStoreCreateFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
+}
+
+// History returns a sequence of AccessTokensStoreCreateFuncCall objects
+// describing the invocations of this function.
+func (f *AccessTokensStoreCreateFunc) History() []AccessTokensStoreCreateFuncCall {
+	f.mutex.Lock()
+	history := make([]AccessTokensStoreCreateFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
+
+	return history
+}
+
+// AccessTokensStoreCreateFuncCall is an object that describes an invocation
+// of method Create on an instance of MockAccessTokensStore.
+type AccessTokensStoreCreateFuncCall struct {
+	// Arg0 is the value of the 1st argument passed to this method
+	// invocation.
+	Arg0 context.Context
+	// Arg1 is the value of the 2nd argument passed to this method
+	// invocation.
+	Arg1 int64
+	// Arg2 is the value of the 3rd argument passed to this method
+	// invocation.
+	Arg2 string
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 *AccessToken
+	// Result1 is the value of the 2nd result returned from this method
+	// invocation.
+	Result1 error
+}
+
+// Args returns an interface slice containing the arguments of this
+// invocation.
+func (c AccessTokensStoreCreateFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1, c.Arg2}
+}
+
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c AccessTokensStoreCreateFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0, c.Result1}
+}
+
+// AccessTokensStoreDeleteByIDFunc describes the behavior when the
+// DeleteByID method of the parent MockAccessTokensStore instance is
+// invoked.
+type AccessTokensStoreDeleteByIDFunc struct {
+	defaultHook func(context.Context, int64, int64) error
+	hooks       []func(context.Context, int64, int64) error
+	history     []AccessTokensStoreDeleteByIDFuncCall
+	mutex       sync.Mutex
+}
+
+// DeleteByID delegates to the next hook function in the queue and stores
+// the parameter and result values of this invocation.
+func (m *MockAccessTokensStore) DeleteByID(v0 context.Context, v1 int64, v2 int64) error {
+	r0 := m.DeleteByIDFunc.nextHook()(v0, v1, v2)
+	m.DeleteByIDFunc.appendCall(AccessTokensStoreDeleteByIDFuncCall{v0, v1, v2, r0})
+	return r0
+}
+
+// SetDefaultHook sets function that is called when the DeleteByID method of
+// the parent MockAccessTokensStore instance is invoked and the hook queue
+// is empty.
+func (f *AccessTokensStoreDeleteByIDFunc) SetDefaultHook(hook func(context.Context, int64, int64) error) {
+	f.defaultHook = hook
+}
+
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// DeleteByID method of the parent MockAccessTokensStore instance invokes
+// the hook at the front of the queue and discards it. After the queue is
+// empty, the default hook function is invoked for any future action.
+func (f *AccessTokensStoreDeleteByIDFunc) PushHook(hook func(context.Context, int64, int64) error) {
+	f.mutex.Lock()
+	f.hooks = append(f.hooks, hook)
+	f.mutex.Unlock()
+}
+
+// SetDefaultReturn calls SetDefaultHook with a function that returns the
+// given values.
+func (f *AccessTokensStoreDeleteByIDFunc) SetDefaultReturn(r0 error) {
+	f.SetDefaultHook(func(context.Context, int64, int64) error {
+		return r0
+	})
 }
 
-func (m *MockAccessTokensStore) GetBySHA1(sha string) (*AccessToken, error) {
-	return m.MockGetBySHA1(sha)
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *AccessTokensStoreDeleteByIDFunc) PushReturn(r0 error) {
+	f.PushHook(func(context.Context, int64, int64) error {
+		return r0
+	})
 }
 
-func (m *MockAccessTokensStore) List(userID int64) ([]*AccessToken, error) {
-	return m.MockList(userID)
-}
+func (f *AccessTokensStoreDeleteByIDFunc) nextHook() func(context.Context, int64, int64) error {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	if len(f.hooks) == 0 {
+		return f.defaultHook
+	}
 
-func (m *MockAccessTokensStore) Save(t *AccessToken) error {
-	return m.MockSave(t)
+	hook := f.hooks[0]
+	f.hooks = f.hooks[1:]
+	return hook
 }
 
-func SetMockAccessTokensStore(t *testing.T, mock AccessTokensStore) {
-	before := AccessTokens
-	AccessTokens = mock
-	t.Cleanup(func() {
-		AccessTokens = before
-	})
+func (f *AccessTokensStoreDeleteByIDFunc) appendCall(r0 AccessTokensStoreDeleteByIDFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
 }
 
-var _ LFSStore = (*MockLFSStore)(nil)
+// History returns a sequence of AccessTokensStoreDeleteByIDFuncCall objects
+// describing the invocations of this function.
+func (f *AccessTokensStoreDeleteByIDFunc) History() []AccessTokensStoreDeleteByIDFuncCall {
+	f.mutex.Lock()
+	history := make([]AccessTokensStoreDeleteByIDFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
 
-type MockLFSStore struct {
-	MockCreateObject     func(repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error
-	MockGetObjectByOID   func(repoID int64, oid lfsutil.OID) (*LFSObject, error)
-	MockGetObjectsByOIDs func(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error)
+	return history
 }
 
-func (m *MockLFSStore) CreateObject(repoID int64, oid lfsutil.OID, size int64, storage lfsutil.Storage) error {
-	return m.MockCreateObject(repoID, oid, size, storage)
+// AccessTokensStoreDeleteByIDFuncCall is an object that describes an
+// invocation of method DeleteByID on an instance of MockAccessTokensStore.
+type AccessTokensStoreDeleteByIDFuncCall struct {
+	// Arg0 is the value of the 1st argument passed to this method
+	// invocation.
+	Arg0 context.Context
+	// Arg1 is the value of the 2nd argument passed to this method
+	// invocation.
+	Arg1 int64
+	// Arg2 is the value of the 3rd argument passed to this method
+	// invocation.
+	Arg2 int64
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 error
 }
 
-func (m *MockLFSStore) GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error) {
-	return m.MockGetObjectByOID(repoID, oid)
+// Args returns an interface slice containing the arguments of this
+// invocation.
+func (c AccessTokensStoreDeleteByIDFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1, c.Arg2}
 }
 
-func (m *MockLFSStore) GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
-	return m.MockGetObjectsByOIDs(repoID, oids...)
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c AccessTokensStoreDeleteByIDFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0}
 }
 
-func SetMockLFSStore(t *testing.T, mock LFSStore) {
-	before := LFS
-	LFS = mock
-	t.Cleanup(func() {
-		LFS = before
-	})
+// AccessTokensStoreGetBySHA1Func describes the behavior when the GetBySHA1
+// method of the parent MockAccessTokensStore instance is invoked.
+type AccessTokensStoreGetBySHA1Func struct {
+	defaultHook func(context.Context, string) (*AccessToken, error)
+	hooks       []func(context.Context, string) (*AccessToken, error)
+	history     []AccessTokensStoreGetBySHA1FuncCall
+	mutex       sync.Mutex
 }
 
-var _ loginSourceFilesStore = (*mockLoginSourceFilesStore)(nil)
+// GetBySHA1 delegates to the next hook function in the queue and stores the
+// parameter and result values of this invocation.
+func (m *MockAccessTokensStore) GetBySHA1(v0 context.Context, v1 string) (*AccessToken, error) {
+	r0, r1 := m.GetBySHA1Func.nextHook()(v0, v1)
+	m.GetBySHA1Func.appendCall(AccessTokensStoreGetBySHA1FuncCall{v0, v1, r0, r1})
+	return r0, r1
+}
 
-type mockLoginSourceFilesStore struct {
-	MockGetByID func(id int64) (*LoginSource, error)
-	MockLen     func() int
-	MockList    func(opts ListLoginSourceOpts) []*LoginSource
-	MockUpdate  func(source *LoginSource)
+// SetDefaultHook sets function that is called when the GetBySHA1 method of
+// the parent MockAccessTokensStore instance is invoked and the hook queue
+// is empty.
+func (f *AccessTokensStoreGetBySHA1Func) SetDefaultHook(hook func(context.Context, string) (*AccessToken, error)) {
+	f.defaultHook = hook
 }
 
-func (m *mockLoginSourceFilesStore) GetByID(id int64) (*LoginSource, error) {
-	return m.MockGetByID(id)
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// GetBySHA1 method of the parent MockAccessTokensStore instance invokes the
+// hook at the front of the queue and discards it. After the queue is empty,
+// the default hook function is invoked for any future action.
+func (f *AccessTokensStoreGetBySHA1Func) PushHook(hook func(context.Context, string) (*AccessToken, error)) {
+	f.mutex.Lock()
+	f.hooks = append(f.hooks, hook)
+	f.mutex.Unlock()
 }
 
-func (m *mockLoginSourceFilesStore) Len() int {
-	return m.MockLen()
+// SetDefaultReturn calls SetDefaultHook with a function that returns the
+// given values.
+func (f *AccessTokensStoreGetBySHA1Func) SetDefaultReturn(r0 *AccessToken, r1 error) {
+	f.SetDefaultHook(func(context.Context, string) (*AccessToken, error) {
+		return r0, r1
+	})
 }
 
-func (m *mockLoginSourceFilesStore) List(opts ListLoginSourceOpts) []*LoginSource {
-	return m.MockList(opts)
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *AccessTokensStoreGetBySHA1Func) PushReturn(r0 *AccessToken, r1 error) {
+	f.PushHook(func(context.Context, string) (*AccessToken, error) {
+		return r0, r1
+	})
 }
 
-func (m *mockLoginSourceFilesStore) Update(source *LoginSource) {
-	m.MockUpdate(source)
+func (f *AccessTokensStoreGetBySHA1Func) nextHook() func(context.Context, string) (*AccessToken, error) {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	if len(f.hooks) == 0 {
+		return f.defaultHook
+	}
+
+	hook := f.hooks[0]
+	f.hooks = f.hooks[1:]
+	return hook
 }
 
-func setMockLoginSourceFilesStore(t *testing.T, db *loginSources, mock loginSourceFilesStore) {
-	before := db.files
-	db.files = mock
-	t.Cleanup(func() {
-		db.files = before
-	})
+func (f *AccessTokensStoreGetBySHA1Func) appendCall(r0 AccessTokensStoreGetBySHA1FuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
 }
 
-var _ loginSourceFileStore = (*mockLoginSourceFileStore)(nil)
+// History returns a sequence of AccessTokensStoreGetBySHA1FuncCall objects
+// describing the invocations of this function.
+func (f *AccessTokensStoreGetBySHA1Func) History() []AccessTokensStoreGetBySHA1FuncCall {
+	f.mutex.Lock()
+	history := make([]AccessTokensStoreGetBySHA1FuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
 
-type mockLoginSourceFileStore struct {
-	MockSetGeneral func(name, value string)
-	MockSetConfig  func(cfg interface{}) error
-	MockSave       func() error
+	return history
 }
 
-func (m *mockLoginSourceFileStore) SetGeneral(name, value string) {
-	m.MockSetGeneral(name, value)
+// AccessTokensStoreGetBySHA1FuncCall is an object that describes an
+// invocation of method GetBySHA1 on an instance of MockAccessTokensStore.
+type AccessTokensStoreGetBySHA1FuncCall struct {
+	// Arg0 is the value of the 1st argument passed to this method
+	// invocation.
+	Arg0 context.Context
+	// Arg1 is the value of the 2nd argument passed to this method
+	// invocation.
+	Arg1 string
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 *AccessToken
+	// Result1 is the value of the 2nd result returned from this method
+	// invocation.
+	Result1 error
 }
 
-func (m *mockLoginSourceFileStore) SetConfig(cfg interface{}) error {
-	return m.MockSetConfig(cfg)
+// Args returns an interface slice containing the arguments of this
+// invocation.
+func (c AccessTokensStoreGetBySHA1FuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1}
 }
 
-func (m *mockLoginSourceFileStore) Save() error {
-	return m.MockSave()
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c AccessTokensStoreGetBySHA1FuncCall) Results() []interface{} {
+	return []interface{}{c.Result0, c.Result1}
 }
 
-var _ PermsStore = (*MockPermsStore)(nil)
+// AccessTokensStoreListFunc describes the behavior when the List method of
+// the parent MockAccessTokensStore instance is invoked.
+type AccessTokensStoreListFunc struct {
+	defaultHook func(context.Context, int64) ([]*AccessToken, error)
+	hooks       []func(context.Context, int64) ([]*AccessToken, error)
+	history     []AccessTokensStoreListFuncCall
+	mutex       sync.Mutex
+}
 
-type MockPermsStore struct {
-	MockAccessMode   func(userID, repoID int64, opts AccessModeOptions) AccessMode
-	MockAuthorize    func(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool
-	MockSetRepoPerms func(repoID int64, accessMap map[int64]AccessMode) error
+// List delegates to the next hook function in the queue and stores the
+// parameter and result values of this invocation.
+func (m *MockAccessTokensStore) List(v0 context.Context, v1 int64) ([]*AccessToken, error) {
+	r0, r1 := m.ListFunc.nextHook()(v0, v1)
+	m.ListFunc.appendCall(AccessTokensStoreListFuncCall{v0, v1, r0, r1})
+	return r0, r1
 }
 
-func (m *MockPermsStore) AccessMode(userID, repoID int64, opts AccessModeOptions) AccessMode {
-	return m.MockAccessMode(userID, repoID, opts)
+// SetDefaultHook sets function that is called when the List method of the
+// parent MockAccessTokensStore instance is invoked and the hook queue is
+// empty.
+func (f *AccessTokensStoreListFunc) SetDefaultHook(hook func(context.Context, int64) ([]*AccessToken, error)) {
+	f.defaultHook = hook
 }
 
-func (m *MockPermsStore) Authorize(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool {
-	return m.MockAuthorize(userID, repoID, desired, opts)
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// List method of the parent MockAccessTokensStore instance invokes the hook
+// at the front of the queue and discards it. After the queue is empty, the
+// default hook function is invoked for any future action.
+func (f *AccessTokensStoreListFunc) PushHook(hook func(context.Context, int64) ([]*AccessToken, error)) {
+	f.mutex.Lock()
+	f.hooks = append(f.hooks, hook)
+	f.mutex.Unlock()
 }
 
-func (m *MockPermsStore) SetRepoPerms(repoID int64, accessMap map[int64]AccessMode) error {
-	return m.MockSetRepoPerms(repoID, accessMap)
+// SetDefaultReturn calls SetDefaultHook with a function that returns the
+// given values.
+func (f *AccessTokensStoreListFunc) SetDefaultReturn(r0 []*AccessToken, r1 error) {
+	f.SetDefaultHook(func(context.Context, int64) ([]*AccessToken, error) {
+		return r0, r1
+	})
 }
 
-func SetMockPermsStore(t *testing.T, mock PermsStore) {
-	before := Perms
-	Perms = mock
-	t.Cleanup(func() {
-		Perms = before
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *AccessTokensStoreListFunc) PushReturn(r0 []*AccessToken, r1 error) {
+	f.PushHook(func(context.Context, int64) ([]*AccessToken, error) {
+		return r0, r1
 	})
 }
 
-var _ ReposStore = (*MockReposStore)(nil)
+func (f *AccessTokensStoreListFunc) nextHook() func(context.Context, int64) ([]*AccessToken, error) {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
 
-type MockReposStore struct {
-	MockGetByName func(ownerID int64, name string) (*Repository, error)
+	if len(f.hooks) == 0 {
+		return f.defaultHook
+	}
+
+	hook := f.hooks[0]
+	f.hooks = f.hooks[1:]
+	return hook
 }
 
-func (m *MockReposStore) GetByName(ownerID int64, name string) (*Repository, error) {
-	return m.MockGetByName(ownerID, name)
+func (f *AccessTokensStoreListFunc) appendCall(r0 AccessTokensStoreListFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
 }
 
-func SetMockReposStore(t *testing.T, mock ReposStore) {
-	before := Repos
-	Repos = mock
-	t.Cleanup(func() {
-		Repos = before
-	})
+// History returns a sequence of AccessTokensStoreListFuncCall objects
+// describing the invocations of this function.
+func (f *AccessTokensStoreListFunc) History() []AccessTokensStoreListFuncCall {
+	f.mutex.Lock()
+	history := make([]AccessTokensStoreListFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
+
+	return history
 }
 
-var _ TwoFactorsStore = (*MockTwoFactorsStore)(nil)
+// AccessTokensStoreListFuncCall is an object that describes an invocation
+// of method List on an instance of MockAccessTokensStore.
+type AccessTokensStoreListFuncCall struct {
+	// Arg0 is the value of the 1st argument passed to this method
+	// invocation.
+	Arg0 context.Context
+	// Arg1 is the value of the 2nd argument passed to this method
+	// invocation.
+	Arg1 int64
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 []*AccessToken
+	// Result1 is the value of the 2nd result returned from this method
+	// invocation.
+	Result1 error
+}
 
-type MockTwoFactorsStore struct {
-	MockCreate        func(userID int64, key, secret string) error
-	MockGetByUserID   func(userID int64) (*TwoFactor, error)
-	MockIsUserEnabled func(userID int64) bool
+// Args returns an interface slice containing the arguments of this
+// invocation.
+func (c AccessTokensStoreListFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1}
 }
 
-func (m *MockTwoFactorsStore) Create(userID int64, key, secret string) error {
-	return m.MockCreate(userID, key, secret)
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c AccessTokensStoreListFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0, c.Result1}
 }
 
-func (m *MockTwoFactorsStore) GetByUserID(userID int64) (*TwoFactor, error) {
-	return m.MockGetByUserID(userID)
+// AccessTokensStoreSaveFunc describes the behavior when the Save method of
+// the parent MockAccessTokensStore instance is invoked.
+type AccessTokensStoreSaveFunc struct {
+	defaultHook func(context.Context, *AccessToken) error
+	hooks       []func(context.Context, *AccessToken) error
+	history     []AccessTokensStoreSaveFuncCall
+	mutex       sync.Mutex
 }
 
-func (m *MockTwoFactorsStore) IsUserEnabled(userID int64) bool {
-	return m.MockIsUserEnabled(userID)
+// Save delegates to the next hook function in the queue and stores the
+// parameter and result values of this invocation.
+func (m *MockAccessTokensStore) Save(v0 context.Context, v1 *AccessToken) error {
+	r0 := m.SaveFunc.nextHook()(v0, v1)
+	m.SaveFunc.appendCall(AccessTokensStoreSaveFuncCall{v0, v1, r0})
+	return r0
 }
 
-func SetMockTwoFactorsStore(t *testing.T, mock TwoFactorsStore) {
-	before := TwoFactors
-	TwoFactors = mock
-	t.Cleanup(func() {
-		TwoFactors = before
-	})
+// SetDefaultHook sets function that is called when the Save method of the
+// parent MockAccessTokensStore instance is invoked and the hook queue is
+// empty.
+func (f *AccessTokensStoreSaveFunc) SetDefaultHook(hook func(context.Context, *AccessToken) error) {
+	f.defaultHook = hook
+}
+
+// PushHook adds a function to the end of hook queue. Each invocation of the
+// Save method of the parent MockAccessTokensStore instance invokes the hook
+// at the front of the queue and discards it. After the queue is empty, the
+// default hook function is invoked for any future action.
+func (f *AccessTokensStoreSaveFunc) PushHook(hook func(context.Context, *AccessToken) error) {
+	f.mutex.Lock()
+	f.hooks = append(f.hooks, hook)
+	f.mutex.Unlock()
 }
 
-var _ UsersStore = (*MockUsersStore)(nil)
+// SetDefaultReturn calls SetDefaultHook with a function that returns the
+// given values.
+func (f *AccessTokensStoreSaveFunc) SetDefaultReturn(r0 error) {
+	f.SetDefaultHook(func(context.Context, *AccessToken) error {
+		return r0
+	})
+}
 
-type MockUsersStore struct {
-	MockAuthenticate  func(username, password string, loginSourceID int64) (*User, error)
-	MockCreate        func(username, email string, opts CreateUserOpts) (*User, error)
-	MockGetByEmail    func(email string) (*User, error)
-	MockGetByID       func(id int64) (*User, error)
-	MockGetByUsername func(username string) (*User, error)
+// PushReturn calls PushHook with a function that returns the given values.
+func (f *AccessTokensStoreSaveFunc) PushReturn(r0 error) {
+	f.PushHook(func(context.Context, *AccessToken) error {
+		return r0
+	})
 }
 
-func (m *MockUsersStore) Authenticate(username, password string, loginSourceID int64) (*User, error) {
-	return m.MockAuthenticate(username, password, loginSourceID)
+func (f *AccessTokensStoreSaveFunc) nextHook() func(context.Context, *AccessToken) error {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	if len(f.hooks) == 0 {
+		return f.defaultHook
+	}
+
+	hook := f.hooks[0]
+	f.hooks = f.hooks[1:]
+	return hook
 }
 
-func (m *MockUsersStore) Create(username, email string, opts CreateUserOpts) (*User, error) {
-	return m.MockCreate(username, email, opts)
+func (f *AccessTokensStoreSaveFunc) appendCall(r0 AccessTokensStoreSaveFuncCall) {
+	f.mutex.Lock()
+	f.history = append(f.history, r0)
+	f.mutex.Unlock()
 }
 
-func (m *MockUsersStore) GetByEmail(email string) (*User, error) {
-	return m.MockGetByEmail(email)
+// History returns a sequence of AccessTokensStoreSaveFuncCall objects
+// describing the invocations of this function.
+func (f *AccessTokensStoreSaveFunc) History() []AccessTokensStoreSaveFuncCall {
+	f.mutex.Lock()
+	history := make([]AccessTokensStoreSaveFuncCall, len(f.history))
+	copy(history, f.history)
+	f.mutex.Unlock()
+
+	return history
 }
 
-func (m *MockUsersStore) GetByID(id int64) (*User, error) {
-	return m.MockGetByID(id)
+// AccessTokensStoreSaveFuncCall is an object that describes an invocation
+// of method Save on an instance of MockAccessTokensStore.
+type AccessTokensStoreSaveFuncCall struct {
+	// Arg0 is the value of the 1st argument passed to this method
+	// invocation.
+	Arg0 context.Context
+	// Arg1 is the value of the 2nd argument passed to this method
+	// invocation.
+	Arg1 *AccessToken
+	// Result0 is the value of the 1st result returned from this method
+	// invocation.
+	Result0 error
 }
 
-func (m *MockUsersStore) GetByUsername(username string) (*User, error) {
-	return m.MockGetByUsername(username)
+// Args returns an interface slice containing the arguments of this
+// invocation.
+func (c AccessTokensStoreSaveFuncCall) Args() []interface{} {
+	return []interface{}{c.Arg0, c.Arg1}
 }
 
-func SetMockUsersStore(t *testing.T, mock UsersStore) {
-	before := Users
-	Users = mock
-	t.Cleanup(func() {
-		Users = before
-	})
+// Results returns an interface slice containing the results of this
+// invocation.
+func (c AccessTokensStoreSaveFuncCall) Results() []interface{} {
+	return []interface{}{c.Result0}
 }

+ 2 - 2
internal/route/api/v1/user/app.go

@@ -14,7 +14,7 @@ import (
 )
 
 func ListAccessTokens(c *context.APIContext) {
-	tokens, err := db.AccessTokens.List(c.User.ID)
+	tokens, err := db.AccessTokens.List(c.Req.Context(), c.User.ID)
 	if err != nil {
 		c.Error(err, "list access tokens")
 		return
@@ -28,7 +28,7 @@ func ListAccessTokens(c *context.APIContext) {
 }
 
 func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
-	t, err := db.AccessTokens.Create(c.User.ID, form.Name)
+	t, err := db.AccessTokens.Create(c.Req.Context(), c.User.ID, form.Name)
 	if err != nil {
 		if db.IsErrAccessTokenAlreadyExist(err) {
 			c.ErrorStatus(http.StatusUnprocessableEntity, err)

+ 2 - 2
internal/route/lfs/route.go

@@ -72,7 +72,7 @@ func authenticate() macaron.Handler {
 
 		// If username and password authentication failed, try again using username as an access token.
 		if auth.IsErrBadCredentials(err) {
-			token, err := db.AccessTokens.GetBySHA1(username)
+			token, err := db.AccessTokens.GetBySHA1(c.Req.Context(), username)
 			if err != nil {
 				if db.IsErrAccessTokenNotExist(err) {
 					askCredentials(c.Resp)
@@ -82,7 +82,7 @@ func authenticate() macaron.Handler {
 				}
 				return
 			}
-			if err = db.AccessTokens.Save(token); err != nil {
+			if err = db.AccessTokens.Save(c.Req.Context(), token); err != nil {
 				log.Error("Failed to update access token: %v", err)
 			}
 

+ 13 - 13
internal/route/lfs/route_test.go

@@ -31,7 +31,7 @@ func Test_authenticate(t *testing.T) {
 		header                http.Header
 		mockUsersStore        *db.MockUsersStore
 		mockTwoFactorsStore   *db.MockTwoFactorsStore
-		mockAccessTokensStore *db.MockAccessTokensStore
+		mockAccessTokensStore func() db.AccessTokensStore
 		expStatusCode         int
 		expHeader             http.Header
 		expBody               string
@@ -74,10 +74,10 @@ func Test_authenticate(t *testing.T) {
 					return nil, auth.ErrBadCredentials{}
 				},
 			},
-			mockAccessTokensStore: &db.MockAccessTokensStore{
-				MockGetBySHA1: func(sha string) (*db.AccessToken, error) {
-					return nil, db.ErrAccessTokenNotExist{}
-				},
+			mockAccessTokensStore: func() db.AccessTokensStore {
+				mock := db.NewMockAccessTokensStore()
+				mock.GetBySHA1Func.SetDefaultReturn(nil, db.ErrAccessTokenNotExist{})
+				return mock
 			},
 			expStatusCode: http.StatusUnauthorized,
 			expHeader: http.Header{
@@ -119,13 +119,10 @@ func Test_authenticate(t *testing.T) {
 					return &db.User{ID: 1, Name: "unknwon"}, nil
 				},
 			},
-			mockAccessTokensStore: &db.MockAccessTokensStore{
-				MockGetBySHA1: func(sha string) (*db.AccessToken, error) {
-					return &db.AccessToken{}, nil
-				},
-				MockSave: func(t *db.AccessToken) error {
-					return nil
-				},
+			mockAccessTokensStore: func() db.AccessTokensStore {
+				mock := db.NewMockAccessTokensStore()
+				mock.GetBySHA1Func.SetDefaultReturn(&db.AccessToken{}, nil)
+				return mock
 			},
 			expStatusCode: http.StatusOK,
 			expHeader:     http.Header{},
@@ -136,7 +133,10 @@ func Test_authenticate(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			db.SetMockUsersStore(t, test.mockUsersStore)
 			db.SetMockTwoFactorsStore(t, test.mockTwoFactorsStore)
-			db.SetMockAccessTokensStore(t, test.mockAccessTokensStore)
+
+			if test.mockAccessTokensStore != nil {
+				db.SetMockAccessTokensStore(t, test.mockAccessTokensStore())
+			}
 
 			r, err := http.NewRequest("GET", "/", nil)
 			if err != nil {

+ 2 - 2
internal/route/repo/http.go

@@ -131,7 +131,7 @@ func HTTPContexter() macaron.Handler {
 
 		// If username and password combination failed, try again using username as a token.
 		if authUser == nil {
-			token, err := db.AccessTokens.GetBySHA1(authUsername)
+			token, err := db.AccessTokens.GetBySHA1(c.Req.Context(), authUsername)
 			if err != nil {
 				if db.IsErrAccessTokenNotExist(err) {
 					askCredentials(c, http.StatusUnauthorized, "")
@@ -141,7 +141,7 @@ func HTTPContexter() macaron.Handler {
 				}
 				return
 			}
-			if err = db.AccessTokens.Save(token); err != nil {
+			if err = db.AccessTokens.Save(c.Req.Context(), token); err != nil {
 				log.Error("Failed to update access token: %v", err)
 			}
 

+ 4 - 4
internal/route/user/setting.go

@@ -581,7 +581,7 @@ func SettingsApplications(c *context.Context) {
 	c.Title("settings.applications")
 	c.PageIs("SettingsApplications")
 
-	tokens, err := db.AccessTokens.List(c.User.ID)
+	tokens, err := db.AccessTokens.List(c.Req.Context(), c.User.ID)
 	if err != nil {
 		c.Errorf(err, "list access tokens")
 		return
@@ -596,7 +596,7 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
 	c.PageIs("SettingsApplications")
 
 	if c.HasError() {
-		tokens, err := db.AccessTokens.List(c.User.ID)
+		tokens, err := db.AccessTokens.List(c.Req.Context(), c.User.ID)
 		if err != nil {
 			c.Errorf(err, "list access tokens")
 			return
@@ -607,7 +607,7 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
 		return
 	}
 
-	t, err := db.AccessTokens.Create(c.User.ID, f.Name)
+	t, err := db.AccessTokens.Create(c.Req.Context(), c.User.ID, f.Name)
 	if err != nil {
 		if db.IsErrAccessTokenAlreadyExist(err) {
 			c.Flash.Error(c.Tr("settings.token_name_exists"))
@@ -624,7 +624,7 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) {
 }
 
 func SettingsDeleteApplication(c *context.Context) {
-	if err := db.AccessTokens.DeleteByID(c.User.ID, c.QueryInt64("id")); err != nil {
+	if err := db.AccessTokens.DeleteByID(c.Req.Context(), c.User.ID, c.QueryInt64("id")); err != nil {
 		c.Flash.Error("DeleteAccessTokenByID: " + err.Error())
 	} else {
 		c.Flash.Success(c.Tr("settings.delete_token_success"))