users_test.go 44 KB

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