Files
iotlcm/server/internal/logic/agent/agent_dept_passrole_test.go
T
2026-10-29 14:36:49 +08:00

189 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package agent
import (
"context"
"testing"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
_ "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/memorydb"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/system"
)
// 复现生产形态:模板角色已配置(role 18 @ dept 100,is_pass=1),
// 一级代理的默认角色是创建代理时复制下来的(is_pass=0)。
// 二级代理(dept 1862, parent 128)要再创建下级时,必须能从本机构默认角色拿到权限。
func TestPassRoleByDeptWorksForChildAgentWithoutTemplateFlag(t *testing.T) {
setupPassRoleTestDB(t)
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
ID: 1,
TenantId: "000000",
DeptId: 128, // 一级代理,要继续创建二级代理
}}})
// 三级结构:平台 100(模板 18,is_pass=1)→ 一级代理 128(默认角色 2321,is_pass=0)→ 二级代理 1862(默认角色 1779,is_pass=0)
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES
(100,'000000',0,1,'0',1),
(128,'000000',100,1,'0',2321),
(1862,'000000',128,1,'0',1779)`)
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
(18,'000000',100,'代理商','dl','0','4',1,100),
(2321,'000000',128,'代理商','dl','0','4',0,128),
(1779,'000000',1862,'代理商','dl','0','4',0,100)`)
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES
('000000',18,100,'0'),('000000',2321,100,'0'),('000000',1779,100,'0')`)
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
service := NewAgentDept()
role, menus, err := service.currentDeptPassRole(ctx)
if err != nil {
t.Fatalf("一级代理创建下级时应能取到本机构默认角色权限,实际报错: %v", err)
}
if role == nil || role.RoleId != 2321 {
t.Fatalf("应以本机构默认角色 2321 作为下发来源,实际: %+v", role)
}
if len(menus) != 1 || menus[0].MenuId != 100 {
t.Fatalf("应复制本机构默认角色的菜单,实际: %+v", menus)
}
}
func setupPassRoleTestDB(t *testing.T) {
t.Helper()
service.RegisterSysDept(system.NewSysDept())
service.RegisterSysRole(system.NewSysRole())
if err := gdb.SetConfig(gdb.Config{
"default": gdb.ConfigGroup{
{Type: "sqlite", Link: "sqlite::@file(:memory:)", Debug: false},
},
}); err != nil {
t.Fatalf("configure sqlite failed: %v", err)
}
// 走真实部署形态:内存库指向主库(config.yaml 里 link 为空时回落主库)
if err := g.Cfg().GetAdapter().(*gcfg.AdapterFile).Set("database.memorydb.link", ""); err != nil {
t.Fatalf("override memorydb link failed: %v", err)
}
ctx := context.Background()
t.Cleanup(func() { _ = g.DB().Close(ctx) })
mustExec(t, ctx, `CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_name TEXT, dept_type INTEGER, status TEXT DEFAULT '0', dept_role INTEGER, deleted_at DATETIME)`)
mustExec(t, ctx, `CREATE TABLE sys_config (config_id INTEGER PRIMARY KEY, tenant_id TEXT, config_name TEXT, config_key TEXT, config_value TEXT, config_type TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, remark TEXT)`)
mustExec(t, ctx, `CREATE TABLE sys_role (
role_id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id TEXT, dept_id INTEGER, role_name TEXT, role_key TEXT, role_sort INTEGER, data_scope TEXT,
menu_check_strictly INTEGER, dept_check_strictly INTEGER, status TEXT, is_pass INTEGER, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, deleted_by INTEGER, deleted_at DATETIME, remark TEXT)`)
mustExec(t, ctx, `CREATE TABLE sys_role_menu (tenant_id TEXT, role_id INTEGER, menu_id INTEGER, data_scope TEXT)`)
mustExec(t, ctx, `CREATE TABLE sys_role_dept (tenant_id TEXT, role_id INTEGER, dept_id INTEGER)`)
mustExec(t, ctx, `CREATE TABLE sys_user_role (tenant_id TEXT DEFAULT '000000', user_id INTEGER, role_id INTEGER)`)
_ = consts.SysRoleDataScopeDeptAndBelow
}
func mustExec(t *testing.T, ctx context.Context, sql string) {
t.Helper()
if _, err := g.DB().Exec(ctx, sql); err != nil {
t.Fatalf("execute sql failed: %v\nsql=%s", err, sql)
}
}
// 平台自身(dept 100 = 模板所属机构)仍以模板角色为下发来源。
func TestPassRoleByDeptTemplateOwnerUsesTemplateRole(t *testing.T) {
setupPassRoleTestDB(t)
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
ID: 1, TenantId: "000000", DeptId: 100,
}}})
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (100,'000000',0,1,'0',1)`)
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
(1,'000000',100,'超级管理员','admin','0','1',0,100),
(18,'000000',100,'代理商','dl','0','4',1,100)`)
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',18,100,'0')`)
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
role, menus, err := NewAgentDept().currentDeptPassRole(ctx)
if err != nil {
t.Fatalf("平台创建一级代理应成功,实际: %v", err)
}
if role.RoleId != 18 {
t.Fatalf("平台下发来源应为模板角色 18,实际: %d", role.RoleId)
}
if len(menus) != 1 || menus[0].MenuId != 100 {
t.Fatalf("应复制模板菜单,实际: %+v", menus)
}
}
// 默认角色缺失/停用/归属不符时必须明确报错,不能静默 0 权限。
func TestPassRoleByDeptRejectsBrokenDefaultRole(t *testing.T) {
cases := []struct {
name string
sql []string
}{
{
name: "默认角色不存在",
sql: []string{
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',9999)`,
},
},
{
name: "默认角色已停用",
sql: []string{
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`,
`INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',128,'代理商','dl','1','4',0,128)`,
},
},
{
name: "默认角色归属其它机构",
sql: []string{
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`,
`INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',129,'代理商','dl','0','4',0,100)`,
},
},
{
name: "本机构未绑定默认角色",
sql: []string{
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',0)`,
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
setupPassRoleTestDB(t)
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
ID: 1, TenantId: "000000", DeptId: 128,
}}})
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (100,'000000',0,1,'0',1)`)
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (18,'000000',100,'代理商','dl','0','4',1,100)`)
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',18,100,'0')`)
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
for _, sql := range tc.sql {
mustExec(t, ctx, sql)
}
if _, _, err := NewAgentDept().currentDeptPassRole(ctx); err == nil {
t.Fatal("默认角色异常时必须报错,不能静默下发空权限")
}
})
}
}
// 未配置平台模板时,保留原 is_pass=1 唯一角色的下发流程。
func TestPassRoleByDeptKeepsLegacyFlowWithoutTemplate(t *testing.T) {
setupPassRoleTestDB(t)
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
ID: 1, TenantId: "000000", DeptId: 128,
}}})
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`)
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',128,'代理商','dl','0','4',1,128)`)
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',2321,100,'0')`)
role, menus, err := NewAgentDept().currentDeptPassRole(ctx)
if err != nil {
t.Fatalf("未配模板时应走原有 is_pass 流程,实际: %v", err)
}
if role.RoleId != 2321 || len(menus) != 1 {
t.Fatalf("旧流程结果不符: role=%+v menus=%+v", role, menus)
}
}