users_test.go 38 KB

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