changePasswordLogic_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. package auth
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "go.uber.org/mock/gomock"
  9. "golang.org/x/crypto/bcrypt"
  10. "perms-system-server/internal/consts"
  11. "perms-system-server/internal/loaders"
  12. "perms-system-server/internal/middleware"
  13. userModel "perms-system-server/internal/model/user"
  14. "perms-system-server/internal/response"
  15. "perms-system-server/internal/svc"
  16. "perms-system-server/internal/testutil"
  17. "perms-system-server/internal/testutil/mocks"
  18. "perms-system-server/internal/types"
  19. "strings"
  20. "testing"
  21. "time"
  22. )
  23. func ctxWithUserId(userId int64) context.Context {
  24. return middleware.WithUserDetails(context.Background(), &loaders.UserDetails{UserId: userId})
  25. }
  26. func insertTestUser(t *testing.T, ctx context.Context, username, password string) int64 {
  27. t.Helper()
  28. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  29. now := time.Now().Unix()
  30. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  31. Username: username,
  32. Password: password,
  33. Nickname: "test",
  34. Avatar: sql.NullString{},
  35. Email: username + "@test.com",
  36. Phone: "13800000000",
  37. Remark: "",
  38. DeptId: 0,
  39. IsSuperAdmin: 2,
  40. MustChangePassword: 1,
  41. Status: 1,
  42. CreateTime: now,
  43. UpdateTime: now,
  44. })
  45. require.NoError(t, err)
  46. id, _ := res.LastInsertId()
  47. return id
  48. }
  49. // TC-0054: 正常修改
  50. func TestChangePassword_Success(t *testing.T) {
  51. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  52. conn := testutil.GetTestSqlConn()
  53. ctx := context.Background()
  54. oldPwd := "Oldpass123"
  55. newPwd := "Newpass456"
  56. username := testutil.UniqueId()
  57. hashed := testutil.HashPassword(oldPwd)
  58. userId := insertTestUser(t, ctx, username, hashed)
  59. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  60. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  61. resp, err := logic.ChangePassword(&types.ChangePasswordReq{
  62. OldPassword: oldPwd,
  63. NewPassword: newPwd,
  64. })
  65. require.NoError(t, err)
  66. require.NotNil(t, resp)
  67. assert.NotEmpty(t, resp.AccessToken)
  68. assert.NotEmpty(t, resp.RefreshToken)
  69. assert.Greater(t, resp.Expires, int64(0))
  70. updated, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  71. require.NoError(t, err)
  72. assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd)))
  73. }
  74. // TC-0055: mustChangePassword重置
  75. func TestChangePassword_MustChangePasswordReset(t *testing.T) {
  76. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  77. conn := testutil.GetTestSqlConn()
  78. ctx := context.Background()
  79. oldPwd := "Oldpass123"
  80. newPwd := "Newpass456"
  81. username := testutil.UniqueId()
  82. hashed := testutil.HashPassword(oldPwd)
  83. userId := insertTestUser(t, ctx, username, hashed)
  84. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  85. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  86. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  87. OldPassword: oldPwd,
  88. NewPassword: newPwd,
  89. })
  90. require.NoError(t, err)
  91. updated, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  92. require.NoError(t, err)
  93. assert.Equal(t, int64(2), updated.MustChangePassword)
  94. }
  95. // TC-0056: 原密码错误
  96. func TestChangePassword_WrongOldPassword(t *testing.T) {
  97. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  98. conn := testutil.GetTestSqlConn()
  99. ctx := context.Background()
  100. username := testutil.UniqueId()
  101. hashed := testutil.HashPassword("Realpass1")
  102. userId := insertTestUser(t, ctx, username, hashed)
  103. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  104. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  105. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  106. OldPassword: "Wrongpass1",
  107. NewPassword: "Newpass456",
  108. })
  109. var codeErr *response.CodeError
  110. require.True(t, errors.As(err, &codeErr))
  111. assert.Equal(t, 400, codeErr.Code())
  112. assert.Equal(t, "原密码错误", codeErr.Error())
  113. }
  114. // TC-0057: 新密码少于8字符
  115. func TestChangePassword_NewPasswordTooShort(t *testing.T) {
  116. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  117. logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx)
  118. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  119. OldPassword: "oldpass",
  120. NewPassword: "Pas1234",
  121. })
  122. var codeErr *response.CodeError
  123. require.True(t, errors.As(err, &codeErr))
  124. assert.Equal(t, 400, codeErr.Code())
  125. assert.Equal(t, "密码长度不能少于8个字符", codeErr.Error())
  126. }
  127. // TC-0058: 新密码恰好8字符(含大小写+数字)
  128. func TestChangePassword_NewPasswordExactly8Chars(t *testing.T) {
  129. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  130. conn := testutil.GetTestSqlConn()
  131. ctx := context.Background()
  132. oldPwd := "Oldpass123"
  133. newPwd := "Abcdef1x"
  134. username := testutil.UniqueId()
  135. hashed := testutil.HashPassword(oldPwd)
  136. userId := insertTestUser(t, ctx, username, hashed)
  137. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  138. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  139. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  140. OldPassword: oldPwd,
  141. NewPassword: newPwd,
  142. })
  143. require.NoError(t, err)
  144. updated, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  145. require.NoError(t, err)
  146. assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd)))
  147. }
  148. // TC-0059: 新密码空字符串
  149. func TestChangePassword_NewPasswordEmpty(t *testing.T) {
  150. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  151. logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx)
  152. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  153. OldPassword: "oldpass",
  154. NewPassword: "",
  155. })
  156. var codeErr *response.CodeError
  157. require.True(t, errors.As(err, &codeErr))
  158. assert.Equal(t, 400, codeErr.Code())
  159. assert.Equal(t, "密码长度不能少于8个字符", codeErr.Error())
  160. }
  161. // TC-0060: 新密码超过72字符
  162. func TestChangePassword_NewPasswordTooLong(t *testing.T) {
  163. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  164. longPwd := "A" + strings.Repeat("a", 71) + "1"
  165. logic := NewChangePasswordLogic(ctxWithUserId(1), svcCtx)
  166. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  167. OldPassword: "oldpass",
  168. NewPassword: longPwd,
  169. })
  170. var codeErr *response.CodeError
  171. require.True(t, errors.As(err, &codeErr))
  172. assert.Equal(t, 400, codeErr.Code())
  173. assert.Equal(t, "密码长度不能超过72个字符", codeErr.Error())
  174. }
  175. // TC-0061: 新密码恰好72字符
  176. func TestChangePassword_NewPasswordExactly72Chars(t *testing.T) {
  177. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  178. conn := testutil.GetTestSqlConn()
  179. ctx := context.Background()
  180. oldPwd := "Oldpass123"
  181. newPwd := "B" + strings.Repeat("b", 70) + "1"
  182. username := testutil.UniqueId()
  183. hashed := testutil.HashPassword(oldPwd)
  184. userId := insertTestUser(t, ctx, username, hashed)
  185. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  186. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  187. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  188. OldPassword: oldPwd,
  189. NewPassword: newPwd,
  190. })
  191. require.NoError(t, err)
  192. updated, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  193. require.NoError(t, err)
  194. assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(updated.Password), []byte(newPwd)))
  195. }
  196. // TC-0062: 新旧密码相同
  197. func TestChangePassword_SameOldAndNew(t *testing.T) {
  198. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  199. conn := testutil.GetTestSqlConn()
  200. ctx := context.Background()
  201. pwd := "Samepass123"
  202. username := testutil.UniqueId()
  203. hashed := testutil.HashPassword(pwd)
  204. userId := insertTestUser(t, ctx, username, hashed)
  205. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) })
  206. logic := NewChangePasswordLogic(ctxWithUserId(userId), svcCtx)
  207. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  208. OldPassword: pwd,
  209. NewPassword: pwd,
  210. })
  211. var codeErr *response.CodeError
  212. require.True(t, errors.As(err, &codeErr))
  213. assert.Equal(t, 400, codeErr.Code())
  214. assert.Equal(t, "新密码不能与原密码相同", codeErr.Error())
  215. }
  216. // TC-1179: "新旧密码相同"校验必须排在 bcrypt.CompareHashAndPassword 之前。
  217. //
  218. // 用例设计:DB 里存的 Password 哈希对应 "RealOldpass123",但请求传入
  219. // OldPassword == NewPassword == "Samepass123"(与 DB 存的原密码不匹配)。
  220. // - 若校验顺序正确(L-R17-4):先断 `OldPassword == NewPassword`,直接 400 "新密码不能
  221. // 与原密码相同",UpdatePassword 不应被触达(mock EXPECT 未声明即 Times(0) 判违约)。
  222. // - 若顺序被误回滚为"先 bcrypt 后等值判断":bcrypt 会先失败并 400 "原密码错误",
  223. // 文案不同,用例会失败。
  224. //
  225. // 该用例同时保证"同密码短路"这条 timing 分支在 mock 层面可回归观测,不依赖真实 DB 写入。
  226. func TestChangePassword_SameOldAndNew_ChecksBeforeBcrypt(t *testing.T) {
  227. ctrl := gomock.NewController(t)
  228. t.Cleanup(ctrl.Finish)
  229. const userId = int64(1750)
  230. // DB 里的哈希基于 "RealOldpass123"——与 req.OldPassword 不一致。
  231. realOld := "RealOldpass123"
  232. hashed, err := bcrypt.GenerateFromPassword([]byte(realOld), bcrypt.DefaultCost)
  233. require.NoError(t, err)
  234. mockUser := mocks.NewMockSysUserModel(ctrl)
  235. mockUser.EXPECT().FindOne(gomock.Any(), userId).
  236. Return(&userModel.SysUser{
  237. Id: userId,
  238. Username: "l_r17_4_subject",
  239. Password: string(hashed),
  240. Status: consts.StatusEnabled,
  241. UpdateTime: 4242,
  242. }, nil)
  243. // 关键护栏:UpdatePassword 绝不应被调用——若 EXPECT 未声明的方法被调用,gomock 会 FAIL。
  244. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  245. svcCtx.TokenOpLimiter = nil
  246. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
  247. wrongOldButEqualNew := "Samepass123"
  248. _, err = NewChangePasswordLogic(ctx, svcCtx).ChangePassword(&types.ChangePasswordReq{
  249. OldPassword: wrongOldButEqualNew,
  250. NewPassword: wrongOldButEqualNew,
  251. })
  252. require.Error(t, err)
  253. var ce *response.CodeError
  254. require.True(t, errors.As(err, &ce))
  255. assert.Equal(t, 400, ce.Code(),
  256. "L-R17-4:OldPassword==NewPassword 必须在 bcrypt 之前被拦截")
  257. assert.Equal(t, "新密码不能与原密码相同", ce.Error(),
  258. "文案必须是'新密码不能与原密码相同',若是'原密码错误'说明顺序被误回滚")
  259. }
  260. // TC-0063: 用户不存在
  261. func TestChangePassword_UserNotFound(t *testing.T) {
  262. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  263. logic := NewChangePasswordLogic(ctxWithUserId(99999999), svcCtx)
  264. _, err := logic.ChangePassword(&types.ChangePasswordReq{
  265. OldPassword: "Oldpass123",
  266. NewPassword: "Newpass456",
  267. })
  268. var codeErr *response.CodeError
  269. require.True(t, errors.As(err, &codeErr))
  270. assert.Equal(t, 404, codeErr.Code())
  271. assert.Equal(t, "用户不存在", codeErr.Error())
  272. }
  273. func TestChangePassword_UpdateConflict_Maps409(t *testing.T) {
  274. ctrl := gomock.NewController(t)
  275. t.Cleanup(ctrl.Finish)
  276. const userId = int64(777)
  277. oldPwd := "Oldpass123"
  278. newPwd := "Newpass456"
  279. hashed, err := bcrypt.GenerateFromPassword([]byte(oldPwd), bcrypt.DefaultCost)
  280. require.NoError(t, err)
  281. mockUser := mocks.NewMockSysUserModel(ctrl)
  282. mockUser.EXPECT().FindOne(gomock.Any(), userId).
  283. Return(&userModel.SysUser{
  284. Id: userId,
  285. Username: "m_r10_4_subject",
  286. Password: string(hashed),
  287. Status: consts.StatusEnabled,
  288. UpdateTime: 1000,
  289. }, nil)
  290. // 关键:强制底层返回 ErrUpdateConflict。
  291. // 签名增加 username 与 expectedUpdateTime 两个透传参数。
  292. mockUser.EXPECT().
  293. UpdatePassword(gomock.Any(), userId, "m_r10_4_subject", gomock.Any(), int64(consts.MustChangePasswordNo), int64(1000)).
  294. Return(userModel.ErrUpdateConflict)
  295. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  296. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
  297. logic := NewChangePasswordLogic(ctx, svcCtx)
  298. _, err = logic.ChangePassword(&types.ChangePasswordReq{
  299. OldPassword: oldPwd,
  300. NewPassword: newPwd,
  301. })
  302. var codeErr *response.CodeError
  303. require.True(t, errors.As(err, &codeErr), "必须是 *response.CodeError,否则会被 rest 兜成 500")
  304. assert.Equal(t, 409, codeErr.Code(), "ErrUpdateConflict 必须映射为 409 Conflict")
  305. assert.Contains(t, codeErr.Error(), "密码已被其他会话修改", "文案与业务契约对齐")
  306. }
  307. // TC-1016: 非 ErrUpdateConflict 的原生错误仍应透传(500 由 rest 兜底),
  308. // 防止修复把所有底层错误都误吞为 409。
  309. func TestChangePassword_GenericUpdateError_StillPropagates(t *testing.T) {
  310. ctrl := gomock.NewController(t)
  311. t.Cleanup(ctrl.Finish)
  312. const userId = int64(778)
  313. oldPwd := "Oldpass123"
  314. newPwd := "Newpass456"
  315. hashed, err := bcrypt.GenerateFromPassword([]byte(oldPwd), bcrypt.DefaultCost)
  316. require.NoError(t, err)
  317. mockUser := mocks.NewMockSysUserModel(ctrl)
  318. mockUser.EXPECT().FindOne(gomock.Any(), userId).
  319. Return(&userModel.SysUser{
  320. Id: userId,
  321. Username: "m_r10_4_subject2",
  322. Password: string(hashed),
  323. Status: consts.StatusEnabled,
  324. UpdateTime: 2000,
  325. }, nil)
  326. genericErr := errors.New("driver: bad connection")
  327. mockUser.EXPECT().
  328. UpdatePassword(gomock.Any(), userId, "m_r10_4_subject2", gomock.Any(), int64(consts.MustChangePasswordNo), int64(2000)).
  329. Return(genericErr)
  330. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  331. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
  332. logic := NewChangePasswordLogic(ctx, svcCtx)
  333. _, err = logic.ChangePassword(&types.ChangePasswordReq{
  334. OldPassword: oldPwd,
  335. NewPassword: newPwd,
  336. })
  337. require.Error(t, err)
  338. assert.ErrorIs(t, err, genericErr, "只把 ErrUpdateConflict 映射 409,其余错误原样透传(由 rest 兜 500)")
  339. var codeErr *response.CodeError
  340. assert.False(t, errors.As(err, &codeErr), "非冲突错误不得伪装成 CodeError")
  341. }
  342. func insertToctouUser(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext,
  343. username, plainPwd string) (int64, func()) {
  344. t.Helper()
  345. now := time.Now().Unix()
  346. hashed, err := bcrypt.GenerateFromPassword([]byte(plainPwd), bcrypt.DefaultCost)
  347. require.NoError(t, err)
  348. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  349. Username: username,
  350. Password: string(hashed),
  351. Nickname: "toctou",
  352. Avatar: sql.NullString{},
  353. Email: username + "@test.com",
  354. Phone: "13800000000",
  355. Remark: "",
  356. DeptId: 0,
  357. IsSuperAdmin: 2,
  358. MustChangePassword: 2,
  359. Status: 1,
  360. CreateTime: now,
  361. UpdateTime: now,
  362. })
  363. require.NoError(t, err)
  364. id, _ := res.LastInsertId()
  365. cleanup := func() {
  366. testutil.CleanTable(ctx, testutil.GetTestSqlConn(), "`sys_user`", id)
  367. }
  368. return id, cleanup
  369. }
  370. // TC-1042: E2E —— 400 vs 409 分支隔离:旧密码失配必须 400,绝不能误落 409
  371. func TestChangePassword_E2E_SecondCallWithOldPwd_Maps400(t *testing.T) {
  372. ctx := context.Background()
  373. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  374. svcCtx.TokenOpLimiter = nil
  375. oldPwd := "Oldpass123"
  376. username := "toctou_seq_" + testutil.UniqueId()
  377. userId, cleanup := insertToctouUser(t, ctx, svcCtx, username, oldPwd)
  378. t.Cleanup(cleanup)
  379. lctx := middleware.WithUserDetails(context.Background(),
  380. &loaders.UserDetails{UserId: userId, Username: username, Status: 1})
  381. require.NoError(t,
  382. func() error {
  383. _, err := NewChangePasswordLogic(lctx, svcCtx).ChangePassword(&types.ChangePasswordReq{
  384. OldPassword: oldPwd, NewPassword: "NewpassX_11",
  385. })
  386. return err
  387. }(),
  388. "首改必须成功")
  389. _, err := NewChangePasswordLogic(lctx, svcCtx).ChangePassword(&types.ChangePasswordReq{
  390. OldPassword: oldPwd, NewPassword: "NewpassY_22",
  391. })
  392. require.Error(t, err)
  393. var ce *response.CodeError
  394. require.True(t, errors.As(err, &ce))
  395. assert.Equal(t, 400, ce.Code(),
  396. "旧密码已失配应 400'原密码错误';不得因 ErrUpdateConflict 映射被误回 409")
  397. assert.Contains(t, ce.Error(), "原密码错误")
  398. // DB 终态:Password 是首改成功的 NewpassX_11,tokenVersion 恰好 1(而不是 2)。
  399. got, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  400. require.NoError(t, err)
  401. assert.NoError(t, bcrypt.CompareHashAndPassword([]byte(got.Password), []byte("NewpassX_11")))
  402. assert.Equal(t, int64(1), got.TokenVersion,
  403. "首改成功递增 1;第二次因 400 未进入 UpdatePassword,tokenVersion 必须仍是 1")
  404. }
  405. // TC-1043: UpdatePassword 签名护栏(mock 驱动):
  406. // 签名一旦回退(例如 username 再次被内部 FindOne 取而非外层透传),整个链路会编译失败;
  407. // 但契约层面的"必须透传外层 snapshot 的 UpdateTime"更细致:Logic 必须把 FindOne 返回的
  408. // snapshot.UpdateTime 原样交给 UpdatePassword,不得自己算 time.Now() 或重新 FindOne。
  409. // 这里用 mock 钉死该契约:FindOne 返回 UpdateTime=4242,UpdatePassword 必须收到 4242。
  410. func TestChangePassword_ForwardsSnapshotUpdateTime(t *testing.T) {
  411. // 注:此契约已由既有 TestChangePassword_UpdateConflict_Maps409(UpdateTime=1000)覆盖,
  412. // 这里再以另一组数值(4242)做"反证哨兵",若 DEV 不小心硬编码常量/写死 time.Now,
  413. // 两组数值会同时失败,快速定位。
  414. t.Run("expected=4242", func(t *testing.T) { runSnapshotForwardCase(t, 4242) })
  415. t.Run("expected=9876543210", func(t *testing.T) { runSnapshotForwardCase(t, 9876543210) })
  416. }
  417. func runSnapshotForwardCase(t *testing.T, expectedUpdateTime int64) {
  418. ctrl := gomock.NewController(t)
  419. t.Cleanup(ctrl.Finish)
  420. const userId = int64(17)
  421. oldPwd := "Oldpass123"
  422. newPwd := "Newpass456"
  423. hashed, err := bcrypt.GenerateFromPassword([]byte(oldPwd), bcrypt.DefaultCost)
  424. require.NoError(t, err)
  425. mockUser := mocks.NewMockSysUserModel(ctrl)
  426. mockUser.EXPECT().FindOne(gomock.Any(), userId).
  427. Return(&userModel.SysUser{
  428. Id: userId,
  429. Username: "snap_subject",
  430. Password: string(hashed),
  431. Status: 1,
  432. UpdateTime: expectedUpdateTime,
  433. }, nil)
  434. // 合同:UpdatePassword 的第 6 个参数必须与 FindOne 返回的 UpdateTime 字面相等。
  435. mockUser.EXPECT().
  436. UpdatePassword(gomock.Any(), userId, "snap_subject", gomock.Any(),
  437. int64(consts.MustChangePasswordNo), expectedUpdateTime).
  438. Return(nil)
  439. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  440. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{UserId: userId})
  441. _, err = NewChangePasswordLogic(ctx, svcCtx).ChangePassword(
  442. &types.ChangePasswordReq{OldPassword: oldPwd, NewPassword: newPwd})
  443. require.NoError(t, err)
  444. }