users_test.go 43 KB

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