users_test.go 36 KB

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