package auth import ( "context" "database/sql" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "perms-system-server/internal/loaders" "perms-system-server/internal/middleware" userModel "perms-system-server/internal/model/user" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func init() { response.Setup() } // TC-0796: handler 薄层契约 —— LogoutHandler 在无认证上下文(userId=0) 时必须返回 401, // 而不是 200 或 5xx。这把"handler 正确透传 logic 错误"的契约冻结住, 避免未来改造时 // 意外把未登录请求吞成成功/崩溃。 func TestLogoutHandler_UnauthorizedWhenNoUserCtx(t *testing.T) { svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) handler := LogoutHandler(svcCtx) req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) var body response.Body require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body)) assert.False(t, body.Success) assert.Equal(t, 401, body.ErrorCode) assert.Contains(t, body.ErrorMessage, "未登录") } // TC-0797: handler 薄层契约 —— LogoutHandler 在有效认证上下文下必须 200 且无响应体(httpx.Ok); // 同时 DB 的 tokenVersion 必须被实际递增 (证明 handler 真的调用了 logic 而不是只返回 200)。 func TestLogoutHandler_SuccessIncrementsTokenVersion(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: "h_lo_" + testutil.UniqueId(), Password: testutil.HashPassword("pw"), Nickname: "h_lo", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, TokenVersion: 0, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) userId, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", userId) }) handler := LogoutHandler(svcCtx) req := httptest.NewRequest(http.MethodPost, "/api/auth/logout", nil) // 模拟 JWT middleware 已经注入 userDetails 的场景 req = req.WithContext(middleware.WithUserDetails(req.Context(), &loaders.UserDetails{ UserId: userId, Username: "h_lo", Status: 1, })) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code, "成功登出必须 200") u, err := svcCtx.SysUserModel.FindOne(ctx, userId) require.NoError(t, err) assert.Equal(t, int64(1), u.TokenVersion, "handler 必须真正触达 logic 层; tokenVersion 未递增说明 handler 伪装成功") }