| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- package role
- import (
- "errors"
- "fmt"
- "testing"
- "time"
- permModel "perms-system-server/internal/model/perm"
- roleModel "perms-system-server/internal/model/role"
- "perms-system-server/internal/model/roleperm"
- "perms-system-server/internal/model/userrole"
- "perms-system-server/internal/response"
- "perms-system-server/internal/svc"
- "perms-system-server/internal/testutil"
- "perms-system-server/internal/testutil/ctxhelper"
- "perms-system-server/internal/types"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "github.com/zeromicro/go-zero/core/stores/redis"
- )
- // TC-0126: 正常删除+级联
- func TestDeleteRole_WithCascading(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- pc := testutil.UniqueId()
- roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- roleId, _ := roleRes.LastInsertId()
- pRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
- ProductCode: pc, Name: testutil.UniqueId(), Code: testutil.UniqueId(),
- Status: 1, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- permId, _ := pRes.LastInsertId()
- rpRes, err := svcCtx.SysRolePermModel.Insert(ctx, &roleperm.SysRolePerm{
- RoleId: roleId, PermId: permId, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- rpId, _ := rpRes.LastInsertId()
- urRes, err := svcCtx.SysUserRoleModel.Insert(ctx, &userrole.SysUserRole{
- UserId: 888888, RoleId: roleId, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- urId, _ := urRes.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_role`", roleId)
- testutil.CleanTable(ctx, conn, "`sys_perm`", permId)
- })
- logic := NewDeleteRoleLogic(ctx, svcCtx)
- err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
- require.NoError(t, err)
- _, err = svcCtx.SysRoleModel.FindOne(ctx, roleId)
- assert.Error(t, err)
- permIds, err := svcCtx.SysRolePermModel.FindPermIdsByRoleId(ctx, roleId)
- require.NoError(t, err)
- assert.Empty(t, permIds)
- roleIds, err := svcCtx.SysUserRoleModel.FindRoleIdsByUserId(ctx, 888888)
- require.NoError(t, err)
- assert.NotContains(t, roleIds, roleId)
- }
- // TC-0128: 无关联数据
- func TestDeleteRole_NoAssociations(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- pc := testutil.UniqueId()
- roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- roleId, _ := roleRes.LastInsertId()
- t.Cleanup(func() {
- testutil.CleanTable(ctx, conn, "`sys_role`", roleId)
- })
- logic := NewDeleteRoleLogic(ctx, svcCtx)
- err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
- require.NoError(t, err)
- _, err = svcCtx.SysRoleModel.FindOne(ctx, roleId)
- assert.Error(t, err)
- }
- // TC-1120: L-R14-1 非超管 DeleteRole 针对别产品的 roleId 必须 404 "角色不存在",
- // 与 RoleDetail 的 M-N3 / UpdateRole 的 TC-1119 口径一致。
- func TestDeleteRole_L_R14_1_CrossProductReturns404(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- otherProduct := "l_r14_1_del_" + testutil.UniqueId()
- res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: otherProduct, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- roleId, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
- adminCtx := ctxhelper.AdminCtx("test_product")
- err = NewDeleteRoleLogic(adminCtx, svcCtx).DeleteRole(&types.DeleteRoleReq{Id: roleId})
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 404, ce.Code(), "跨产品 roleId 必须 404")
- assert.Equal(t, "角色不存在", ce.Error(), "文案必须与 id 不存在完全一致")
- // DB 必须保持原状
- still, err := svcCtx.SysRoleModel.FindOne(ctx, roleId)
- require.NoError(t, err, "跨产品拒绝的 DeleteRole 不得真的删角色")
- assert.Equal(t, roleId, still.Id)
- }
- // TC-1201: H-R17-2 —— DeleteRole 成功后必须在 post-commit 上显式失效 sysRole 的两把缓存键
- // (id key + productCode:name key),与 DeleteDept 的 H-R17-2 同构。
- //
- // 失效窗口:DeleteWithTx 里 sqlc.CachedConn.ExecCtx 的 "exec → DelCache" 钩子先于外层
- // TransactCtx commit 执行。commit 前并发 FindOne / FindOneByProductCodeName 会把旧行
- // 回填回 Redis,commit 后 DB 没了 role 但 Redis 还挂着"幽灵 role 快照"到 TTL。叠加
- // 角色树权限模型,后续 ResolveOwnRoleOr404 命中幽灵 snapshot 会产生谜之 409(CAS
- // 命中但 DB 无行),排障代价巨大——post-commit 必须再补一次 InvalidateRoleCache。
- func TestDeleteRole_H_R17_2_InvalidatesCacheAfterCommit(t *testing.T) {
- ctx := ctxhelper.SuperAdminCtx()
- cfg := testutil.GetTestConfig()
- svcCtx := svc.NewServiceContext(cfg)
- conn := testutil.GetTestSqlConn()
- rds := redis.MustNewRedis(cfg.CacheRedis.Nodes[0].RedisConf)
- now := time.Now().Unix()
- pc := "h_r17_2_" + testutil.UniqueId()
- roleName := "h_r17_2_role_" + testutil.UniqueId()
- res, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: pc, Name: roleName, Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- roleId, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
- idKey := fmt.Sprintf("%s:cache:sysRole:id:%d", cfg.CacheRedis.KeyPrefix, roleId)
- nameKey := fmt.Sprintf("%s:cache:sysRole:productCode:name:%s:%s",
- cfg.CacheRedis.KeyPrefix, pc, roleName)
- t.Cleanup(func() { _, _ = rds.Del(idKey, nameKey) })
- require.NoError(t, rds.Set(idKey,
- fmt.Sprintf(`{"Id":%d,"ProductCode":%q,"Name":%q,"PermsLevel":1,"Status":1}`, roleId, pc, roleName)),
- "预置 ghost idKey 失败")
- require.NoError(t, rds.Set(nameKey,
- fmt.Sprintf(`{"Id":%d,"ProductCode":%q,"Name":%q,"PermsLevel":1,"Status":1}`, roleId, pc, roleName)),
- "预置 ghost nameKey 失败")
- require.NoError(t, NewDeleteRoleLogic(ctx, svcCtx).DeleteRole(&types.DeleteRoleReq{Id: roleId}))
- existsId, err := rds.Exists(idKey)
- require.NoError(t, err)
- assert.False(t, existsId,
- "H-R17-2:commit 之后 InvalidateRoleCache 必须 DEL sysRoleIdKey,"+
- "否则后续 FindOne(roleId) 命中 ghost snapshot → CAS 撞不到行回 409")
- existsName, err := rds.Exists(nameKey)
- require.NoError(t, err)
- assert.False(t, existsName,
- "H-R17-2:InvalidateRoleCache 必须同时 DEL sysRoleProductCodeNameKey,"+
- "因为 FindOneByProductCodeName 是 CreateRole 前的唯一性校验路径,"+
- "残留 ghost 会让同名 role 在删除后反复被 409 误判为冲突")
- }
- // TC-0540: deleteRole非管理员拒绝
- func TestDeleteRole_MemberRejected(t *testing.T) {
- pc := "test_product"
- ctx := ctxhelper.MemberCtx(pc)
- svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
- conn := testutil.GetTestSqlConn()
- now := time.Now().Unix()
- roleRes, err := svcCtx.SysRoleModel.Insert(ctx, &roleModel.SysRole{
- ProductCode: pc, Name: testutil.UniqueId(), Status: 1, PermsLevel: 1,
- CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- roleId, _ := roleRes.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_role`", roleId) })
- logic := NewDeleteRoleLogic(ctx, svcCtx)
- err = logic.DeleteRole(&types.DeleteRoleReq{Id: roleId})
- require.Error(t, err)
- var ce *response.CodeError
- require.True(t, errors.As(err, &ce))
- assert.Equal(t, 403, ce.Code())
- }
|