users_test.go 43 KB

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