users_test.go 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401
  1. // Copyright 2020 The Gogs 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. package database
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. "gorm.io/gorm"
  16. "gogs.io/gogs/internal/auth"
  17. "gogs.io/gogs/internal/conf"
  18. "gogs.io/gogs/internal/dbtest"
  19. "gogs.io/gogs/internal/dbutil"
  20. "gogs.io/gogs/internal/errutil"
  21. "gogs.io/gogs/internal/osutil"
  22. "gogs.io/gogs/internal/repoutil"
  23. "gogs.io/gogs/internal/userutil"
  24. "gogs.io/gogs/public"
  25. )
  26. func TestUser_BeforeCreate(t *testing.T) {
  27. now := time.Now()
  28. db := &gorm.DB{
  29. Config: &gorm.Config{
  30. SkipDefaultTransaction: true,
  31. NowFunc: func() time.Time {
  32. return now
  33. },
  34. },
  35. }
  36. t.Run("CreatedUnix has been set", func(t *testing.T) {
  37. user := &User{
  38. CreatedUnix: 1,
  39. }
  40. _ = user.BeforeCreate(db)
  41. assert.Equal(t, int64(1), user.CreatedUnix)
  42. assert.Equal(t, int64(0), user.UpdatedUnix)
  43. })
  44. t.Run("CreatedUnix has not been set", func(t *testing.T) {
  45. user := &User{}
  46. _ = user.BeforeCreate(db)
  47. assert.Equal(t, db.NowFunc().Unix(), user.CreatedUnix)
  48. assert.Equal(t, db.NowFunc().Unix(), user.UpdatedUnix)
  49. })
  50. }
  51. func TestUser_AfterFind(t *testing.T) {
  52. now := time.Now()
  53. db := &gorm.DB{
  54. Config: &gorm.Config{
  55. SkipDefaultTransaction: true,
  56. NowFunc: func() time.Time {
  57. return now
  58. },
  59. },
  60. }
  61. user := &User{
  62. FullName: "user1<script src=http://localhost:8181/xss.js>",
  63. CreatedUnix: now.Unix(),
  64. UpdatedUnix: now.Unix(),
  65. }
  66. _ = user.AfterFind(db)
  67. assert.Equal(t, "user1", user.FullName)
  68. assert.Equal(t, user.CreatedUnix, user.Created.Unix())
  69. assert.Equal(t, user.UpdatedUnix, user.Updated.Unix())
  70. }
  71. func TestUsers(t *testing.T) {
  72. if testing.Short() {
  73. t.Skip()
  74. }
  75. t.Parallel()
  76. ctx := context.Background()
  77. tables := []any{
  78. new(User), new(EmailAddress), new(Repository), new(Follow), new(PullRequest), new(PublicKey), new(OrgUser),
  79. new(Watch), new(Star), new(Issue), new(AccessToken), new(Collaboration), new(Action), new(IssueUser),
  80. new(Access),
  81. }
  82. db := &users{
  83. DB: dbtest.NewDB(t, "users", tables...),
  84. }
  85. for _, tc := range []struct {
  86. name string
  87. test func(t *testing.T, ctx context.Context, db *users)
  88. }{
  89. {"Authenticate", usersAuthenticate},
  90. {"ChangeUsername", usersChangeUsername},
  91. {"Count", usersCount},
  92. {"Create", usersCreate},
  93. {"DeleteCustomAvatar", usersDeleteCustomAvatar},
  94. {"DeleteByID", usersDeleteByID},
  95. {"DeleteInactivated", usersDeleteInactivated},
  96. {"GetByEmail", usersGetByEmail},
  97. {"GetByID", usersGetByID},
  98. {"GetByUsername", usersGetByUsername},
  99. {"GetByKeyID", usersGetByKeyID},
  100. {"GetMailableEmailsByUsernames", usersGetMailableEmailsByUsernames},
  101. {"IsUsernameUsed", usersIsUsernameUsed},
  102. {"List", usersList},
  103. {"ListFollowers", usersListFollowers},
  104. {"ListFollowings", usersListFollowings},
  105. {"SearchByName", usersSearchByName},
  106. {"Update", usersUpdate},
  107. {"UseCustomAvatar", usersUseCustomAvatar},
  108. {"AddEmail", usersAddEmail},
  109. {"GetEmail", usersGetEmail},
  110. {"ListEmails", usersListEmails},
  111. {"MarkEmailActivated", usersMarkEmailActivated},
  112. {"MarkEmailPrimary", usersMarkEmailPrimary},
  113. {"DeleteEmail", usersDeleteEmail},
  114. {"Follow", usersFollow},
  115. {"IsFollowing", usersIsFollowing},
  116. {"Unfollow", usersUnfollow},
  117. } {
  118. t.Run(tc.name, func(t *testing.T) {
  119. t.Cleanup(func() {
  120. err := clearTables(t, db.DB, tables...)
  121. require.NoError(t, err)
  122. })
  123. tc.test(t, ctx, db)
  124. })
  125. if t.Failed() {
  126. break
  127. }
  128. }
  129. }
  130. func usersAuthenticate(t *testing.T, ctx context.Context, db *users) {
  131. password := "pa$$word"
  132. alice, err := db.Create(ctx, "alice", "[email protected]",
  133. CreateUserOptions{
  134. Password: password,
  135. },
  136. )
  137. require.NoError(t, err)
  138. t.Run("user not found", func(t *testing.T) {
  139. _, err := db.Authenticate(ctx, "bob", password, -1)
  140. wantErr := auth.ErrBadCredentials{Args: map[string]any{"login": "bob"}}
  141. assert.Equal(t, wantErr, err)
  142. })
  143. t.Run("invalid password", func(t *testing.T) {
  144. _, err := db.Authenticate(ctx, alice.Name, "bad_password", -1)
  145. wantErr := auth.ErrBadCredentials{Args: map[string]any{"login": alice.Name, "userID": alice.ID}}
  146. assert.Equal(t, wantErr, err)
  147. })
  148. t.Run("via email and password", func(t *testing.T) {
  149. user, err := db.Authenticate(ctx, alice.Email, password, -1)
  150. require.NoError(t, err)
  151. assert.Equal(t, alice.Name, user.Name)
  152. })
  153. t.Run("via username and password", func(t *testing.T) {
  154. user, err := db.Authenticate(ctx, alice.Name, password, -1)
  155. require.NoError(t, err)
  156. assert.Equal(t, alice.Name, user.Name)
  157. })
  158. t.Run("login source mismatch", func(t *testing.T) {
  159. _, err := db.Authenticate(ctx, alice.Email, password, 1)
  160. gotErr := fmt.Sprintf("%v", err)
  161. wantErr := ErrLoginSourceMismatch{args: map[string]any{"actual": 0, "expect": 1}}.Error()
  162. assert.Equal(t, wantErr, gotErr)
  163. })
  164. t.Run("via login source", func(t *testing.T) {
  165. mockLoginSources := NewMockLoginSourcesStore()
  166. mockLoginSources.GetByIDFunc.SetDefaultHook(func(ctx context.Context, id int64) (*LoginSource, error) {
  167. mockProvider := NewMockProvider()
  168. mockProvider.AuthenticateFunc.SetDefaultReturn(&auth.ExternalAccount{}, nil)
  169. s := &LoginSource{
  170. IsActived: true,
  171. Provider: mockProvider,
  172. }
  173. return s, nil
  174. })
  175. setMockLoginSourcesStore(t, mockLoginSources)
  176. bob, err := db.Create(ctx, "bob", "[email protected]",
  177. CreateUserOptions{
  178. Password: password,
  179. LoginSource: 1,
  180. },
  181. )
  182. require.NoError(t, err)
  183. user, err := db.Authenticate(ctx, bob.Email, password, 1)
  184. require.NoError(t, err)
  185. assert.Equal(t, bob.Name, user.Name)
  186. })
  187. t.Run("new user via login source", func(t *testing.T) {
  188. mockLoginSources := NewMockLoginSourcesStore()
  189. mockLoginSources.GetByIDFunc.SetDefaultHook(func(ctx context.Context, id int64) (*LoginSource, error) {
  190. mockProvider := NewMockProvider()
  191. mockProvider.AuthenticateFunc.SetDefaultReturn(
  192. &auth.ExternalAccount{
  193. Name: "cindy",
  194. Email: "[email protected]",
  195. },
  196. nil,
  197. )
  198. s := &LoginSource{
  199. IsActived: true,
  200. Provider: mockProvider,
  201. }
  202. return s, nil
  203. })
  204. setMockLoginSourcesStore(t, mockLoginSources)
  205. user, err := db.Authenticate(ctx, "cindy", password, 1)
  206. require.NoError(t, err)
  207. assert.Equal(t, "cindy", user.Name)
  208. user, err = db.GetByUsername(ctx, "cindy")
  209. require.NoError(t, err)
  210. assert.Equal(t, "[email protected]", user.Email)
  211. })
  212. }
  213. func usersChangeUsername(t *testing.T, ctx context.Context, db *users) {
  214. alice, err := db.Create(
  215. ctx,
  216. "alice",
  217. "[email protected]",
  218. CreateUserOptions{
  219. Activated: true,
  220. },
  221. )
  222. require.NoError(t, err)
  223. t.Run("name not allowed", func(t *testing.T) {
  224. err := db.ChangeUsername(ctx, alice.ID, "-")
  225. wantErr := ErrNameNotAllowed{
  226. args: errutil.Args{
  227. "reason": "reserved",
  228. "name": "-",
  229. },
  230. }
  231. assert.Equal(t, wantErr, err)
  232. })
  233. t.Run("name already exists", func(t *testing.T) {
  234. bob, err := db.Create(
  235. ctx,
  236. "bob",
  237. "[email protected]",
  238. CreateUserOptions{
  239. Activated: true,
  240. },
  241. )
  242. require.NoError(t, err)
  243. err = db.ChangeUsername(ctx, alice.ID, bob.Name)
  244. wantErr := ErrUserAlreadyExist{
  245. args: errutil.Args{
  246. "name": bob.Name,
  247. },
  248. }
  249. assert.Equal(t, wantErr, err)
  250. })
  251. tempRepositoryRoot := filepath.Join(os.TempDir(), "usersChangeUsername-tempRepositoryRoot")
  252. conf.SetMockRepository(
  253. t,
  254. conf.RepositoryOpts{
  255. Root: tempRepositoryRoot,
  256. },
  257. )
  258. err = os.RemoveAll(tempRepositoryRoot)
  259. require.NoError(t, err)
  260. defer func() { _ = os.RemoveAll(tempRepositoryRoot) }()
  261. tempServerAppDataPath := filepath.Join(os.TempDir(), "usersChangeUsername-tempServerAppDataPath")
  262. conf.SetMockServer(
  263. t,
  264. conf.ServerOpts{
  265. AppDataPath: tempServerAppDataPath,
  266. },
  267. )
  268. err = os.RemoveAll(tempServerAppDataPath)
  269. require.NoError(t, err)
  270. defer func() { _ = os.RemoveAll(tempServerAppDataPath) }()
  271. repo, err := NewReposStore(db.DB).Create(
  272. ctx,
  273. alice.ID,
  274. CreateRepoOptions{
  275. Name: "test-repo-1",
  276. },
  277. )
  278. require.NoError(t, err)
  279. // TODO: Use PullRequests.Create to replace SQL hack when the method is available.
  280. err = db.Exec(`INSERT INTO pull_request (head_user_name) VALUES (?)`, alice.Name).Error
  281. require.NoError(t, err)
  282. err = db.Model(&User{}).Where("id = ?", alice.ID).Update("updated_unix", 0).Error
  283. require.NoError(t, err)
  284. err = os.MkdirAll(repoutil.UserPath(alice.Name), os.ModePerm)
  285. require.NoError(t, err)
  286. err = os.MkdirAll(repoutil.RepositoryLocalPath(repo.ID), os.ModePerm)
  287. require.NoError(t, err)
  288. err = os.MkdirAll(repoutil.RepositoryLocalWikiPath(repo.ID), os.ModePerm)
  289. require.NoError(t, err)
  290. // Make sure mock data is set up correctly
  291. // TODO: Use PullRequests.GetByID to replace SQL hack when the method is available.
  292. var headUserName string
  293. err = db.Model(&PullRequest{}).Select("head_user_name").Row().Scan(&headUserName)
  294. require.NoError(t, err)
  295. assert.Equal(t, headUserName, alice.Name)
  296. var updatedUnix int64
  297. err = db.Model(&User{}).Select("updated_unix").Where("id = ?", alice.ID).Row().Scan(&updatedUnix)
  298. require.NoError(t, err)
  299. assert.Equal(t, int64(0), updatedUnix)
  300. assert.True(t, osutil.IsExist(repoutil.UserPath(alice.Name)))
  301. assert.True(t, osutil.IsExist(repoutil.RepositoryLocalPath(repo.ID)))
  302. assert.True(t, osutil.IsExist(repoutil.RepositoryLocalWikiPath(repo.ID)))
  303. const newUsername = "alice-new"
  304. err = db.ChangeUsername(ctx, alice.ID, newUsername)
  305. require.NoError(t, err)
  306. // TODO: Use PullRequests.GetByID to replace SQL hack when the method is available.
  307. err = db.Model(&PullRequest{}).Select("head_user_name").Row().Scan(&headUserName)
  308. require.NoError(t, err)
  309. assert.Equal(t, headUserName, newUsername)
  310. assert.True(t, osutil.IsExist(repoutil.UserPath(newUsername)))
  311. assert.False(t, osutil.IsExist(repoutil.UserPath(alice.Name)))
  312. assert.False(t, osutil.IsExist(repoutil.RepositoryLocalPath(repo.ID)))
  313. assert.False(t, osutil.IsExist(repoutil.RepositoryLocalWikiPath(repo.ID)))
  314. alice, err = db.GetByID(ctx, alice.ID)
  315. require.NoError(t, err)
  316. assert.Equal(t, newUsername, alice.Name)
  317. assert.Equal(t, db.NowFunc().Unix(), alice.UpdatedUnix)
  318. // Change the cases of the username should just be fine
  319. err = db.ChangeUsername(ctx, alice.ID, strings.ToUpper(newUsername))
  320. require.NoError(t, err)
  321. alice, err = db.GetByID(ctx, alice.ID)
  322. require.NoError(t, err)
  323. assert.Equal(t, strings.ToUpper(newUsername), alice.Name)
  324. }
  325. func usersCount(t *testing.T, ctx context.Context, db *users) {
  326. // Has no user initially
  327. got := db.Count(ctx)
  328. assert.Equal(t, int64(0), got)
  329. _, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  330. require.NoError(t, err)
  331. got = db.Count(ctx)
  332. assert.Equal(t, int64(1), got)
  333. // Create an organization shouldn't count
  334. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
  335. org1, err := db.Create(ctx, "org1", "[email protected]", CreateUserOptions{})
  336. require.NoError(t, err)
  337. err = db.Exec(
  338. dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"),
  339. UserTypeOrganization, org1.ID,
  340. ).Error
  341. require.NoError(t, err)
  342. got = db.Count(ctx)
  343. assert.Equal(t, int64(1), got)
  344. }
  345. func usersCreate(t *testing.T, ctx context.Context, db *users) {
  346. alice, err := db.Create(
  347. ctx,
  348. "alice",
  349. "[email protected]",
  350. CreateUserOptions{
  351. Activated: true,
  352. },
  353. )
  354. require.NoError(t, err)
  355. t.Run("name not allowed", func(t *testing.T) {
  356. _, err := db.Create(ctx, "-", "", CreateUserOptions{})
  357. wantErr := ErrNameNotAllowed{
  358. args: errutil.Args{
  359. "reason": "reserved",
  360. "name": "-",
  361. },
  362. }
  363. assert.Equal(t, wantErr, err)
  364. })
  365. t.Run("name already exists", func(t *testing.T) {
  366. _, err := db.Create(ctx, alice.Name, "", CreateUserOptions{})
  367. wantErr := ErrUserAlreadyExist{
  368. args: errutil.Args{
  369. "name": alice.Name,
  370. },
  371. }
  372. assert.Equal(t, wantErr, err)
  373. })
  374. t.Run("email already exists", func(t *testing.T) {
  375. _, err := db.Create(ctx, "bob", alice.Email, CreateUserOptions{})
  376. wantErr := ErrEmailAlreadyUsed{
  377. args: errutil.Args{
  378. "email": alice.Email,
  379. },
  380. }
  381. assert.Equal(t, wantErr, err)
  382. })
  383. user, err := db.GetByUsername(ctx, alice.Name)
  384. require.NoError(t, err)
  385. assert.Equal(t, db.NowFunc().Format(time.RFC3339), user.Created.UTC().Format(time.RFC3339))
  386. assert.Equal(t, db.NowFunc().Format(time.RFC3339), user.Updated.UTC().Format(time.RFC3339))
  387. }
  388. func usersDeleteCustomAvatar(t *testing.T, ctx context.Context, db *users) {
  389. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  390. require.NoError(t, err)
  391. avatar, err := public.Files.ReadFile("img/avatar_default.png")
  392. require.NoError(t, err)
  393. avatarPath := userutil.CustomAvatarPath(alice.ID)
  394. _ = os.Remove(avatarPath)
  395. defer func() { _ = os.Remove(avatarPath) }()
  396. err = db.UseCustomAvatar(ctx, alice.ID, avatar)
  397. require.NoError(t, err)
  398. // Make sure avatar is saved and the user flag is updated.
  399. got := osutil.IsFile(avatarPath)
  400. assert.True(t, got)
  401. alice, err = db.GetByID(ctx, alice.ID)
  402. require.NoError(t, err)
  403. assert.True(t, alice.UseCustomAvatar)
  404. // Delete avatar should remove the file and revert the user flag.
  405. err = db.DeleteCustomAvatar(ctx, alice.ID)
  406. require.NoError(t, err)
  407. got = osutil.IsFile(avatarPath)
  408. assert.False(t, got)
  409. alice, err = db.GetByID(ctx, alice.ID)
  410. require.NoError(t, err)
  411. assert.False(t, alice.UseCustomAvatar)
  412. }
  413. func usersDeleteByID(t *testing.T, ctx context.Context, db *users) {
  414. reposStore := NewReposStore(db.DB)
  415. t.Run("user still has repository ownership", func(t *testing.T) {
  416. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  417. require.NoError(t, err)
  418. _, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"})
  419. require.NoError(t, err)
  420. err = db.DeleteByID(ctx, alice.ID, false)
  421. wantErr := ErrUserOwnRepos{errutil.Args{"userID": alice.ID}}
  422. assert.Equal(t, wantErr, err)
  423. })
  424. t.Run("user still has organization membership", func(t *testing.T) {
  425. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  426. require.NoError(t, err)
  427. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
  428. org1, err := db.Create(ctx, "org1", "[email protected]", CreateUserOptions{})
  429. require.NoError(t, err)
  430. err = db.Exec(
  431. dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?)", "user"),
  432. UserTypeOrganization, org1.ID,
  433. ).Error
  434. require.NoError(t, err)
  435. // TODO: Use Orgs.Join to replace SQL hack when the method is available.
  436. err = db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, bob.ID, org1.ID).Error
  437. require.NoError(t, err)
  438. err = db.DeleteByID(ctx, bob.ID, false)
  439. wantErr := ErrUserHasOrgs{errutil.Args{"userID": bob.ID}}
  440. assert.Equal(t, wantErr, err)
  441. })
  442. cindy, err := db.Create(ctx, "cindy", "[email protected]", CreateUserOptions{})
  443. require.NoError(t, err)
  444. frank, err := db.Create(ctx, "frank", "[email protected]", CreateUserOptions{})
  445. require.NoError(t, err)
  446. repo2, err := reposStore.Create(ctx, cindy.ID, CreateRepoOptions{Name: "repo2"})
  447. require.NoError(t, err)
  448. testUser, err := db.Create(ctx, "testUser", "[email protected]", CreateUserOptions{})
  449. require.NoError(t, err)
  450. // Mock watches, stars and follows
  451. err = reposStore.Watch(ctx, testUser.ID, repo2.ID)
  452. require.NoError(t, err)
  453. err = reposStore.Star(ctx, testUser.ID, repo2.ID)
  454. require.NoError(t, err)
  455. err = db.Follow(ctx, testUser.ID, cindy.ID)
  456. require.NoError(t, err)
  457. err = db.Follow(ctx, frank.ID, testUser.ID)
  458. require.NoError(t, err)
  459. // Mock "authorized_keys" file
  460. // TODO: Use PublicKeys.Add to replace SQL hack when the method is available.
  461. publicKey := &PublicKey{
  462. OwnerID: testUser.ID,
  463. Name: "test-key",
  464. Fingerprint: "12:f8:7e:78:61:b4:bf:e2:de:24:15:96:4e:d4:72:53",
  465. Content: "test-key-content",
  466. }
  467. err = db.DB.Create(publicKey).Error
  468. require.NoError(t, err)
  469. tempSSHRootPath := filepath.Join(os.TempDir(), "usersDeleteByID-tempSSHRootPath")
  470. conf.SetMockSSH(t, conf.SSHOpts{RootPath: tempSSHRootPath})
  471. err = NewPublicKeysStore(db.DB).RewriteAuthorizedKeys()
  472. require.NoError(t, err)
  473. // Mock issue assignee
  474. // TODO: Use Issues.Assign to replace SQL hack when the method is available.
  475. issue := &Issue{
  476. RepoID: repo2.ID,
  477. Index: 1,
  478. PosterID: cindy.ID,
  479. Title: "test-issue",
  480. AssigneeID: testUser.ID,
  481. }
  482. err = db.DB.Create(issue).Error
  483. require.NoError(t, err)
  484. // Mock random entries in related tables
  485. for _, table := range []any{
  486. &AccessToken{UserID: testUser.ID},
  487. &Collaboration{UserID: testUser.ID},
  488. &Access{UserID: testUser.ID},
  489. &Action{UserID: testUser.ID},
  490. &IssueUser{UserID: testUser.ID},
  491. &EmailAddress{UserID: testUser.ID},
  492. } {
  493. err = db.DB.Create(table).Error
  494. require.NoError(t, err, "table for %T", table)
  495. }
  496. // Mock user directory
  497. tempRepositoryRoot := filepath.Join(os.TempDir(), "usersDeleteByID-tempRepositoryRoot")
  498. conf.SetMockRepository(t, conf.RepositoryOpts{Root: tempRepositoryRoot})
  499. tempUserPath := repoutil.UserPath(testUser.Name)
  500. err = os.MkdirAll(tempUserPath, os.ModePerm)
  501. require.NoError(t, err)
  502. // Mock user custom avatar
  503. tempPictureAvatarUploadPath := filepath.Join(os.TempDir(), "usersDeleteByID-tempPictureAvatarUploadPath")
  504. conf.SetMockPicture(t, conf.PictureOpts{AvatarUploadPath: tempPictureAvatarUploadPath})
  505. err = os.MkdirAll(tempPictureAvatarUploadPath, os.ModePerm)
  506. require.NoError(t, err)
  507. tempCustomAvatarPath := userutil.CustomAvatarPath(testUser.ID)
  508. err = os.WriteFile(tempCustomAvatarPath, []byte("test"), 0600)
  509. require.NoError(t, err)
  510. // Verify mock data
  511. repo2, err = reposStore.GetByID(ctx, repo2.ID)
  512. require.NoError(t, err)
  513. assert.Equal(t, 2, repo2.NumWatches) // The owner is watching the repo by default.
  514. assert.Equal(t, 1, repo2.NumStars)
  515. cindy, err = db.GetByID(ctx, cindy.ID)
  516. require.NoError(t, err)
  517. assert.Equal(t, 1, cindy.NumFollowers)
  518. frank, err = db.GetByID(ctx, frank.ID)
  519. require.NoError(t, err)
  520. assert.Equal(t, 1, frank.NumFollowing)
  521. authorizedKeys, err := os.ReadFile(authorizedKeysPath())
  522. require.NoError(t, err)
  523. assert.Contains(t, string(authorizedKeys), fmt.Sprintf("key-%d", publicKey.ID))
  524. assert.Contains(t, string(authorizedKeys), publicKey.Content)
  525. // TODO: Use Issues.GetByID to replace SQL hack when the method is available.
  526. err = db.DB.First(issue, issue.ID).Error
  527. require.NoError(t, err)
  528. assert.Equal(t, testUser.ID, issue.AssigneeID)
  529. relatedTables := []any{
  530. &Watch{UserID: testUser.ID},
  531. &Star{UserID: testUser.ID},
  532. &Follow{UserID: testUser.ID},
  533. &PublicKey{OwnerID: testUser.ID},
  534. &AccessToken{UserID: testUser.ID},
  535. &Collaboration{UserID: testUser.ID},
  536. &Access{UserID: testUser.ID},
  537. &Action{UserID: testUser.ID},
  538. &IssueUser{UserID: testUser.ID},
  539. &EmailAddress{UserID: testUser.ID},
  540. }
  541. for _, table := range relatedTables {
  542. var count int64
  543. err = db.DB.Model(table).Where(table).Count(&count).Error
  544. require.NoError(t, err, "table for %T", table)
  545. assert.NotZero(t, count, "table for %T", table)
  546. }
  547. assert.True(t, osutil.IsExist(tempUserPath))
  548. assert.True(t, osutil.IsExist(tempCustomAvatarPath))
  549. // Pull the trigger
  550. err = db.DeleteByID(ctx, testUser.ID, false)
  551. require.NoError(t, err)
  552. // Verify after-the-fact data
  553. repo2, err = reposStore.GetByID(ctx, repo2.ID)
  554. require.NoError(t, err)
  555. assert.Equal(t, 1, repo2.NumWatches) // The owner is watching the repo by default.
  556. assert.Equal(t, 0, repo2.NumStars)
  557. cindy, err = db.GetByID(ctx, cindy.ID)
  558. require.NoError(t, err)
  559. assert.Equal(t, 0, cindy.NumFollowers)
  560. frank, err = db.GetByID(ctx, frank.ID)
  561. require.NoError(t, err)
  562. assert.Equal(t, 0, frank.NumFollowing)
  563. authorizedKeys, err = os.ReadFile(authorizedKeysPath())
  564. require.NoError(t, err)
  565. assert.Empty(t, authorizedKeys)
  566. // TODO: Use Issues.GetByID to replace SQL hack when the method is available.
  567. err = db.DB.First(issue, issue.ID).Error
  568. require.NoError(t, err)
  569. assert.Equal(t, int64(0), issue.AssigneeID)
  570. for _, table := range []any{
  571. &Watch{UserID: testUser.ID},
  572. &Star{UserID: testUser.ID},
  573. &Follow{UserID: testUser.ID},
  574. &PublicKey{OwnerID: testUser.ID},
  575. &AccessToken{UserID: testUser.ID},
  576. &Collaboration{UserID: testUser.ID},
  577. &Access{UserID: testUser.ID},
  578. &Action{UserID: testUser.ID},
  579. &IssueUser{UserID: testUser.ID},
  580. &EmailAddress{UserID: testUser.ID},
  581. } {
  582. var count int64
  583. err = db.DB.Model(table).Where(table).Count(&count).Error
  584. require.NoError(t, err, "table for %T", table)
  585. assert.Equal(t, int64(0), count, "table for %T", table)
  586. }
  587. assert.False(t, osutil.IsExist(tempUserPath))
  588. assert.False(t, osutil.IsExist(tempCustomAvatarPath))
  589. _, err = db.GetByID(ctx, testUser.ID)
  590. wantErr := ErrUserNotExist{errutil.Args{"userID": testUser.ID}}
  591. assert.Equal(t, wantErr, err)
  592. }
  593. func usersDeleteInactivated(t *testing.T, ctx context.Context, db *users) {
  594. // User with repository ownership should be skipped
  595. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  596. require.NoError(t, err)
  597. reposStore := NewReposStore(db.DB)
  598. _, err = reposStore.Create(ctx, alice.ID, CreateRepoOptions{Name: "repo1"})
  599. require.NoError(t, err)
  600. // User with organization membership should be skipped
  601. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  602. require.NoError(t, err)
  603. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
  604. org1, err := db.Create(ctx, "org1", "[email protected]", CreateUserOptions{})
  605. require.NoError(t, err)
  606. err = db.Exec(
  607. dbutil.Quote("UPDATE %s SET type = ? WHERE id IN (?)", "user"),
  608. UserTypeOrganization, org1.ID,
  609. ).Error
  610. require.NoError(t, err)
  611. // TODO: Use Orgs.Join to replace SQL hack when the method is available.
  612. err = db.Exec(`INSERT INTO org_user (uid, org_id) VALUES (?, ?)`, bob.ID, org1.ID).Error
  613. require.NoError(t, err)
  614. // User activated state should be skipped
  615. _, err = db.Create(ctx, "cindy", "[email protected]", CreateUserOptions{Activated: true})
  616. require.NoError(t, err)
  617. // User meant to be deleted
  618. david, err := db.Create(ctx, "david", "[email protected]", CreateUserOptions{})
  619. require.NoError(t, err)
  620. tempSSHRootPath := filepath.Join(os.TempDir(), "usersDeleteInactivated-tempSSHRootPath")
  621. conf.SetMockSSH(t, conf.SSHOpts{RootPath: tempSSHRootPath})
  622. err = db.DeleteInactivated()
  623. require.NoError(t, err)
  624. _, err = db.GetByID(ctx, david.ID)
  625. wantErr := ErrUserNotExist{errutil.Args{"userID": david.ID}}
  626. assert.Equal(t, wantErr, err)
  627. users, err := db.List(ctx, 1, 10)
  628. require.NoError(t, err)
  629. require.Len(t, users, 3)
  630. }
  631. func usersGetByEmail(t *testing.T, ctx context.Context, db *users) {
  632. t.Run("empty email", func(t *testing.T) {
  633. _, err := db.GetByEmail(ctx, "")
  634. wantErr := ErrUserNotExist{args: errutil.Args{"email": ""}}
  635. assert.Equal(t, wantErr, err)
  636. })
  637. t.Run("ignore organization", func(t *testing.T) {
  638. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
  639. org, err := db.Create(ctx, "gogs", "[email protected]", CreateUserOptions{})
  640. require.NoError(t, err)
  641. err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error
  642. require.NoError(t, err)
  643. _, err = db.GetByEmail(ctx, org.Email)
  644. wantErr := ErrUserNotExist{args: errutil.Args{"email": org.Email}}
  645. assert.Equal(t, wantErr, err)
  646. })
  647. t.Run("by primary email", func(t *testing.T) {
  648. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  649. require.NoError(t, err)
  650. _, err = db.GetByEmail(ctx, alice.Email)
  651. wantErr := ErrUserNotExist{args: errutil.Args{"email": alice.Email}}
  652. assert.Equal(t, wantErr, err)
  653. // Mark user as activated
  654. // TODO: Use UserEmails.Verify to replace SQL hack when the method is available.
  655. err = db.Model(&User{}).Where("id", alice.ID).UpdateColumn("is_active", true).Error
  656. require.NoError(t, err)
  657. user, err := db.GetByEmail(ctx, alice.Email)
  658. require.NoError(t, err)
  659. assert.Equal(t, alice.Name, user.Name)
  660. })
  661. t.Run("by secondary email", func(t *testing.T) {
  662. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  663. require.NoError(t, err)
  664. // TODO: Use UserEmails.Create to replace SQL hack when the method is available.
  665. email2 := "[email protected]"
  666. err = db.Exec(`INSERT INTO email_address (uid, email) VALUES (?, ?)`, bob.ID, email2).Error
  667. require.NoError(t, err)
  668. _, err = db.GetByEmail(ctx, email2)
  669. wantErr := ErrUserNotExist{args: errutil.Args{"email": email2}}
  670. assert.Equal(t, wantErr, err)
  671. // TODO: Use UserEmails.Verify to replace SQL hack when the method is available.
  672. err = db.Exec(`UPDATE email_address SET is_activated = ? WHERE email = ?`, true, email2).Error
  673. require.NoError(t, err)
  674. user, err := db.GetByEmail(ctx, email2)
  675. require.NoError(t, err)
  676. assert.Equal(t, bob.Name, user.Name)
  677. })
  678. }
  679. func usersGetByID(t *testing.T, ctx context.Context, db *users) {
  680. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  681. require.NoError(t, err)
  682. user, err := db.GetByID(ctx, alice.ID)
  683. require.NoError(t, err)
  684. assert.Equal(t, alice.Name, user.Name)
  685. _, err = db.GetByID(ctx, 404)
  686. wantErr := ErrUserNotExist{args: errutil.Args{"userID": int64(404)}}
  687. assert.Equal(t, wantErr, err)
  688. }
  689. func usersGetByUsername(t *testing.T, ctx context.Context, db *users) {
  690. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  691. require.NoError(t, err)
  692. user, err := db.GetByUsername(ctx, alice.Name)
  693. require.NoError(t, err)
  694. assert.Equal(t, alice.Name, user.Name)
  695. _, err = db.GetByUsername(ctx, "bad_username")
  696. wantErr := ErrUserNotExist{args: errutil.Args{"name": "bad_username"}}
  697. assert.Equal(t, wantErr, err)
  698. }
  699. func usersGetByKeyID(t *testing.T, ctx context.Context, db *users) {
  700. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  701. require.NoError(t, err)
  702. // TODO: Use PublicKeys.Create to replace SQL hack when the method is available.
  703. publicKey := &PublicKey{
  704. OwnerID: alice.ID,
  705. Name: "test-key",
  706. Fingerprint: "12:f8:7e:78:61:b4:bf:e2:de:24:15:96:4e:d4:72:53",
  707. Content: "test-key-content",
  708. CreatedUnix: db.NowFunc().Unix(),
  709. UpdatedUnix: db.NowFunc().Unix(),
  710. }
  711. err = db.WithContext(ctx).Create(publicKey).Error
  712. require.NoError(t, err)
  713. user, err := db.GetByKeyID(ctx, publicKey.ID)
  714. require.NoError(t, err)
  715. assert.Equal(t, alice.Name, user.Name)
  716. _, err = db.GetByKeyID(ctx, publicKey.ID+1)
  717. wantErr := ErrUserNotExist{args: errutil.Args{"keyID": publicKey.ID + 1}}
  718. assert.Equal(t, wantErr, err)
  719. }
  720. func usersGetMailableEmailsByUsernames(t *testing.T, ctx context.Context, db *users) {
  721. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  722. require.NoError(t, err)
  723. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{Activated: true})
  724. require.NoError(t, err)
  725. _, err = db.Create(ctx, "cindy", "[email protected]", CreateUserOptions{Activated: true})
  726. require.NoError(t, err)
  727. got, err := db.GetMailableEmailsByUsernames(ctx, []string{alice.Name, bob.Name, "ignore-non-exist"})
  728. require.NoError(t, err)
  729. want := []string{bob.Email}
  730. assert.Equal(t, want, got)
  731. }
  732. func usersIsUsernameUsed(t *testing.T, ctx context.Context, db *users) {
  733. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  734. require.NoError(t, err)
  735. tests := []struct {
  736. name string
  737. username string
  738. excludeUserID int64
  739. want bool
  740. }{
  741. {
  742. name: "no change",
  743. username: alice.Name,
  744. excludeUserID: alice.ID,
  745. want: false,
  746. },
  747. {
  748. name: "change case",
  749. username: strings.ToUpper(alice.Name),
  750. excludeUserID: alice.ID,
  751. want: false,
  752. },
  753. {
  754. name: "not used",
  755. username: "bob",
  756. excludeUserID: alice.ID,
  757. want: false,
  758. },
  759. {
  760. name: "not used when not excluded",
  761. username: "bob",
  762. excludeUserID: 0,
  763. want: false,
  764. },
  765. {
  766. name: "used when not excluded",
  767. username: alice.Name,
  768. excludeUserID: 0,
  769. want: true,
  770. },
  771. }
  772. for _, test := range tests {
  773. t.Run(test.name, func(t *testing.T) {
  774. got := db.IsUsernameUsed(ctx, test.username, test.excludeUserID)
  775. assert.Equal(t, test.want, got)
  776. })
  777. }
  778. }
  779. func usersList(t *testing.T, ctx context.Context, db *users) {
  780. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  781. require.NoError(t, err)
  782. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  783. require.NoError(t, err)
  784. // Create an organization shouldn't count
  785. // TODO: Use Orgs.Create to replace SQL hack when the method is available.
  786. org1, err := db.Create(ctx, "org1", "[email protected]", CreateUserOptions{})
  787. require.NoError(t, err)
  788. err = db.Exec(
  789. dbutil.Quote("UPDATE %s SET type = ? WHERE id = ?", "user"),
  790. UserTypeOrganization, org1.ID,
  791. ).Error
  792. require.NoError(t, err)
  793. got, err := db.List(ctx, 1, 1)
  794. require.NoError(t, err)
  795. require.Len(t, got, 1)
  796. assert.Equal(t, alice.ID, got[0].ID)
  797. got, err = db.List(ctx, 2, 1)
  798. require.NoError(t, err)
  799. require.Len(t, got, 1)
  800. assert.Equal(t, bob.ID, got[0].ID)
  801. got, err = db.List(ctx, 1, 3)
  802. require.NoError(t, err)
  803. require.Len(t, got, 2)
  804. assert.Equal(t, alice.ID, got[0].ID)
  805. assert.Equal(t, bob.ID, got[1].ID)
  806. }
  807. func usersListFollowers(t *testing.T, ctx context.Context, db *users) {
  808. john, err := db.Create(ctx, "john", "[email protected]", CreateUserOptions{})
  809. require.NoError(t, err)
  810. got, err := db.ListFollowers(ctx, john.ID, 1, 1)
  811. require.NoError(t, err)
  812. assert.Empty(t, got)
  813. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  814. require.NoError(t, err)
  815. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  816. require.NoError(t, err)
  817. err = db.Follow(ctx, alice.ID, john.ID)
  818. require.NoError(t, err)
  819. err = db.Follow(ctx, bob.ID, john.ID)
  820. require.NoError(t, err)
  821. // First page only has bob
  822. got, err = db.ListFollowers(ctx, john.ID, 1, 1)
  823. require.NoError(t, err)
  824. require.Len(t, got, 1)
  825. assert.Equal(t, bob.ID, got[0].ID)
  826. // Second page only has alice
  827. got, err = db.ListFollowers(ctx, john.ID, 2, 1)
  828. require.NoError(t, err)
  829. require.Len(t, got, 1)
  830. assert.Equal(t, alice.ID, got[0].ID)
  831. }
  832. func usersListFollowings(t *testing.T, ctx context.Context, db *users) {
  833. john, err := db.Create(ctx, "john", "[email protected]", CreateUserOptions{})
  834. require.NoError(t, err)
  835. got, err := db.ListFollowers(ctx, john.ID, 1, 1)
  836. require.NoError(t, err)
  837. assert.Empty(t, got)
  838. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  839. require.NoError(t, err)
  840. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  841. require.NoError(t, err)
  842. err = db.Follow(ctx, john.ID, alice.ID)
  843. require.NoError(t, err)
  844. err = db.Follow(ctx, john.ID, bob.ID)
  845. require.NoError(t, err)
  846. // First page only has bob
  847. got, err = db.ListFollowings(ctx, john.ID, 1, 1)
  848. require.NoError(t, err)
  849. require.Len(t, got, 1)
  850. assert.Equal(t, bob.ID, got[0].ID)
  851. // Second page only has alice
  852. got, err = db.ListFollowings(ctx, john.ID, 2, 1)
  853. require.NoError(t, err)
  854. require.Len(t, got, 1)
  855. assert.Equal(t, alice.ID, got[0].ID)
  856. }
  857. func usersSearchByName(t *testing.T, ctx context.Context, db *users) {
  858. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{FullName: "Alice Jordan"})
  859. require.NoError(t, err)
  860. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{FullName: "Bob Jordan"})
  861. require.NoError(t, err)
  862. t.Run("search for username alice", func(t *testing.T) {
  863. users, count, err := db.SearchByName(ctx, "Li", 1, 1, "")
  864. require.NoError(t, err)
  865. require.Len(t, users, int(count))
  866. assert.Equal(t, int64(1), count)
  867. assert.Equal(t, alice.ID, users[0].ID)
  868. })
  869. t.Run("search for username bob", func(t *testing.T) {
  870. users, count, err := db.SearchByName(ctx, "oB", 1, 1, "")
  871. require.NoError(t, err)
  872. require.Len(t, users, int(count))
  873. assert.Equal(t, int64(1), count)
  874. assert.Equal(t, bob.ID, users[0].ID)
  875. })
  876. t.Run("search for full name jordan", func(t *testing.T) {
  877. users, count, err := db.SearchByName(ctx, "Jo", 1, 10, "")
  878. require.NoError(t, err)
  879. require.Len(t, users, int(count))
  880. assert.Equal(t, int64(2), count)
  881. })
  882. t.Run("search for full name jordan ORDER BY id DESC LIMIT 1", func(t *testing.T) {
  883. users, count, err := db.SearchByName(ctx, "Jo", 1, 1, "id DESC")
  884. require.NoError(t, err)
  885. require.Len(t, users, 1)
  886. assert.Equal(t, int64(2), count)
  887. assert.Equal(t, bob.ID, users[0].ID)
  888. })
  889. }
  890. func usersUpdate(t *testing.T, ctx context.Context, db *users) {
  891. const oldPassword = "Password"
  892. alice, err := db.Create(
  893. ctx,
  894. "alice",
  895. "[email protected]",
  896. CreateUserOptions{
  897. FullName: "FullName",
  898. Password: oldPassword,
  899. LoginSource: 9,
  900. LoginName: "LoginName",
  901. Location: "Location",
  902. Website: "Website",
  903. Activated: false,
  904. Admin: false,
  905. },
  906. )
  907. require.NoError(t, err)
  908. t.Run("update password", func(t *testing.T) {
  909. got := userutil.ValidatePassword(alice.Password, alice.Salt, oldPassword)
  910. require.True(t, got)
  911. newPassword := "NewPassword"
  912. err = db.Update(ctx, alice.ID, UpdateUserOptions{Password: &newPassword})
  913. require.NoError(t, err)
  914. alice, err = db.GetByID(ctx, alice.ID)
  915. require.NoError(t, err)
  916. got = userutil.ValidatePassword(alice.Password, alice.Salt, oldPassword)
  917. assert.False(t, got, "Old password should stop working")
  918. got = userutil.ValidatePassword(alice.Password, alice.Salt, newPassword)
  919. assert.True(t, got, "New password should work")
  920. })
  921. t.Run("update email but already used", func(t *testing.T) {
  922. bob, err := db.Create(
  923. ctx,
  924. "bob",
  925. "[email protected]",
  926. CreateUserOptions{
  927. Activated: true,
  928. },
  929. )
  930. require.NoError(t, err)
  931. got := db.Update(ctx, alice.ID, UpdateUserOptions{Email: &bob.Email})
  932. want := ErrEmailAlreadyUsed{args: errutil.Args{"email": bob.Email}}
  933. assert.Equal(t, want, got)
  934. })
  935. loginSource := int64(1)
  936. maxRepoCreation := 99
  937. lastRepoVisibility := true
  938. overLimitStr := strings.Repeat("a", 2050)
  939. opts := UpdateUserOptions{
  940. LoginSource: &loginSource,
  941. LoginName: &alice.Name,
  942. FullName: &overLimitStr,
  943. Website: &overLimitStr,
  944. Location: &overLimitStr,
  945. Description: &overLimitStr,
  946. MaxRepoCreation: &maxRepoCreation,
  947. LastRepoVisibility: &lastRepoVisibility,
  948. IsActivated: &lastRepoVisibility,
  949. IsAdmin: &lastRepoVisibility,
  950. AllowGitHook: &lastRepoVisibility,
  951. AllowImportLocal: &lastRepoVisibility,
  952. ProhibitLogin: &lastRepoVisibility,
  953. Avatar: &overLimitStr,
  954. AvatarEmail: &overLimitStr,
  955. }
  956. err = db.Update(ctx, alice.ID, opts)
  957. require.NoError(t, err)
  958. alice, err = db.GetByID(ctx, alice.ID)
  959. require.NoError(t, err)
  960. assertValues := func() {
  961. assert.Equal(t, loginSource, alice.LoginSource)
  962. assert.Equal(t, alice.Name, alice.LoginName)
  963. wantStr255 := strings.Repeat("a", 255)
  964. assert.Equal(t, wantStr255, alice.FullName)
  965. assert.Equal(t, wantStr255, alice.Website)
  966. assert.Equal(t, wantStr255, alice.Location)
  967. assert.Equal(t, wantStr255, alice.Description)
  968. assert.Equal(t, maxRepoCreation, alice.MaxRepoCreation)
  969. assert.Equal(t, lastRepoVisibility, alice.LastRepoVisibility)
  970. assert.Equal(t, lastRepoVisibility, alice.IsActive)
  971. assert.Equal(t, lastRepoVisibility, alice.IsAdmin)
  972. assert.Equal(t, lastRepoVisibility, alice.AllowGitHook)
  973. assert.Equal(t, lastRepoVisibility, alice.AllowImportLocal)
  974. assert.Equal(t, lastRepoVisibility, alice.ProhibitLogin)
  975. wantStr2048 := strings.Repeat("a", 2048)
  976. assert.Equal(t, wantStr2048, alice.Avatar)
  977. assert.Equal(t, wantStr255, alice.AvatarEmail)
  978. }
  979. assertValues()
  980. // Test ignored values
  981. err = db.Update(ctx, alice.ID, UpdateUserOptions{})
  982. require.NoError(t, err)
  983. alice, err = db.GetByID(ctx, alice.ID)
  984. require.NoError(t, err)
  985. assertValues()
  986. }
  987. func usersUseCustomAvatar(t *testing.T, ctx context.Context, db *users) {
  988. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  989. require.NoError(t, err)
  990. avatar, err := public.Files.ReadFile("img/avatar_default.png")
  991. require.NoError(t, err)
  992. avatarPath := userutil.CustomAvatarPath(alice.ID)
  993. _ = os.Remove(avatarPath)
  994. defer func() { _ = os.Remove(avatarPath) }()
  995. err = db.UseCustomAvatar(ctx, alice.ID, avatar)
  996. require.NoError(t, err)
  997. // Make sure avatar is saved and the user flag is updated.
  998. got := osutil.IsFile(avatarPath)
  999. assert.True(t, got)
  1000. alice, err = db.GetByID(ctx, alice.ID)
  1001. require.NoError(t, err)
  1002. assert.True(t, alice.UseCustomAvatar)
  1003. }
  1004. func TestIsUsernameAllowed(t *testing.T) {
  1005. for name := range reservedUsernames {
  1006. t.Run(name, func(t *testing.T) {
  1007. assert.True(t, IsErrNameNotAllowed(isUsernameAllowed(name)))
  1008. })
  1009. }
  1010. for _, pattern := range reservedUsernamePatterns {
  1011. t.Run(pattern, func(t *testing.T) {
  1012. username := strings.ReplaceAll(pattern, "*", "alice")
  1013. assert.True(t, IsErrNameNotAllowed(isUsernameAllowed(username)))
  1014. })
  1015. }
  1016. }
  1017. func usersAddEmail(t *testing.T, ctx context.Context, db *users) {
  1018. t.Run("multiple users can add the same unverified email", func(t *testing.T) {
  1019. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1020. require.NoError(t, err)
  1021. err = db.AddEmail(ctx, alice.ID+1, "[email protected]", false)
  1022. require.NoError(t, err)
  1023. })
  1024. t.Run("only one user can add the same verified email", func(t *testing.T) {
  1025. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{Activated: true})
  1026. require.NoError(t, err)
  1027. got := db.AddEmail(ctx, bob.ID+1, "[email protected]", true)
  1028. want := ErrEmailAlreadyUsed{args: errutil.Args{"email": "[email protected]"}}
  1029. require.Equal(t, want, got)
  1030. })
  1031. }
  1032. func usersGetEmail(t *testing.T, ctx context.Context, db *users) {
  1033. const testUserID = 1
  1034. const testEmail = "[email protected]"
  1035. _, err := db.GetEmail(ctx, testUserID, testEmail, false)
  1036. wantErr := ErrEmailNotExist{
  1037. args: errutil.Args{
  1038. "email": testEmail,
  1039. },
  1040. }
  1041. assert.Equal(t, wantErr, err)
  1042. err = db.AddEmail(ctx, testUserID, testEmail, false)
  1043. require.NoError(t, err)
  1044. got, err := db.GetEmail(ctx, testUserID, testEmail, false)
  1045. require.NoError(t, err)
  1046. assert.Equal(t, testEmail, got.Email)
  1047. // Should not return if we ask for a different user
  1048. _, err = db.GetEmail(ctx, testUserID+1, testEmail, false)
  1049. assert.Equal(t, wantErr, err)
  1050. // Should not return if we only want activated emails
  1051. _, err = db.GetEmail(ctx, testUserID, testEmail, true)
  1052. assert.Equal(t, wantErr, err)
  1053. err = db.MarkEmailActivated(ctx, testUserID, testEmail)
  1054. require.NoError(t, err)
  1055. got, err = db.GetEmail(ctx, testUserID, testEmail, true)
  1056. require.NoError(t, err)
  1057. assert.Equal(t, testEmail, got.Email)
  1058. }
  1059. func usersListEmails(t *testing.T, ctx context.Context, db *users) {
  1060. t.Run("list emails with primary email", func(t *testing.T) {
  1061. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1062. require.NoError(t, err)
  1063. err = db.AddEmail(ctx, alice.ID, "[email protected]", true)
  1064. require.NoError(t, err)
  1065. err = db.MarkEmailPrimary(ctx, alice.ID, "[email protected]")
  1066. require.NoError(t, err)
  1067. emails, err := db.ListEmails(ctx, alice.ID)
  1068. require.NoError(t, err)
  1069. got := make([]string, 0, len(emails))
  1070. for _, email := range emails {
  1071. got = append(got, email.Email)
  1072. }
  1073. want := []string{"[email protected]", "[email protected]"}
  1074. assert.Equal(t, want, got)
  1075. })
  1076. t.Run("list emails without primary email", func(t *testing.T) {
  1077. bob, err := db.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  1078. require.NoError(t, err)
  1079. err = db.AddEmail(ctx, bob.ID, "[email protected]", false)
  1080. require.NoError(t, err)
  1081. emails, err := db.ListEmails(ctx, bob.ID)
  1082. require.NoError(t, err)
  1083. got := make([]string, 0, len(emails))
  1084. for _, email := range emails {
  1085. got = append(got, email.Email)
  1086. }
  1087. want := []string{"[email protected]", "[email protected]"}
  1088. assert.Equal(t, want, got)
  1089. })
  1090. }
  1091. func usersMarkEmailActivated(t *testing.T, ctx context.Context, db *users) {
  1092. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1093. require.NoError(t, err)
  1094. err = db.AddEmail(ctx, alice.ID, "[email protected]", false)
  1095. require.NoError(t, err)
  1096. err = db.MarkEmailActivated(ctx, alice.ID, "[email protected]")
  1097. require.NoError(t, err)
  1098. gotEmail, err := db.GetEmail(ctx, alice.ID, "[email protected]", true)
  1099. require.NoError(t, err)
  1100. assert.True(t, gotEmail.IsActivated)
  1101. gotAlice, err := db.GetByID(ctx, alice.ID)
  1102. require.NoError(t, err)
  1103. assert.NotEqual(t, alice.Rands, gotAlice.Rands)
  1104. }
  1105. func usersMarkEmailPrimary(t *testing.T, ctx context.Context, db *users) {
  1106. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1107. require.NoError(t, err)
  1108. err = db.AddEmail(ctx, alice.ID, "[email protected]", false)
  1109. require.NoError(t, err)
  1110. // Should fail because email not verified
  1111. gotError := db.MarkEmailPrimary(ctx, alice.ID, "[email protected]")
  1112. wantError := ErrEmailNotVerified{args: errutil.Args{"email": "[email protected]"}}
  1113. assert.Equal(t, wantError, gotError)
  1114. // Mark email as verified and should succeed
  1115. err = db.MarkEmailActivated(ctx, alice.ID, "[email protected]")
  1116. require.NoError(t, err)
  1117. err = db.MarkEmailPrimary(ctx, alice.ID, "[email protected]")
  1118. require.NoError(t, err)
  1119. gotAlice, err := db.GetByID(ctx, alice.ID)
  1120. require.NoError(t, err)
  1121. assert.Equal(t, "[email protected]", gotAlice.Email)
  1122. // Former primary email should be preserved
  1123. gotEmail, err := db.GetEmail(ctx, alice.ID, "[email protected]", false)
  1124. require.NoError(t, err)
  1125. assert.False(t, gotEmail.IsActivated)
  1126. }
  1127. func usersDeleteEmail(t *testing.T, ctx context.Context, db *users) {
  1128. alice, err := db.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1129. require.NoError(t, err)
  1130. err = db.AddEmail(ctx, alice.ID, "[email protected]", false)
  1131. require.NoError(t, err)
  1132. _, err = db.GetEmail(ctx, alice.ID, "[email protected]", false)
  1133. require.NoError(t, err)
  1134. err = db.DeleteEmail(ctx, alice.ID, "[email protected]")
  1135. require.NoError(t, err)
  1136. _, got := db.GetEmail(ctx, alice.ID, "[email protected]", false)
  1137. want := ErrEmailNotExist{args: errutil.Args{"email": "[email protected]"}}
  1138. require.Equal(t, want, got)
  1139. }
  1140. func usersFollow(t *testing.T, ctx context.Context, db *users) {
  1141. usersStore := NewUsersStore(db.DB)
  1142. alice, err := usersStore.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1143. require.NoError(t, err)
  1144. bob, err := usersStore.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  1145. require.NoError(t, err)
  1146. err = db.Follow(ctx, alice.ID, bob.ID)
  1147. require.NoError(t, err)
  1148. // It is OK to follow multiple times and just be noop.
  1149. err = db.Follow(ctx, alice.ID, bob.ID)
  1150. require.NoError(t, err)
  1151. alice, err = usersStore.GetByID(ctx, alice.ID)
  1152. require.NoError(t, err)
  1153. assert.Equal(t, 1, alice.NumFollowing)
  1154. bob, err = usersStore.GetByID(ctx, bob.ID)
  1155. require.NoError(t, err)
  1156. assert.Equal(t, 1, bob.NumFollowers)
  1157. }
  1158. func usersIsFollowing(t *testing.T, ctx context.Context, db *users) {
  1159. usersStore := NewUsersStore(db.DB)
  1160. alice, err := usersStore.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1161. require.NoError(t, err)
  1162. bob, err := usersStore.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  1163. require.NoError(t, err)
  1164. got := db.IsFollowing(ctx, alice.ID, bob.ID)
  1165. assert.False(t, got)
  1166. err = db.Follow(ctx, alice.ID, bob.ID)
  1167. require.NoError(t, err)
  1168. got = db.IsFollowing(ctx, alice.ID, bob.ID)
  1169. assert.True(t, got)
  1170. err = db.Unfollow(ctx, alice.ID, bob.ID)
  1171. require.NoError(t, err)
  1172. got = db.IsFollowing(ctx, alice.ID, bob.ID)
  1173. assert.False(t, got)
  1174. }
  1175. func usersUnfollow(t *testing.T, ctx context.Context, db *users) {
  1176. usersStore := NewUsersStore(db.DB)
  1177. alice, err := usersStore.Create(ctx, "alice", "[email protected]", CreateUserOptions{})
  1178. require.NoError(t, err)
  1179. bob, err := usersStore.Create(ctx, "bob", "[email protected]", CreateUserOptions{})
  1180. require.NoError(t, err)
  1181. err = db.Follow(ctx, alice.ID, bob.ID)
  1182. require.NoError(t, err)
  1183. // It is OK to unfollow multiple times and just be noop.
  1184. err = db.Unfollow(ctx, alice.ID, bob.ID)
  1185. require.NoError(t, err)
  1186. err = db.Unfollow(ctx, alice.ID, bob.ID)
  1187. require.NoError(t, err)
  1188. alice, err = usersStore.GetByID(ctx, alice.ID)
  1189. require.NoError(t, err)
  1190. assert.Equal(t, 0, alice.NumFollowing)
  1191. bob, err = usersStore.GetByID(ctx, bob.ID)
  1192. require.NoError(t, err)
  1193. assert.Equal(t, 0, bob.NumFollowers)
  1194. }