users_test.go 43 KB

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