package middleware_test import ( "context" "database/sql" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "perms-system-server/internal/consts" "perms-system-server/internal/middleware" "perms-system-server/internal/model" productModel "perms-system-server/internal/model/product" productmemberModel "perms-system-server/internal/model/productmember" userModel "perms-system-server/internal/model/user" "perms-system-server/internal/response" "perms-system-server/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TC-0749: L-B 修复回归 —— jwtauthMiddleware 必须优先判定 TokenVersion 失效, // 而不是 ProductStatus/MemberType。旧实现会在"产品已禁用"场景下先返回 403 ProductDisabled, // 使用户被强制退出时看到无关文案;修复后 TokenVersion 不一致应返回 401 "登录状态已失效,请重新登录"。 func TestJwtAuthMiddleware_TokenVersionCheckedBeforeProductStatus(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() models := model.NewModels(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() // 1) 创建"已禁用"的产品 pCode := "mw_ord_" + testutil.UniqueId() pRes, err := models.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: pCode, Name: pCode, AppKey: pCode + "_k", AppSecret: "s", Status: consts.StatusDisabled, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() // 2) 创建启用中的用户,TokenVersion=5 username := "mw_ord_u_" + testutil.UniqueId() uRes, err := models.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: username, Password: "x", Nickname: "n", Avatar: sql.NullString{}, IsSuperAdmin: consts.IsSuperAdminNo, MustChangePassword: 2, Status: consts.StatusEnabled, TokenVersion: 5, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) userId, _ := uRes.LastInsertId() // 3) 该用户是禁用产品的 ADMIN 成员 mRes, err := models.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{ ProductCode: pCode, UserId: userId, MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mId, _ := mRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", mId) testutil.CleanTable(ctx, conn, "`sys_user`", userId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) m, _ := newTestMiddleware() // 携带 stale TokenVersion=3 的 access token(DB 是 5)+ 禁用产品 code tokenStr := generateTestToken(testAccessSecret, 3600, &middleware.Claims{ TokenType: consts.TokenTypeAccess, UserId: userId, Username: username, ProductCode: pCode, TokenVersion: 3, }) handler := m.Handle(func(w http.ResponseWriter, r *http.Request) { t.Fatal("should not reach handler") }) req := httptest.NewRequest(http.MethodPost, "/test", nil) req.Header.Set("Authorization", "Bearer "+tokenStr) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) var body response.Body require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body)) assert.Equal(t, 401, body.Code, "L-B:TokenVersion 失配必须先于产品禁用被识别(返回 401 而非 403)") assert.Equal(t, "登录状态已失效,请重新登录", body.Msg, "L-B:文案必须是'登录状态已失效'而不是'该产品已被禁用',否则用户会被无关信息误导") } // TC-0750: L-B 修复回归 —— TokenVersion 匹配但产品被禁用,仍应返回 403 "该产品已被禁用"。 // 保证修复未把所有场景都吞成 401。 func TestJwtAuthMiddleware_ProductDisabledAfterVersionOk(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() models := model.NewModels(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() pCode := "mw_ord2_" + testutil.UniqueId() pRes, err := models.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: pCode, Name: pCode, AppKey: pCode + "_k", AppSecret: "s", Status: consts.StatusDisabled, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() username := "mw_ord2_u_" + testutil.UniqueId() uRes, err := models.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: username, Password: "x", Nickname: "n", Avatar: sql.NullString{}, IsSuperAdmin: consts.IsSuperAdminNo, MustChangePassword: 2, Status: consts.StatusEnabled, TokenVersion: 0, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) userId, _ := uRes.LastInsertId() mRes, err := models.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{ ProductCode: pCode, UserId: userId, MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mId, _ := mRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", mId) testutil.CleanTable(ctx, conn, "`sys_user`", userId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) m, _ := newTestMiddleware() tokenStr := generateTestToken(testAccessSecret, 3600, &middleware.Claims{ TokenType: consts.TokenTypeAccess, UserId: userId, Username: username, ProductCode: pCode, TokenVersion: 0, }) handler := m.Handle(func(w http.ResponseWriter, r *http.Request) { t.Fatal("should not reach handler") }) req := httptest.NewRequest(http.MethodPost, "/test", nil) req.Header.Set("Authorization", "Bearer "+tokenStr) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) var body response.Body require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &body)) assert.Equal(t, 403, body.Code) assert.Equal(t, "该产品已被禁用", body.Msg) }