package server import ( "context" "database/sql" "fmt" "testing" "time" authHelper "perms-system-server/internal/logic/auth" permModel "perms-system-server/internal/model/perm" productModel "perms-system-server/internal/model/product" memberModel "perms-system-server/internal/model/productmember" roleModel "perms-system-server/internal/model/role" rolePermModel "perms-system-server/internal/model/roleperm" userModel "perms-system-server/internal/model/user" userPermModel "perms-system-server/internal/model/userperm" userRoleModel "perms-system-server/internal/model/userrole" "perms-system-server/internal/svc" "perms-system-server/internal/testutil" "perms-system-server/pb" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func bcryptHash(t *testing.T, plaintext string) string { t.Helper() h, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.MinCost) require.NoError(t, err) return string(h) } // ---------- SyncPermissions ---------- // TC-0230: 正常同步 func TestSyncPermissions_Normal(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "secret1"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTableByField(ctx, conn, "`sys_perm`", "productCode", uid) testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) srv := NewPermServer(svcCtx) resp, err := srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: uid, AppSecret: "secret1", Perms: []*pb.PermItem{ {Code: "perm_a", Name: "Perm A", Remark: "remark_a"}, {Code: "perm_b", Name: "Perm B", Remark: "remark_b"}, }, }) require.NoError(t, err) assert.Equal(t, int64(2), resp.Added) assert.Equal(t, int64(0), resp.Updated) assert.Equal(t, int64(0), resp.Disabled) resp2, err := srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: uid, AppSecret: "secret1", Perms: []*pb.PermItem{ {Code: "perm_a", Name: "Perm A Updated", Remark: "remark_a"}, }, }) require.NoError(t, err) assert.Equal(t, int64(0), resp2.Added) assert.Equal(t, int64(1), resp2.Updated) assert.Equal(t, int64(1), resp2.Disabled) } // TC-0231: appKey无效 func TestSyncPermissions_InvalidAppKey(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) srv := NewPermServer(svcCtx) _, err := srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: "nonexistent_key", AppSecret: "any", Perms: []*pb.PermItem{{Code: "c", Name: "n"}}, }) require.Error(t, err) assert.Equal(t, codes.Unauthenticated, status.Code(err)) assert.Equal(t, "无效的appKey", status.Convert(err).Message()) } // TC-0232: appSecret错误 func TestSyncPermissions_WrongAppSecret(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "real_secret"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) srv := NewPermServer(svcCtx) _, err = srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: uid, AppSecret: "wrong_secret", Perms: []*pb.PermItem{{Code: "c", Name: "n"}}, }) require.Error(t, err) assert.Equal(t, codes.Unauthenticated, status.Code(err)) assert.Equal(t, "appSecret验证失败", status.Convert(err).Message()) } // TC-0233: 产品已禁用 func TestSyncPermissions_ProductDisabled(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "secret1"), Status: 2, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) srv := NewPermServer(svcCtx) _, err = srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: uid, AppSecret: "secret1", Perms: []*pb.PermItem{{Code: "c", Name: "n"}}, }) require.Error(t, err) assert.Equal(t, codes.PermissionDenied, status.Code(err)) assert.Equal(t, "产品已被禁用", status.Convert(err).Message()) } // ---------- Login ---------- // TC-0235: 正常登录(普通用户+productCode) func TestLogin_Normal(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pmId, _ := pmRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) resp, err := srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "pass123", ProductCode: uid, }) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.NotEmpty(t, resp.RefreshToken) assert.True(t, resp.Expires > time.Now().Unix(), "expires应为未来的unix时间戳") assert.Equal(t, uId, resp.UserId) assert.Equal(t, uid, resp.Username) // BUG-01: proto定义了nickname字段,实现应返回用户昵称 assert.Equal(t, "nick", resp.Nickname, "BUG-01: LoginResp.Nickname 应返回用户昵称而非空字符串") } // TC-0236: 用户不存在 func TestLogin_UserNotFound(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) srv := NewPermServer(svcCtx) _, err := srv.Login(ctx, &pb.LoginReq{ Username: "nonexistent_user_xyz", Password: "any", ProductCode: "any_product", }) require.Error(t, err) assert.Equal(t, codes.Unauthenticated, status.Code(err)) assert.Equal(t, "用户名或密码错误", status.Convert(err).Message()) } // TC-0237: 密码错误 func TestLogin_WrongPassword(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("correct_pass"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) _, err = srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "wrong_pass", ProductCode: "any_product", }) require.Error(t, err) assert.Equal(t, codes.Unauthenticated, status.Code(err)) assert.Equal(t, "用户名或密码错误", status.Convert(err).Message()) } // TC-0238: 账号冻结 func TestLogin_AccountFrozen(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 2, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) _, err = srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "pass123", ProductCode: "any_product", }) require.Error(t, err) assert.Equal(t, codes.PermissionDenied, status.Code(err)) assert.Equal(t, "账号已被冻结", status.Convert(err).Message()) } // TC-0239: 超管被拒绝 func TestLogin_SuperAdminRejected(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "sa", Avatar: sql.NullString{}, IsSuperAdmin: 1, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) _, err = srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "pass123", ProductCode: "any_product", }) require.Error(t, err) assert.Equal(t, codes.PermissionDenied, status.Code(err)) assert.Equal(t, "超级管理员不允许通过产品端登录,请使用管理后台", status.Convert(err).Message()) } // TC-0240: 普通用户+productCode func TestLogin_NormalUserWithProductCode(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() mbrRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mbrId, _ := mbrRes.LastInsertId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: uid, Name: uid + "_role", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() pm1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "p1", Code: uid + "_c1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pm1Id, _ := pm1Res.LastInsertId() urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userRoleModel.SysUserRole{ UserId: uId, RoleId: roleId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) urId, _ := urRes.LastInsertId() rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &rolePermModel.SysRolePerm{ RoleId: roleId, PermId: pm1Id, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) rpId, _ := rpRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpId) testutil.CleanTable(ctx, conn, "`sys_user_role`", urId) testutil.CleanTable(ctx, conn, "`sys_perm`", pm1Id) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) testutil.CleanTable(ctx, conn, "`sys_product_member`", mbrId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) resp, err := srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "pass123", ProductCode: uid, }) require.NoError(t, err) assert.Equal(t, "MEMBER", resp.MemberType) assert.Contains(t, resp.Perms, uid+"_c1") assert.NotEmpty(t, resp.AccessToken) assert.NotEmpty(t, resp.RefreshToken) } // TC-0242: productCode为空 func TestLogin_EmptyProductCode(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) srv := NewPermServer(svcCtx) _, err := srv.Login(ctx, &pb.LoginReq{ Username: "anyuser", Password: "anypass", ProductCode: "", }) require.Error(t, err) assert.Equal(t, codes.InvalidArgument, status.Code(err)) assert.Equal(t, "productCode不能为空", status.Convert(err).Message()) } // ---------- RefreshToken ---------- // TC-0243: 正常刷新(refreshToken原样返回,不重新生成) func TestRefreshToken_Normal(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) cfg := testutil.GetTestConfig() refreshToken, err := authHelper.GenerateRefreshToken(cfg.Auth.RefreshSecret, cfg.Auth.RefreshExpire, uId, "", 0) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: refreshToken, }) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.Equal(t, refreshToken, resp.RefreshToken, "refreshToken应原样返回,不重新生成") assert.True(t, resp.Expires > time.Now().Unix(), "expires应为未来的unix时间戳") } // TC-0244: token无效 func TestRefreshToken_InvalidToken(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) srv := NewPermServer(svcCtx) _, err := srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: "invalid.token.string", }) require.Error(t, err) assert.Equal(t, codes.Unauthenticated, status.Code(err)) assert.Equal(t, "refreshToken无效或已过期", status.Convert(err).Message()) } // TC-0245: 账号冻结 func TestRefreshToken_AccountFrozen(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 2, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) cfg := testutil.GetTestConfig() refreshToken, err := authHelper.GenerateRefreshToken(cfg.Auth.RefreshSecret, cfg.Auth.RefreshExpire, uId, "", 0) require.NoError(t, err) srv := NewPermServer(svcCtx) _, err = srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: refreshToken, }) require.Error(t, err) assert.Equal(t, codes.PermissionDenied, status.Code(err)) assert.Equal(t, "账号已被冻结", status.Convert(err).Message()) } // TC-0246: productCode回退到claims func TestRefreshToken_FallbackToClaimsProductCode(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() mbrRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mbrId, _ := mbrRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", mbrId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) cfg := testutil.GetTestConfig() refreshToken, err := authHelper.GenerateRefreshToken(cfg.Auth.RefreshSecret, cfg.Auth.RefreshExpire, uId, uid, 0) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: refreshToken, ProductCode: "", }) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.Equal(t, refreshToken, resp.RefreshToken, "refreshToken应原样返回") } // TC-0247: 超管+productCode func TestRefreshToken_SuperAdminWithProductCode(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "sa", Avatar: sql.NullString{}, IsSuperAdmin: 1, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() pm1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "p1", Code: uid + "_c1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pm1Id, _ := pm1Res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", pm1Id) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) cfg := testutil.GetTestConfig() refreshToken, err := authHelper.GenerateRefreshToken(cfg.Auth.RefreshSecret, cfg.Auth.RefreshExpire, uId, uid, 0) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: refreshToken, ProductCode: uid, }) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.Equal(t, refreshToken, resp.RefreshToken, "refreshToken应原样返回") } // TC-0248: 普通用户+productCode func TestRefreshToken_NormalUserWithProductCode(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() mbrRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mbrId, _ := mbrRes.LastInsertId() pm1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "p1", Code: uid + "_c1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pm1Id, _ := pm1Res.LastInsertId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: uid, Name: uid + "_role", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userRoleModel.SysUserRole{ UserId: uId, RoleId: roleId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) urId, _ := urRes.LastInsertId() rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &rolePermModel.SysRolePerm{ RoleId: roleId, PermId: pm1Id, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) rpId, _ := rpRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpId) testutil.CleanTable(ctx, conn, "`sys_user_role`", urId) testutil.CleanTable(ctx, conn, "`sys_perm`", pm1Id) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) testutil.CleanTable(ctx, conn, "`sys_product_member`", mbrId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) cfg := testutil.GetTestConfig() refreshToken, err := authHelper.GenerateRefreshToken(cfg.Auth.RefreshSecret, cfg.Auth.RefreshExpire, uId, uid, 0) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.RefreshToken(ctx, &pb.RefreshTokenReq{ RefreshToken: refreshToken, ProductCode: uid, }) require.NoError(t, err) assert.NotEmpty(t, resp.AccessToken) assert.Equal(t, refreshToken, resp.RefreshToken, "refreshToken应原样返回") } // ---------- VerifyToken ---------- // TC-0249: 有效token(VerifyToken 现在实时查询DB,需要真实数据) func TestVerifyToken_Valid(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) cfg := testutil.GetTestConfig() conn := testutil.GetTestSqlConn() ts := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick_verify", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: ts, UpdateTime: ts, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "prod_verify", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: ts, UpdateTime: ts, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts, }) require.NoError(t, err) pmId, _ := pmRes.LastInsertId() pm1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "perm_a", Code: "perm_a", Status: 1, CreateTime: ts, UpdateTime: ts, }) require.NoError(t, err) pm1Id, _ := pm1Res.LastInsertId() pm2Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "perm_b", Code: "perm_b", Status: 1, CreateTime: ts, UpdateTime: ts, }) require.NoError(t, err) pm2Id, _ := pm2Res.LastInsertId() t.Cleanup(func() { svcCtx.UserDetailsLoader.Del(ctx, uId, uid) testutil.CleanTable(ctx, conn, "`sys_perm`", pm1Id, pm2Id) testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) svcCtx.UserDetailsLoader.Del(ctx, uId, uid) accessToken, err := authHelper.GenerateAccessToken( cfg.Auth.AccessSecret, cfg.Auth.AccessExpire, uId, uid, uid, "ADMIN", 0, ) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: accessToken}) require.NoError(t, err) assert.True(t, resp.Valid) assert.Equal(t, uId, resp.UserId) assert.Equal(t, uid, resp.Username) assert.Equal(t, "ADMIN", resp.MemberType) assert.ElementsMatch(t, []string{"perm_a", "perm_b"}, resp.Perms) // BUG-02: proto定义了productCode字段,实现应返回产品编码 assert.Equal(t, uid, resp.ProductCode, "BUG-02: VerifyTokenResp.ProductCode 应返回产品编码而非空字符串") } // TC-0250: 无效token func TestVerifyToken_Invalid(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: "invalid.token.here"}) require.NoError(t, err) assert.False(t, resp.Valid) } // TC-0251: 缺少userId func TestVerifyToken_MissingUserId(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) cfg := testutil.GetTestConfig() // Generate a token without userId by using raw JWT token := createTokenWithoutUserId(cfg.Auth.AccessSecret) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: token}) require.NoError(t, err) assert.False(t, resp.Valid) } // ---------- GetUserPerms ---------- // TC-0255: 用户不存在 func TestGetUserPerms_UserNotFound(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "secret1"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) srv := NewPermServer(svcCtx) _, err = srv.GetUserPerms(ctx, &pb.GetUserPermsReq{ UserId: 999999999, ProductCode: uid, AppKey: uid, AppSecret: "secret1", }) require.Error(t, err) assert.Equal(t, codes.NotFound, status.Code(err)) assert.Equal(t, "用户不存在", status.Convert(err).Message()) } // TC-0256: 超管 func TestGetUserPerms_SuperAdmin(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass"), Nickname: "sa", Avatar: sql.NullString{}, IsSuperAdmin: 1, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "secret1"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() pm1Res, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "p1", Code: uid + "_c1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pm1Id, _ := pm1Res.LastInsertId() mRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "ADMIN", Status: 1, 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_perm`", pm1Id) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) resp, err := srv.GetUserPerms(ctx, &pb.GetUserPermsReq{ UserId: uId, ProductCode: uid, AppKey: uid, AppSecret: "secret1", }) require.NoError(t, err) assert.Equal(t, "SUPER_ADMIN", resp.MemberType) assert.Contains(t, resp.Perms, uid+"_c1") } // TC-0234: 验证disabled计数 func TestSyncPermissions_VerifyDisabledCount(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid, AppSecret: bcryptHash(t, "secret1"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() var permIds []int64 for i := 0; i < 5; i++ { pmRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "p", Code: fmt.Sprintf("%s_c%d", uid, i), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pmId, _ := pmRes.LastInsertId() permIds = append(permIds, pmId) } t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permIds...) testutil.CleanTable(ctx, conn, "`sys_product`", pId) }) srv := NewPermServer(svcCtx) resp, err := srv.SyncPermissions(ctx, &pb.SyncPermissionsReq{ AppKey: uid, AppSecret: "secret1", Perms: []*pb.PermItem{ {Code: fmt.Sprintf("%s_c0", uid), Name: "p"}, {Code: fmt.Sprintf("%s_c1", uid), Name: "p"}, }, }) require.NoError(t, err) assert.Equal(t, int64(3), resp.Disabled) } // TC-0257: MEMBER-DENY覆盖 func TestGetUserPerms_MemberDENYOverride(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "test_prod", AppKey: uid + "_k", AppSecret: bcryptHash(t, "secret1"), Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() mbrRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mbrId, _ := mbrRes.LastInsertId() roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{ ProductCode: uid, Name: uid + "_role", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) roleId, _ := roleRes.LastInsertId() permARes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "permA", Code: uid + "_pA", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) permAId, _ := permARes.LastInsertId() permBRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "permB", Code: uid + "_pB", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) permBId, _ := permBRes.LastInsertId() urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userRoleModel.SysUserRole{ UserId: uId, RoleId: roleId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) urId, _ := urRes.LastInsertId() rpARes, err := svcCtx.SysRolePermModel.Insert(ctx, &rolePermModel.SysRolePerm{ RoleId: roleId, PermId: permAId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) rpAId, _ := rpARes.LastInsertId() rpBRes, err := svcCtx.SysRolePermModel.Insert(ctx, &rolePermModel.SysRolePerm{ RoleId: roleId, PermId: permBId, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) rpBId, _ := rpBRes.LastInsertId() upRes, err := svcCtx.SysUserPermModel.Insert(ctx, &userPermModel.SysUserPerm{ UserId: uId, PermId: permAId, Effect: "DENY", CreateTime: now, UpdateTime: now, }) require.NoError(t, err) upId, _ := upRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user_perm`", upId) testutil.CleanTable(ctx, conn, "`sys_role_perm`", rpAId, rpBId) testutil.CleanTable(ctx, conn, "`sys_user_role`", urId) testutil.CleanTable(ctx, conn, "`sys_perm`", permAId, permBId) testutil.CleanTable(ctx, conn, "`sys_role`", roleId) testutil.CleanTable(ctx, conn, "`sys_product_member`", mbrId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) resp, err := srv.GetUserPerms(ctx, &pb.GetUserPermsReq{ UserId: uId, ProductCode: uid, AppKey: uid + "_k", AppSecret: "secret1", }) require.NoError(t, err) assert.Equal(t, "MEMBER", resp.MemberType) assert.Contains(t, resp.Perms, uid+"_pB") assert.NotContains(t, resp.Perms, uid+"_pA") } // TC-0252: gRPC VerifyToken 用户已冻结返回valid=false(H-4修复验证) func TestVerifyToken_FrozenUserReturnsInvalid(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() cfg := testutil.GetTestConfig() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass"), Nickname: "frozen", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 2, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) accessToken, err := authHelper.GenerateAccessToken( cfg.Auth.AccessSecret, cfg.Auth.AccessExpire, uId, uid, "", "MEMBER", 0, ) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: accessToken}) require.NoError(t, err) assert.False(t, resp.Valid, "frozen user token should be invalid") } // TC-0253: gRPC VerifyToken 非产品成员返回valid=false(H-4修复验证) func TestVerifyToken_NonMemberReturnsInvalid(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() pc := testutil.UniqueId() cfg := testutil.GetTestConfig() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass"), Nickname: "user", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: pc, Name: "prod", AppKey: testutil.UniqueId(), AppSecret: "s", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) accessToken, err := authHelper.GenerateAccessToken( cfg.Auth.AccessSecret, cfg.Auth.AccessExpire, uId, uid, pc, "MEMBER", 0, ) require.NoError(t, err) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: accessToken}) require.NoError(t, err) assert.False(t, resp.Valid, "non-member user with productCode should be invalid") } // TC-0254: gRPC VerifyToken 返回实时权限和成员类型(H-4修复验证) func TestVerifyToken_ReturnsRealtimeData(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() cfg := testutil.GetTestConfig() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass"), Nickname: "user", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "prod", AppKey: uid + "_k", AppSecret: "s", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() mbrRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "ADMIN", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) mbrId, _ := mbrRes.LastInsertId() permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{ ProductCode: uid, Name: "realtime_perm", Code: uid + "_rt", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) permId, _ := permRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permId) testutil.CleanTable(ctx, conn, "`sys_product_member`", mbrId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) accessToken, err := authHelper.GenerateAccessToken( cfg.Auth.AccessSecret, cfg.Auth.AccessExpire, uId, uid, uid, "MEMBER", 0, ) require.NoError(t, err) svcCtx.UserDetailsLoader.Clean(ctx, uId) srv := NewPermServer(svcCtx) resp, err := srv.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: accessToken}) require.NoError(t, err) assert.True(t, resp.Valid) assert.Equal(t, "ADMIN", resp.MemberType, "should return realtime memberType, not token's") assert.Contains(t, resp.Perms, uid+"_rt", "should return realtime perms") } // TC-0241: gRPC Login 产品成员被禁用时拒绝(H-3修复验证) func TestLogin_DisabledMemberRejected(t *testing.T) { ctx := context.Background() svcCtx := svc.NewServiceContext(testutil.GetTestConfig()) conn := testutil.GetTestSqlConn() now := time.Now().Unix() uid := testutil.UniqueId() uRes, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{ Username: uid, Password: testutil.HashPassword("pass123"), Nickname: "nick", Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2, Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) uId, _ := uRes.LastInsertId() pRes, err := svcCtx.SysProductModel.Insert(ctx, &productModel.SysProduct{ Code: uid, Name: "prod", AppKey: uid + "_k", AppSecret: "s1", Status: 1, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pId, _ := pRes.LastInsertId() pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &memberModel.SysProductMember{ ProductCode: uid, UserId: uId, MemberType: "MEMBER", Status: 2, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) pmId, _ := pmRes.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) testutil.CleanTable(ctx, conn, "`sys_product`", pId) testutil.CleanTable(ctx, conn, "`sys_user`", uId) }) srv := NewPermServer(svcCtx) _, err = srv.Login(ctx, &pb.LoginReq{ Username: uid, Password: "pass123", ProductCode: uid, }) require.Error(t, err) assert.Equal(t, codes.PermissionDenied, status.Code(err)) assert.Equal(t, "您在该产品下的成员资格已被禁用", status.Convert(err).Message()) } // helper: create a JWT with no userId claim func createTokenWithoutUserId(secret string) string { claims := jwt.MapClaims{ "username": "test", "exp": time.Now().Add(time.Hour).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) s, _ := token.SignedString([]byte(secret)) return s }