credentialsLogic_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package user
  2. import (
  3. "errors"
  4. "sync"
  5. "sync/atomic"
  6. "testing"
  7. "perms-system-server/internal/consts"
  8. userModel "perms-system-server/internal/model/user"
  9. "perms-system-server/internal/response"
  10. "perms-system-server/internal/svc"
  11. "perms-system-server/internal/testutil"
  12. "perms-system-server/internal/testutil/ctxhelper"
  13. "perms-system-server/internal/types"
  14. "perms-system-server/internal/util"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. )
  18. // TC-1285 超管重置普通用户密码
  19. func TestResetPassword_SuperAdmin(t *testing.T) {
  20. ctx := ctxhelper.SuperAdminCtx()
  21. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  22. conn := testutil.GetTestSqlConn()
  23. username := testutil.UniqueId()
  24. hashed := testutil.HashPassword("OldPass123")
  25. userId := insertTestUser(t, ctx, username, hashed)
  26. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  27. logic := NewResetPasswordLogic(ctx, svcCtx)
  28. resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
  29. require.NoError(t, err)
  30. require.NotNil(t, resp)
  31. assert.NotEmpty(t, resp.CredentialsTicket)
  32. assert.Greater(t, resp.CredentialsExpiresAt, int64(0))
  33. // 清理 Redis ticket
  34. t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
  35. }
  36. // TC-1287 不能重置超管密码
  37. func TestResetPassword_CannotResetSuperAdmin(t *testing.T) {
  38. ctx := ctxhelper.SuperAdminCtxWithUserId(999)
  39. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  40. conn := testutil.GetTestSqlConn()
  41. username := testutil.UniqueId()
  42. hashed := testutil.HashPassword("Pass123456")
  43. superUserId := insertTestUserFull(t, ctx, &userModel.SysUser{
  44. Username: username,
  45. Password: hashed,
  46. IsSuperAdmin: consts.IsSuperAdminYes,
  47. Status: consts.StatusEnabled,
  48. MustChangePassword: consts.MustChangePasswordNo,
  49. })
  50. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", superUserId) })
  51. logic := NewResetPasswordLogic(ctx, svcCtx)
  52. _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: superUserId})
  53. require.Error(t, err)
  54. var codeErr *response.CodeError
  55. require.True(t, errors.As(err, &codeErr))
  56. assert.Equal(t, 403, codeErr.Code())
  57. assert.Contains(t, codeErr.Error(), "超级管理员")
  58. }
  59. // TC-1288 MEMBER无权重置
  60. func TestResetPassword_MemberForbidden(t *testing.T) {
  61. ctx := ctxhelper.MemberCtx("test-product")
  62. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  63. logic := NewResetPasswordLogic(ctx, svcCtx)
  64. _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: 1})
  65. require.Error(t, err)
  66. var codeErr *response.CodeError
  67. require.True(t, errors.As(err, &codeErr))
  68. assert.Equal(t, 403, codeErr.Code())
  69. }
  70. // TC-1289 重置后旧token失效(tokenVersion递增)
  71. func TestResetPassword_TokenVersionIncremented(t *testing.T) {
  72. ctx := ctxhelper.SuperAdminCtx()
  73. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  74. conn := testutil.GetTestSqlConn()
  75. username := testutil.UniqueId()
  76. hashed := testutil.HashPassword("OldPass123")
  77. userId := insertTestUser(t, ctx, username, hashed)
  78. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  79. userBefore, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  80. require.NoError(t, err)
  81. logic := NewResetPasswordLogic(ctx, svcCtx)
  82. resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
  83. require.NoError(t, err)
  84. t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
  85. userAfter, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  86. require.NoError(t, err)
  87. assert.Equal(t, userBefore.TokenVersion+1, userAfter.TokenVersion)
  88. }
  89. // TC-1290 重置后mustChangePassword=1
  90. func TestResetPassword_MustChangePassword(t *testing.T) {
  91. ctx := ctxhelper.SuperAdminCtx()
  92. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  93. conn := testutil.GetTestSqlConn()
  94. username := testutil.UniqueId()
  95. hashed := testutil.HashPassword("OldPass123")
  96. userId := insertTestUserFull(t, ctx, &userModel.SysUser{
  97. Username: username,
  98. Password: hashed,
  99. IsSuperAdmin: consts.IsSuperAdminNo,
  100. Status: consts.StatusEnabled,
  101. MustChangePassword: consts.MustChangePasswordNo,
  102. })
  103. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  104. logic := NewResetPasswordLogic(ctx, svcCtx)
  105. resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
  106. require.NoError(t, err)
  107. t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
  108. user, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  109. require.NoError(t, err)
  110. assert.Equal(t, int64(consts.MustChangePasswordYes), user.MustChangePassword)
  111. }
  112. // TC-1291 目标用户不存在
  113. func TestResetPassword_UserNotFound(t *testing.T) {
  114. ctx := ctxhelper.SuperAdminCtx()
  115. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  116. logic := NewResetPasswordLogic(ctx, svcCtx)
  117. _, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: 999999999})
  118. require.Error(t, err)
  119. var codeErr *response.CodeError
  120. require.True(t, errors.As(err, &codeErr))
  121. assert.Equal(t, 404, codeErr.Code())
  122. }
  123. // TC-1293 重置后用新密码可验证
  124. func TestResetPassword_NewPasswordValid(t *testing.T) {
  125. ctx := ctxhelper.SuperAdminCtx()
  126. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  127. conn := testutil.GetTestSqlConn()
  128. username := testutil.UniqueId()
  129. hashed := testutil.HashPassword("OldPass123")
  130. userId := insertTestUser(t, ctx, username, hashed)
  131. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  132. logic := NewResetPasswordLogic(ctx, svcCtx)
  133. resp, err := logic.ResetPassword(&types.ResetPasswordReq{UserId: userId})
  134. require.NoError(t, err)
  135. t.Cleanup(func() { svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket) })
  136. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  137. cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: resp.CredentialsTicket})
  138. require.NoError(t, err)
  139. assert.Equal(t, username, cred.Username)
  140. assert.NotEmpty(t, cred.Password)
  141. msg := util.ValidatePassword(cred.Password)
  142. assert.Empty(t, msg)
  143. }
  144. // TC-1294 正常消费ticket
  145. func TestFetchUserCredentials_Success(t *testing.T) {
  146. ctx := ctxhelper.SuperAdminCtx()
  147. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  148. conn := testutil.GetTestSqlConn()
  149. username := testutil.UniqueId()
  150. createLogic := NewCreateUserLogic(ctx, svcCtx)
  151. createResp, err := createLogic.CreateUser(&types.CreateUserReq{
  152. Username: username,
  153. DeptId: 0,
  154. })
  155. require.NoError(t, err)
  156. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
  157. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  158. cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
  159. require.NoError(t, err)
  160. assert.Equal(t, username, cred.Username)
  161. assert.NotEmpty(t, cred.Password)
  162. }
  163. // TC-1295 二次消费同一ticket
  164. func TestFetchUserCredentials_ConsumedTwice(t *testing.T) {
  165. ctx := ctxhelper.SuperAdminCtx()
  166. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  167. conn := testutil.GetTestSqlConn()
  168. username := testutil.UniqueId()
  169. createLogic := NewCreateUserLogic(ctx, svcCtx)
  170. createResp, err := createLogic.CreateUser(&types.CreateUserReq{
  171. Username: username,
  172. DeptId: 0,
  173. })
  174. require.NoError(t, err)
  175. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
  176. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  177. _, err = fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
  178. require.NoError(t, err)
  179. _, err = fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
  180. require.Error(t, err)
  181. var codeErr *response.CodeError
  182. require.True(t, errors.As(err, &codeErr))
  183. assert.Equal(t, 400, codeErr.Code())
  184. assert.Contains(t, codeErr.Error(), "无效或已过期")
  185. }
  186. // TC-1296 空ticket
  187. func TestFetchUserCredentials_EmptyTicket(t *testing.T) {
  188. ctx := ctxhelper.SuperAdminCtx()
  189. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  190. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  191. _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: ""})
  192. require.Error(t, err)
  193. var codeErr *response.CodeError
  194. require.True(t, errors.As(err, &codeErr))
  195. assert.Equal(t, 400, codeErr.Code())
  196. assert.Contains(t, codeErr.Error(), "ticket 不能为空")
  197. }
  198. // TC-1297 非法ticket
  199. func TestFetchUserCredentials_InvalidTicket(t *testing.T) {
  200. ctx := ctxhelper.SuperAdminCtx()
  201. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  202. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  203. _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: "random_garbage_ticket_value"})
  204. require.Error(t, err)
  205. var codeErr *response.CodeError
  206. require.True(t, errors.As(err, &codeErr))
  207. assert.Equal(t, 400, codeErr.Code())
  208. assert.Contains(t, codeErr.Error(), "无效或已过期")
  209. }
  210. // TC-1298 非ADMIN无权消费
  211. func TestFetchUserCredentials_MemberForbidden(t *testing.T) {
  212. ctx := ctxhelper.MemberCtx("test-product")
  213. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  214. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  215. _, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: "any_ticket"})
  216. require.Error(t, err)
  217. var codeErr *response.CodeError
  218. require.True(t, errors.As(err, &codeErr))
  219. assert.Equal(t, 403, codeErr.Code())
  220. }
  221. // TC-1299 并发消费同一ticket
  222. func TestFetchUserCredentials_ConcurrentConsume(t *testing.T) {
  223. ctx := ctxhelper.SuperAdminCtx()
  224. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  225. conn := testutil.GetTestSqlConn()
  226. username := testutil.UniqueId()
  227. createLogic := NewCreateUserLogic(ctx, svcCtx)
  228. createResp, err := createLogic.CreateUser(&types.CreateUserReq{
  229. Username: username,
  230. DeptId: 0,
  231. })
  232. require.NoError(t, err)
  233. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
  234. var successCount atomic.Int32
  235. var wg sync.WaitGroup
  236. for i := 0; i < 10; i++ {
  237. wg.Add(1)
  238. go func() {
  239. defer wg.Done()
  240. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  241. _, ferr := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
  242. if ferr == nil {
  243. successCount.Add(1)
  244. }
  245. }()
  246. }
  247. wg.Wait()
  248. assert.Equal(t, int32(1), successCount.Load())
  249. }
  250. // TC-1280 正常创建用户返回ticket
  251. func TestCreateUser_TicketFlow(t *testing.T) {
  252. ctx := ctxhelper.SuperAdminCtx()
  253. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  254. conn := testutil.GetTestSqlConn()
  255. username := testutil.UniqueId()
  256. logic := NewCreateUserLogic(ctx, svcCtx)
  257. resp, err := logic.CreateUser(&types.CreateUserReq{
  258. Username: username,
  259. Nickname: "测试用户",
  260. DeptId: 0,
  261. })
  262. require.NoError(t, err)
  263. require.NotNil(t, resp)
  264. assert.Greater(t, resp.Id, int64(0))
  265. assert.NotEmpty(t, resp.CredentialsTicket)
  266. assert.Greater(t, resp.CredentialsExpiresAt, int64(0))
  267. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id) })
  268. }
  269. // TC-1282 生成密码满足强度要求
  270. func TestCreateUser_PasswordStrength(t *testing.T) {
  271. ctx := ctxhelper.SuperAdminCtx()
  272. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  273. conn := testutil.GetTestSqlConn()
  274. username := testutil.UniqueId()
  275. createLogic := NewCreateUserLogic(ctx, svcCtx)
  276. createResp, err := createLogic.CreateUser(&types.CreateUserReq{
  277. Username: username,
  278. DeptId: 0,
  279. })
  280. require.NoError(t, err)
  281. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", createResp.Id) })
  282. fetchLogic := NewFetchUserCredentialsLogic(ctx, svcCtx)
  283. cred, err := fetchLogic.FetchUserCredentials(&types.FetchUserCredentialsReq{Ticket: createResp.CredentialsTicket})
  284. require.NoError(t, err)
  285. msg := util.ValidatePassword(cred.Password)
  286. assert.Empty(t, msg, "generated password should pass strength validation")
  287. }
  288. // TC-1284 创建后mustChangePassword=1
  289. func TestCreateUser_MustChangePassword(t *testing.T) {
  290. ctx := ctxhelper.SuperAdminCtx()
  291. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  292. conn := testutil.GetTestSqlConn()
  293. username := testutil.UniqueId()
  294. logic := NewCreateUserLogic(ctx, svcCtx)
  295. resp, err := logic.CreateUser(&types.CreateUserReq{
  296. Username: username,
  297. DeptId: 0,
  298. })
  299. require.NoError(t, err)
  300. t.Cleanup(func() {
  301. testutil.CleanTable(ctx, conn, "`sys_user`", resp.Id)
  302. svcCtx.Redis.DelCtx(ctx, userCredentialsKeyPrefix+resp.CredentialsTicket)
  303. })
  304. user, err := svcCtx.SysUserModel.FindOne(ctx, resp.Id)
  305. require.NoError(t, err)
  306. assert.Equal(t, int64(consts.MustChangePasswordYes), user.MustChangePassword)
  307. }