Skip to content

Commit cf02cd7

Browse files
ethantkoeniglunny
authored andcommitted
Fix and test for delete user (#1713)
* Fix and test for delete user * Run updates in batches * Unit test
1 parent 85a7396 commit cf02cd7

File tree

11 files changed

+160
-68
lines changed

11 files changed

+160
-68
lines changed

integrations/delete_user_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"bytes"
9+
"net/http"
10+
"net/url"
11+
"testing"
12+
13+
"code.gitea.io/gitea/models"
14+
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
func TestDeleteUser(t *testing.T) {
19+
prepareTestEnv(t)
20+
21+
session := loginUser(t, "user1", "password")
22+
23+
req, err := http.NewRequest("GET", "/admin/users/8", nil)
24+
assert.NoError(t, err)
25+
resp := session.MakeRequest(t, req)
26+
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
27+
28+
doc, err := NewHtmlParser(resp.Body)
29+
assert.NoError(t, err)
30+
req, err = http.NewRequest("POST", "/admin/users/8/delete",
31+
bytes.NewBufferString(url.Values{
32+
"_csrf": []string{doc.GetInputValueByName("_csrf")},
33+
}.Encode()))
34+
assert.NoError(t, err)
35+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
36+
resp = session.MakeRequest(t, req)
37+
assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
38+
39+
models.AssertNotExistsBean(t, &models.User{ID: 8})
40+
models.CheckConsistencyFor(t, &models.User{})
41+
}

models/consistency_test.go renamed to models/consistency.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
"github.com/stretchr/testify/assert"
1313
)
1414

15-
// ConsistencyCheckable a type that can be tested for database consistency
16-
type ConsistencyCheckable interface {
17-
CheckForConsistency(t *testing.T)
15+
// consistencyCheckable a type that can be tested for database consistency
16+
type consistencyCheckable interface {
17+
checkForConsistency(t *testing.T)
1818
}
1919

2020
// CheckConsistencyForAll test that the entire database is consistent
@@ -44,12 +44,12 @@ func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) {
4444

4545
for i := 0; i < sliceValue.Len(); i++ {
4646
entity := sliceValue.Index(i).Interface()
47-
checkable, ok := entity.(ConsistencyCheckable)
47+
checkable, ok := entity.(consistencyCheckable)
4848
if !ok {
4949
t.Errorf("Expected %+v (of type %T) to be checkable for consistency",
5050
entity, entity)
5151
} else {
52-
checkable.CheckForConsistency(t)
52+
checkable.checkForConsistency(t)
5353
}
5454
}
5555
}
@@ -68,7 +68,7 @@ func assertCount(t *testing.T, bean interface{}, expected int) {
6868
"Failed consistency test, the counted bean (of type %T) was %+v", bean, bean)
6969
}
7070

