package resx_test import ( "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/samber/do/v2" "github.com/stretchr/testify/suite" "git.fsdpf.net/go/db" "git.fsdpf.net/go/db/engine" "git.fsdpf.net/go/req" "git.fsdpf.net/go/req/resx" _ "git.fsdpf.net/go/db/dialect/mysql" ) // permissionTest 验证 req.WithPermission(req.ResFlags) 各个取值(不传/传 0/单独 ResRow/单独 // ResMask/ResAll)分别独立生效:ResRow 控制行级过滤条件要不要拼进查询,ResMask 控制字段级脱敏 // 要不要生效,两者互不影响彼此。GetDBTable 不传 WithPermission 时按文档约定的零值处理(两者都 // 跳过),这里也显式测出来,作为这个默认行为的回归防护——之后如果谁想改这个默认值,这些测试会 // 先失败提醒。 type permissionTest struct { suite.Suite } func TestPermission(t *testing.T) { suite.Run(t, new(permissionTest)) } // newPermissionResource 构造一个既配置了受限字段(salary,Roles=hr)、又注册了行级过滤条件 // (owned_user = 当前用户)的资源,用同一份数据分别验证 ResRow/ResMask 两个开关。 func (t *permissionTest) newPermissionResource() (req.Resource, sqlmock.Sqlmock) { mockDB, mock, err := sqlmock.New() t.Require().NoError(err) app := do.New() do.Provide(app, func(i do.Injector) (engine.Engine, error) { return engine.Mock(map[string]engine.MockDBConfig{ "default": {Driver: "mysql", Mock: mockDB}, }), nil }) do.Provide(app, func(i do.Injector) (resx.DataInterceptor, error) { return func(res req.Resource, u req.User, category resx.ResEventType) (*db.SelectDataset, db.Expression, resx.ResChangeRowFunc) { return nil, db.C("owned_user").Eq(u.Uuid()), nil }, nil }) res := resx.New(app, "User", "users", resx.WithConn("default"), resx.WithFields(nameField, salaryField), ) return res, mock } // TestNoPermissionOption_SkipsBothRowFilterAndMask 不传 WithPermission:Permission 零值,行级 // 过滤和字段脱敏都跳过——这是 options.go 里 Permission 字段注释明确写的默认行为 func (t *permissionTest) TestNoPermissionOption_SkipsBothRowFilterAndMask() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}).Select(db.Star()).Executor().ToSQL() t.NotContains(sql, "owned_user", "不传 WithPermission 时行级过滤应该被跳过") t.NotContains(sql, "-999999999", "不传 WithPermission 时字段脱敏应该被跳过") } // TestExplicitZeroPermission_SkipsBothRowFilterAndMask 显式传 WithPermission(0),效果跟不传一样 func (t *permissionTest) TestExplicitZeroPermission_SkipsBothRowFilterAndMask() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}, req.WithPermission(0)).Select(db.Star()).Executor().ToSQL() t.NotContains(sql, "owned_user") t.NotContains(sql, "-999999999") } // TestResRowOnly_EnablesRowFilterOnlyNotMask 只传 ResRow:行级过滤生效,字段脱敏依旧跳过——两个 // 开关相互独立,不是绑在一起的 func (t *permissionTest) TestResRowOnly_EnablesRowFilterOnlyNotMask() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}, req.WithPermission(req.ResRow)).Select(db.Star()).Executor().ToSQL() t.Contains(sql, "owned_user", "传了 ResRow,行级过滤应该生效") t.NotContains(sql, "-999999999", "没传 ResMask,字段脱敏不该生效") } // TestResMaskOnly_EnablesMaskOnlyNotRowFilter 只传 ResMask:字段脱敏生效,行级过滤依旧跳过 func (t *permissionTest) TestResMaskOnly_EnablesMaskOnlyNotRowFilter() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}, req.WithPermission(req.ResMask)).Select(db.Star()).Executor().ToSQL() t.Contains(sql, "-999999999 AS `salary`", "传了 ResMask,字段脱敏应该生效") t.NotContains(sql, "owned_user", "没传 ResRow,行级过滤不该生效") } // TestResAll_EnablesBothRowFilterAndMask 传 ResAll:行级过滤和字段脱敏都生效 func (t *permissionTest) TestResAll_EnablesBothRowFilterAndMask() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}, req.WithPermission(req.ResAll)).Select(db.Star()).Executor().ToSQL() t.Contains(sql, "owned_user") t.Contains(sql, "-999999999 AS `salary`") } // TestResRowOrResMaskBitwiseCombo_SameAsResAll 手动按位组合 ResRow|ResMask,效果跟直接传 ResAll // 一样——ResAll 只是这个组合的一个别名,不是什么特殊值 func (t *permissionTest) TestResRowOrResMaskBitwiseCombo_SameAsResAll() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{}, req.WithPermission(req.ResRow|req.ResMask)).Select(db.Star()).Executor().ToSQL() t.Contains(sql, "owned_user") t.Contains(sql, "-999999999 AS `salary`") } // TestUserHasRole_ResMaskStillOnButFieldPassesThrough ResMask 开启时,用户角色命中字段 Roles 的话 // 字段本身按真实值查询——ResMask 只是"开启检查",不是"无条件脱敏",命中权限时检查结果是放行 func (t *permissionTest) TestUserHasRole_ResMaskStillOnButFieldPassesThrough() { res, _ := t.newPermissionResource() sql, _, _ := res.GetDBTable(testUser{roles: []string{"hr"}}, req.WithPermission(req.ResMask)). Select(db.Star()).Executor().ToSQL() t.Contains(sql, "SELECT *", "没有字段需要脱敏时不会展开成显式列表") t.NotContains(sql, "-999999999") }