updateSelfInfoLogic_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package auth
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "strings"
  7. "testing"
  8. "time"
  9. "perms-system-server/internal/loaders"
  10. "perms-system-server/internal/middleware"
  11. userModel "perms-system-server/internal/model/user"
  12. "perms-system-server/internal/response"
  13. "perms-system-server/internal/svc"
  14. "perms-system-server/internal/testutil"
  15. "perms-system-server/internal/types"
  16. "github.com/stretchr/testify/assert"
  17. "github.com/stretchr/testify/require"
  18. )
  19. func ptrStr(s string) *string { return &s }
  20. func insertTestUserForUpdate(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, username, password string) (int64, func()) {
  21. t.Helper()
  22. conn := testutil.GetTestSqlConn()
  23. now := time.Now().Unix()
  24. hashed := testutil.HashPassword(password)
  25. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  26. Username: username,
  27. Password: hashed,
  28. Nickname: "old_nick",
  29. Avatar: sql.NullString{String: "old_avatar.png", Valid: true},
  30. Email: "[email protected]",
  31. Phone: "13800000000",
  32. Remark: "",
  33. DeptId: 0,
  34. IsSuperAdmin: 2,
  35. MustChangePassword: 2,
  36. Status: 1,
  37. CreateTime: now,
  38. UpdateTime: now,
  39. })
  40. require.NoError(t, err)
  41. id, _ := res.LastInsertId()
  42. cleanup := func() {
  43. testutil.CleanTable(ctx, conn, "`sys_user`", id)
  44. }
  45. return id, cleanup
  46. }
  47. // TC-1230: 未登录
  48. func TestUpdateSelfInfo_NotLoggedIn(t *testing.T) {
  49. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  50. logic := NewUpdateSelfInfoLogic(context.Background(), svcCtx)
  51. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Nickname: ptrStr("new")})
  52. require.Error(t, err)
  53. var codeErr *response.CodeError
  54. require.True(t, errors.As(err, &codeErr))
  55. assert.Equal(t, 401, codeErr.Code())
  56. assert.Contains(t, codeErr.Error(), "未登录")
  57. }
  58. // TC-1231: 所有字段为 nil
  59. func TestUpdateSelfInfo_AllFieldsNil(t *testing.T) {
  60. ctx := context.Background()
  61. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  62. username := testutil.UniqueId()
  63. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  64. t.Cleanup(cleanUser)
  65. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  66. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  67. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{})
  68. require.Error(t, err)
  69. var codeErr *response.CodeError
  70. require.True(t, errors.As(err, &codeErr))
  71. assert.Equal(t, 400, codeErr.Code())
  72. assert.Contains(t, codeErr.Error(), "至少需要修改一个字段")
  73. }
  74. // TC-1232: 正常更新 nickname
  75. func TestUpdateSelfInfo_UpdateNickname(t *testing.T) {
  76. ctx := context.Background()
  77. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  78. username := testutil.UniqueId()
  79. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  80. t.Cleanup(cleanUser)
  81. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  82. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  83. newNick := "new_nickname"
  84. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Nickname: &newNick})
  85. require.NoError(t, err)
  86. user, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  87. require.NoError(t, err)
  88. assert.Equal(t, newNick, user.Nickname)
  89. }
  90. // TC-1233: 正常更新 avatar
  91. func TestUpdateSelfInfo_UpdateAvatar(t *testing.T) {
  92. ctx := context.Background()
  93. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  94. username := testutil.UniqueId()
  95. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  96. t.Cleanup(cleanUser)
  97. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  98. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  99. newAvatar := "https://example.com/new_avatar.png"
  100. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Avatar: &newAvatar})
  101. require.NoError(t, err)
  102. user, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  103. require.NoError(t, err)
  104. assert.Equal(t, newAvatar, user.Avatar.String)
  105. }
  106. // TC-1234: 正常更新 email + phone
  107. func TestUpdateSelfInfo_UpdateEmailAndPhone(t *testing.T) {
  108. ctx := context.Background()
  109. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  110. username := testutil.UniqueId()
  111. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  112. t.Cleanup(cleanUser)
  113. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  114. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  115. newEmail := "[email protected]"
  116. newPhone := "13900000000"
  117. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Email: &newEmail, Phone: &newPhone})
  118. require.NoError(t, err)
  119. user, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  120. require.NoError(t, err)
  121. assert.Equal(t, newEmail, user.Email)
  122. assert.Equal(t, newPhone, user.Phone)
  123. }
  124. // TC-1235: nickname 超过 64 字符
  125. func TestUpdateSelfInfo_NicknameTooLong(t *testing.T) {
  126. ctx := context.Background()
  127. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  128. username := testutil.UniqueId()
  129. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  130. t.Cleanup(cleanUser)
  131. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  132. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  133. longNick := strings.Repeat("x", 65)
  134. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Nickname: &longNick})
  135. require.Error(t, err)
  136. var codeErr *response.CodeError
  137. require.True(t, errors.As(err, &codeErr))
  138. assert.Equal(t, 400, codeErr.Code())
  139. assert.Contains(t, codeErr.Error(), "昵称长度不能超过64个字符")
  140. }
  141. // TC-1236: avatar 超过 255 字符
  142. func TestUpdateSelfInfo_AvatarTooLong(t *testing.T) {
  143. ctx := context.Background()
  144. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  145. username := testutil.UniqueId()
  146. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  147. t.Cleanup(cleanUser)
  148. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  149. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  150. longAvatar := strings.Repeat("a", 256)
  151. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Avatar: &longAvatar})
  152. require.Error(t, err)
  153. var codeErr *response.CodeError
  154. require.True(t, errors.As(err, &codeErr))
  155. assert.Equal(t, 400, codeErr.Code())
  156. assert.Contains(t, codeErr.Error(), "头像地址长度不能超过255个字符")
  157. }
  158. // TC-1237: email 超过 64 字符
  159. func TestUpdateSelfInfo_EmailTooLong(t *testing.T) {
  160. ctx := context.Background()
  161. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  162. username := testutil.UniqueId()
  163. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  164. t.Cleanup(cleanUser)
  165. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  166. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  167. longEmail := strings.Repeat("e", 65)
  168. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Email: &longEmail})
  169. require.Error(t, err)
  170. var codeErr *response.CodeError
  171. require.True(t, errors.As(err, &codeErr))
  172. assert.Equal(t, 400, codeErr.Code())
  173. assert.Contains(t, codeErr.Error(), "邮箱长度不能超过64个字符")
  174. }
  175. // TC-1238: phone 超过 32 字符
  176. func TestUpdateSelfInfo_PhoneTooLong(t *testing.T) {
  177. ctx := context.Background()
  178. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  179. username := testutil.UniqueId()
  180. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  181. t.Cleanup(cleanUser)
  182. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  183. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  184. longPhone := strings.Repeat("1", 33)
  185. err := logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Phone: &longPhone})
  186. require.Error(t, err)
  187. var codeErr *response.CodeError
  188. require.True(t, errors.As(err, &codeErr))
  189. assert.Equal(t, 400, codeErr.Code())
  190. assert.Contains(t, codeErr.Error(), "手机号长度不能超过32个字符")
  191. }
  192. // TC-1239: 并发更新冲突
  193. func TestUpdateSelfInfo_ConcurrentConflict(t *testing.T) {
  194. ctx := context.Background()
  195. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  196. conn := testutil.GetTestSqlConn()
  197. username := testutil.UniqueId()
  198. // 使用过去的 updateTime(1 秒前),确保 UpdateSelfInfo 写入的 now > expectedUpdateTime
  199. pastTime := time.Now().Unix() - 1
  200. hashed := testutil.HashPassword("Pass123456")
  201. res, err := conn.ExecCtx(ctx,
  202. "INSERT INTO `sys_user` (`username`,`password`,`nickname`,`avatar`,`email`,`phone`,`remark`,`deptId`,`isSuperAdmin`,`mustChangePassword`,`status`,`tokenVersion`,`createTime`,`updateTime`) VALUES (?,?,?,NULL,?,?,?,?,?,?,?,?,?,?)",
  203. username, hashed, "old_nick", "[email protected]", "13800000000", "", 0, 2, 2, 1, 0, pastTime, pastTime)
  204. require.NoError(t, err)
  205. userId, _ := res.LastInsertId()
  206. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  207. // session A 读取快照(updateTime=pastTime)
  208. snapshotUpdateTime := pastTime
  209. // session A 先提交(成功,因为 WHERE updateTime=pastTime 匹配)
  210. err = svcCtx.SysUserModel.UpdateSelfInfo(ctx, userId, username, "sessionA", "", "[email protected]", "13800000000", snapshotUpdateTime)
  211. require.NoError(t, err)
  212. // session B 用相同旧 updateTime 提交(冲突,因为 session A 已将 updateTime 推进到 now)
  213. err = svcCtx.SysUserModel.UpdateSelfInfo(ctx, userId, username, "sessionB", "", "[email protected]", "13800000000", snapshotUpdateTime)
  214. require.Error(t, err)
  215. assert.True(t, errors.Is(err, userModel.ErrUpdateConflict))
  216. }
  217. // TC-1240: 更新后 UserDetails 缓存失效
  218. func TestUpdateSelfInfo_CacheInvalidation(t *testing.T) {
  219. ctx := context.Background()
  220. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  221. username := testutil.UniqueId()
  222. userId, cleanUser := insertTestUserForUpdate(t, ctx, svcCtx, username, "Pass123456")
  223. t.Cleanup(cleanUser)
  224. // 预热缓存
  225. ud, err := svcCtx.UserDetailsLoader.Load(ctx, userId, "")
  226. require.NoError(t, err)
  227. assert.Equal(t, "old_nick", ud.Nickname)
  228. // 执行更新
  229. logicCtx := middleware.WithUserDetails(ctx, &loaders.UserDetails{UserId: userId})
  230. logic := NewUpdateSelfInfoLogic(logicCtx, svcCtx)
  231. newNick := "cache_test_nick"
  232. err = logic.UpdateSelfInfo(&types.UpdateSelfInfoReq{Nickname: &newNick})
  233. require.NoError(t, err)
  234. // 再次 Load 应读到新值
  235. ud2, err := svcCtx.UserDetailsLoader.Load(ctx, userId, "")
  236. require.NoError(t, err)
  237. assert.Equal(t, newNick, ud2.Nickname)
  238. }