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