users_test.go 43 KB

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