users_test.go 44 KB

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