logoutUsernameForward_audit_test.go 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. package auth
  2. import (
  3. "testing"
  4. "perms-system-server/internal/loaders"
  5. "perms-system-server/internal/middleware"
  6. "perms-system-server/internal/testutil/mocks"
  7. "github.com/stretchr/testify/require"
  8. "go.uber.org/mock/gomock"
  9. )
  10. // ---------------------------------------------------------------------------
  11. // 覆盖目标:审计 M-R11-2 —— Logout Logic 必须把 middleware.UserDetails 里的 Username
  12. // 直接透传给 SysUserModel.IncrementTokenVersion,而不是让 Model 内部再 FindOne 取一次。
  13. //
  14. // 本文件以 mock 的方式对 Logic→Model 的契约做最严格的钉子:
  15. // IncrementTokenVersion 的第 3 个参数必须**字面等于** ud.Username;
  16. // 若 DEV 未来回归成"传 '' 让 Model 内部 FindOne",gomock 会拦截报错。
  17. // ---------------------------------------------------------------------------
  18. // TC-1047: M-R11-2 —— Logout 透传 ud.Username 给 IncrementTokenVersion
  19. func TestLogout_ForwardsUsername_NoInternalFindOne(t *testing.T) {
  20. ctrl := gomock.NewController(t)
  21. t.Cleanup(ctrl.Finish)
  22. const userId = int64(7777)
  23. const username = "m_r11_2_subject"
  24. mockUser := mocks.NewMockSysUserModel(ctrl)
  25. // 契约:username 参数必须与 ud.Username 字面一致。
  26. mockUser.EXPECT().
  27. IncrementTokenVersion(gomock.Any(), userId, username).
  28. Return(int64(1), nil)
  29. // 不允许再出现任何 FindOne / FindOneByUsername 的调用(gomock 默认就会对未声明的调用 fail)。
  30. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  31. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{
  32. UserId: userId, Username: username, Status: 1,
  33. })
  34. require.NoError(t, NewLogoutLogic(ctx, svcCtx).Logout(),
  35. "Logout 正常路径应通过,且必须按签名字面透传 username")
  36. }
  37. // TC-1048: M-R11-2 —— Logout 在 Username 为 "" 的极端场景下仍然透传空串(不隐式 FindOne 修补)
  38. // 该契约保证 Model 层对 "调用方未提供 username" 的场景**不做缓存键兜底**;
  39. // 若 DEV 回退成 Model 内部 FindOne,行为会变:Logic 传 "" 进来时 Model 会真的去 DB 查一次。
  40. func TestLogout_EmptyUsernameStillForwarded(t *testing.T) {
  41. ctrl := gomock.NewController(t)
  42. t.Cleanup(ctrl.Finish)
  43. const userId = int64(8888)
  44. mockUser := mocks.NewMockSysUserModel(ctrl)
  45. mockUser.EXPECT().
  46. IncrementTokenVersion(gomock.Any(), userId, ""). // 严格 "" 不是 gomock.Any()
  47. Return(int64(3), nil)
  48. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{User: mockUser})
  49. ctx := middleware.WithUserDetails(t.Context(), &loaders.UserDetails{
  50. UserId: userId, Username: "", Status: 1,
  51. })
  52. require.NoError(t, NewLogoutLogic(ctx, svcCtx).Logout())
  53. }