71-
func (user *User) CheckForConsistency(t *testing.T) {
71+
func (user *User) checkForConsistency(t *testing.T) {
7272
assertCount(t, &Repository{OwnerID: user.ID}, user.NumRepos)
7373
assertCount(t, &Star{UID: user.ID}, user.NumStars)
7474
assertCount(t, &OrgUser{OrgID: user.ID}, user.NumMembers)
@@ -81,7 +81,7 @@ func (user *User) CheckForConsistency(t *testing.T) {
8181
}
8282
}
8383

84-
func (repo *Repository) CheckForConsistency(t *testing.T) {
84+
func (repo *Repository) checkForConsistency(t *testing.T) {
8585
assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
8686
assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
8787
assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
@@ -112,7 +112,7 @@ func (repo *Repository) CheckForConsistency(t *testing.T) {
112112
"Unexpected number of closed milestones for repo %+v", repo)
113113
}
114114

115-
func (issue *Issue) CheckForConsistency(t *testing.T) {
115+
func (issue *Issue) checkForConsistency(t *testing.T) {
116116
actual := getCount(t, x.Where("type=?", CommentTypeComment), &Comment{IssueID: issue.ID})
117117
assert.EqualValues(t, issue.NumComments, actual,
118118
"Unexpected number of comments for issue %+v", issue)
@@ -122,21 +122,21 @@ func (issue *Issue) CheckForConsistency(t *testing.T) {
122122
}
123123
}
124124

125-
func (pr *PullRequest) CheckForConsistency(t *testing.T) {
125+
func (pr *PullRequest) checkForConsistency(t *testing.T) {
126126
issue := AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue)
127127
assert.True(t, issue.IsPull)
128128
assert.EqualValues(t, issue.Index, pr.Index)
129129
}
130130

131-
func (milestone *Milestone) CheckForConsistency(t *testing.T) {
131+
func (milestone *Milestone) checkForConsistency(t *testing.T) {
132132
assertCount(t, &Issue{MilestoneID: milestone.ID}, milestone.NumIssues)
133133

134134
actual := getCount(t, x.Where("is_closed=?", true), &Issue{MilestoneID: milestone.ID})
135135
assert.EqualValues(t, milestone.NumClosedIssues, actual,
136136
"Unexpected number of closed issues for milestone %+v", milestone)
137137
}
138138

139-
func (label *Label) CheckForConsistency(t *testing.T) {
139+
func (label *Label) checkForConsistency(t *testing.T) {
140140
issueLabels := make([]*IssueLabel, 0, 10)
141141
assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID}))
142142
assert.EqualValues(t, label.NumIssues, len(issueLabels),
@@ -155,12 +155,12 @@ func (label *Label) CheckForConsistency(t *testing.T) {
155155
"Unexpected number of closed issues for label %+v", label)
156156
}
157157

158-
func (team *Team) CheckForConsistency(t *testing.T) {
158+
func (team *Team) checkForConsistency(t *testing.T) {
159159
assertCount(t, &TeamUser{TeamID: team.ID}, team.NumMembers)
160160
assertCount(t, &TeamRepo{TeamID: team.ID}, team.NumRepos)
161161
}
162162

163-
func (action *Action) CheckForConsistency(t *testing.T) {
163+
func (action *Action) checkForConsistency(t *testing.T) {
164164
repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository)
165165
owner := AssertExistsAndLoadBean(t, &User{ID: repo.OwnerID}).(*User)
166166
actor := AssertExistsAndLoadBean(t, &User{ID: action.ActUserID}).(*User)

models/fixtures/follow.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,13 @@
22
id: 1
33
user_id: 4
44
follow_id: 2
5+
6+
-
7+
id: 2
8+
user_id: 8
9+
follow_id: 2
10+
11+
-
12+
id: 3
13+
user_id: 2
14+
follow_id: 8

models/fixtures/user.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
name: user1
55
full_name: User One
66
7-
passwd: password
7+
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
88
type: 0 # individual
9-
salt: salt
9+
salt: ZogKvWdyEx
1010
is_admin: true
1111
avatar: avatar1
1212
avatar_email: [email protected]
@@ -26,7 +26,8 @@
2626
avatar_email: [email protected]
2727
num_repos: 2
2828
num_stars: 2
29-
num_followers: 1
29+
num_followers: 2
30+
num_following: 1
3031
is_active: true
3132

3233
-
@@ -123,6 +124,8 @@
123124
avatar_email: [email protected]
124125
num_repos: 0
125126
is_active: true
127+
num_followers: 1
128+
num_following: 1
126129

127130
-
128131
id: 9

models/main_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package models
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/setting"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
if err := CreateTestEngine(); err != nil {
14+
fmt.Printf("Error creating test engine: %v\n", err)
15+
os.Exit(1)
16+
}
17+
18+
setting.AppURL = "https://try.gitea.io/"
19+
setting.RunUser = "runuser"
20+
setting.SSH.Port = 3000
21+
setting.SSH.Domain = "try.gitea.io"
22+
setting.RepoRootPath = filepath.Join(os.TempDir(), "repos")
23+
setting.AppDataPath = filepath.Join(os.TempDir(), "appdata")
24+
25+
os.Exit(m.Run())
26+
}

models/setup_for_test.go renamed to models/unit_tests.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,18 @@
55
package models
66

77
import (
8-
"fmt"
9-
"os"
10-
"path/filepath"
118
"testing"
129

13-
"code.gitea.io/gitea/modules/setting"
14-
1510
"github.com/go-xorm/core"
1611
"github.com/go-xorm/xorm"
1712
_ "github.com/mattn/go-sqlite3" // for the test engine
1813
"github.com/stretchr/testify/assert"
1914
"gopkg.in/testfixtures.v2"
2015
)
2116

17+
// NonexistentID an ID that will never exist
2218
const NonexistentID = 9223372036854775807
2319

24-
func TestMain(m *testing.M) {
25-
if err := CreateTestEngine(); err != nil {
26-
fmt.Printf("Error creating test engine: %v\n", err)
27-
os.Exit(1)
28-
}
29-
30-
setting.AppURL = "https://try.gitea.io/"
31-
setting.RunUser = "runuser"
32-
setting.SSH.Port = 3000
33-
setting.SSH.Domain = "try.gitea.io"
34-
setting.RepoRootPath = filepath.Join(os.TempDir(), "repos")
35-
setting.AppDataPath = filepath.Join(os.TempDir(), "appdata")
36-
37-
os.Exit(m.Run())
38-
}
39-
4020
// CreateTestEngine create an xorm engine for testing
4121
func CreateTestEngine() error {
4222
var err error
@@ -52,6 +32,7 @@ func CreateTestEngine() error {
5232
return InitFixtures(&testfixtures.SQLite{}, "fixtures/")
5333
}
5434

35+
// TestFixturesAreConsistent assert that test fixtures are consistent
5536
func TestFixturesAreConsistent(t *testing.T) {
5637
assert.NoError(t, PrepareTestDatabase())
5738
CheckConsistencyForAll(t)

models/user.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -924,38 +924,41 @@ func deleteUser(e *xorm.Session, u *User) error {
924924
}
925925

926926
// ***** START: Watch *****
927-
watches := make([]*Watch, 0, 10)
928-
if err = e.Find(&watches, &Watch{UserID: u.ID}); err != nil {
927+
watchedRepoIDs := make([]int64, 0, 10)
928+
if err = e.Table("watch").Cols("watch.repo_id").
929+
Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
929930
return fmt.Errorf("get all watches: %v", err)
930931
}
931-
for i := range watches {
932-
if _, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
933-
return fmt.Errorf("decrease repository watch number[%d]: %v", watches[i].RepoID, err)
934-
}
932+
if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).Update(new(Repository)); err != nil {
933+
return fmt.Errorf("decrease repository num_watches: %v", err)
935934
}
936935
// ***** END: Watch *****
937936

938937
// ***** START: Star *****
939-
stars := make([]*Star, 0, 10)
940-
if err = e.Find(&stars, &Star{UID: u.ID}); err != nil {
938+
starredRepoIDs := make([]int64, 0, 10)
939+
if err = e.Table("star").Cols("star.repo_id").
940+
Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
941941
return fmt.Errorf("get all stars: %v", err)
942-
}
943-
for i := range stars {
944-
if _, err = e.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
945-
return fmt.Errorf("decrease repository star number[%d]: %v", stars[i].RepoID, err)
946-
}
942+
} else if _, err = e.Decr("num_watches").In("id", starredRepoIDs).Update(new(Repository)); err != nil {
943+
return fmt.Errorf("decrease repository num_stars: %v", err)
947944
}
948945
// ***** END: Star *****
949946

950947
// ***** START: Follow *****
951-
followers := make([]*Follow, 0, 10)
952-
if err = e.Find(&followers, &Follow{UserID: u.ID}); err != nil {
953-
return fmt.Errorf("get all followers: %v", err)
948+
followeeIDs := make([]int64, 0, 10)
949+
if err = e.Table("follow").Cols("follow.follow_id").
950+
Where("follow.user_id = ?", u.ID).Find(&followeeIDs); err != nil {
951+
return fmt.Errorf("get all followees: %v", err)
952+
} else if _, err = e.Decr("num_followers").In("id", followeeIDs).Update(new(User)); err != nil {
953+
return fmt.Errorf("decrease user num_followers: %v", err)
954954
}
955-
for i := range followers {
956-
if _, err = e.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
957-
return fmt.Errorf("decrease user follower number[%d]: %v", followers[i].UserID, err)
958-
}
955+
956+
followerIDs := make([]int64, 0, 10)
957+
if err = e.Table("follow").Cols("follow.user_id").
958+
Where("follow.follow_id = ?", u.ID).Find(&followerIDs); err != nil {
959+
return fmt.Errorf("get all followers: %v", err)
960+
} else if _, err = e.Decr("num_following").In("id", followerIDs).Update(new(User)); err != nil {
961+
return fmt.Errorf("decrease user num_following: %v", err)
959962
}
960963
// ***** END: Follow *****
961964

@@ -965,6 +968,7 @@ func deleteUser(e *xorm.Session, u *User) error {
965968
&Access{UserID: u.ID},
966969
&Watch{UserID: u.ID},
967970
&Star{UID: u.ID},
971+
&Follow{UserID: u.ID},
968972
&Follow{FollowID: u.ID},
969973
&Action{UserID: u.ID},
970974
&IssueUser{UID: u.ID},

models/user_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,36 @@ func TestCanCreateOrganization(t *testing.T) {
3636
user.AllowCreateOrganization = true
3737
assert.True(t, admin.CanCreateOrganization())
3838
assert.False(t, user.CanCreateOrganization())
39+
}
40+
41+
func TestDeleteUser(t *testing.T) {
42+
test := func(userID int64) {
43+
assert.NoError(t, PrepareTestDatabase())
44+
user := AssertExistsAndLoadBean(t, &User{ID: userID}).(*User)
45+
46+
ownedRepos := make([]*Repository, 0, 10)
47+
assert.NoError(t, x.Find(&ownedRepos, &Repository{OwnerID: userID}))
48+
if len(ownedRepos) > 0 {
49+
err := DeleteUser(user)
50+
assert.Error(t, err)
51+
assert.True(t, IsErrUserOwnRepos(err))
52+
return
53+
}
3954

55+
orgUsers := make([]*OrgUser, 0, 10)
56+
assert.NoError(t, x.Find(&orgUsers, &OrgUser{UID: userID}))
57+
for _, orgUser := range orgUsers {
58+
if err := RemoveOrgUser(orgUser.OrgID, orgUser.UID); err != nil {
59+
assert.True(t, IsErrLastOrgOwner(err))
60+
return
61+
}
62+
}
63+
assert.NoError(t, DeleteUser(user))
64+
AssertNotExistsBean(t, &User{ID: userID})
65+
CheckConsistencyFor(t, &User{}, &Repository{})
66+
}
67+
test(2)
68+
test(4)
69+
test(8)
70+
test(11)
4071
}

vendor/github.com/go-xorm/builder/cond_in.go

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/go-xorm/builder/cond_notin.go

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)