users_test.go 38 KB

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