Smail/backend/services/accountgroup/service_test.go
smail 4cf1de87d3
Some checks are pending
Security Checks / security (push) Waiting to run
修复管理员分组版本校验和受限保存
修复管理员用户列表成员版本缺失导致的多分组保存失败,并保留管理员不可管理分组的成员关系,补充回归测试。
2026-10-02 06:26:18 +08:00

170 lines
5.7 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 accountgroup
import (
"testing"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/services/access"
_ "modernc.org/sqlite"
"xorm.io/xorm"
)
func TestCanAccessExcludesSuperAdminFromAdminScope(t *testing.T) {
engine, err := xorm.NewEngine("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer engine.Close()
if err := engine.Sync2(new(models.User), new(models.AccountGroupMember)); err != nil {
t.Fatal(err)
}
superAdmin := models.User{Account: "superadmin", Name: "超级管理员", Role: string(access.RoleSuperAdmin)}
admin := models.User{Account: "admin", Name: "分组管理员", Role: string(access.RoleAdmin), IsAdmin: 1}
member := models.User{Account: "member", Name: "普通用户", Role: string(access.RoleUser)}
if _, err := engine.Insert(&superAdmin, &admin, &member); err != nil {
t.Fatal(err)
}
if _, err := engine.Insert(
&models.AccountGroupMember{UserID: admin.ID, GroupID: 1},
&models.AccountGroupMember{UserID: member.ID, GroupID: 1},
); err != nil {
t.Fatal(err)
}
if CanAccess(engine, admin.ID, access.RoleAdmin, superAdmin.ID) {
t.Fatal("管理员不应访问超级管理员账户")
}
if !CanAccess(engine, admin.ID, access.RoleAdmin, member.ID) {
t.Fatal("管理员应访问同组普通用户账户")
}
}
func TestMembershipScopeGuardLimitsAdminToOwnedGroups(t *testing.T) {
engine, err := xorm.NewEngine("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer engine.Close()
if err := engine.Sync2(new(models.User), new(models.AccountGroup), new(models.AccountGroupMember)); err != nil {
t.Fatal(err)
}
admin := models.User{Account: "admin", Name: "分组管理员", Role: string(access.RoleAdmin), IsAdmin: 1}
superAdmin := models.User{Account: "superadmin", Name: "超级管理员", Role: string(access.RoleSuperAdmin)}
if _, err := engine.Insert(&admin, &superAdmin); err != nil {
t.Fatal(err)
}
if _, err := engine.Insert(
&models.AccountGroup{ID: 1, Name: "一组"},
&models.AccountGroup{ID: 2, Name: "二组"},
&models.AccountGroupMember{UserID: admin.ID, GroupID: 1},
); err != nil {
t.Fatal(err)
}
tx := engine.NewSession()
defer tx.Close()
if err := tx.Begin(); err != nil {
t.Fatal(err)
}
defer tx.Rollback()
if err := MembershipScopeGuard(admin.ID, access.RoleAdmin, []int{1})(tx); err != nil {
t.Fatalf("管理员应能管理所属分组:%v", err)
}
if err := MembershipScopeGuard(admin.ID, access.RoleAdmin, []int{2})(tx); err != ErrDenied {
t.Fatalf("管理员不应管理其他分组,得到:%v", err)
}
if err := MembershipScopeGuard(superAdmin.ID, access.RoleSuperAdmin, []int{2})(tx); err != nil {
t.Fatalf("超级管理员应能管理全部分组:%v", err)
}
}
func TestMembershipScopeGuardUsesDefaultGroupWhenAdminHasNoMembership(t *testing.T) {
engine, err := xorm.NewEngine("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer engine.Close()
if err := engine.Sync2(new(models.User), new(models.AccountGroupMember)); err != nil {
t.Fatal(err)
}
admin := models.User{Account: "admin", Name: "默认分组管理员", Role: string(access.RoleAdmin), IsAdmin: 1}
if _, err := engine.Insert(&admin); err != nil {
t.Fatal(err)
}
tx := engine.NewSession()
defer tx.Close()
if err := tx.Begin(); err != nil {
t.Fatal(err)
}
defer tx.Rollback()
if err := MembershipScopeGuard(admin.ID, access.RoleAdmin, []int{0})(tx); err != nil {
t.Fatalf("无明确分组的管理员应能管理默认分组:%v", err)
}
if err := MembershipScopeGuard(admin.ID, access.RoleAdmin, []int{1})(tx); err != ErrDenied {
t.Fatalf("无明确分组的管理员不应管理其他分组,得到:%v", err)
}
}
func TestValidateMembershipKeepsAdminMultiGroupAndUserSingleGroupRules(t *testing.T) {
engine, err := xorm.NewEngine("sqlite", ":memory:")
if err != nil {
t.Fatal(err)
}
defer engine.Close()
if err := engine.Sync2(new(models.AccountGroup)); err != nil {
t.Fatal(err)
}
if _, err := engine.Insert(&models.AccountGroup{ID: 1, Name: "一组"}, &models.AccountGroup{ID: 2, Name: "二组"}); err != nil {
t.Fatal(err)
}
tx := engine.NewSession()
defer tx.Close()
if ids, err := ValidateMembership(tx, access.RoleAdmin, []int{1, 2}); err != nil || len(ids) != 2 {
t.Fatalf("管理员应允许多个分组,ids=%v err=%v", ids, err)
}
if _, err := ValidateMembership(tx, access.RoleUser, []int{1, 2}); err != ErrInvalid {
t.Fatalf("普通用户不应允许多个分组,得到:%v", err)
}
}
func TestSetScopedMembershipPreservesGroupsOutsideAdminScope(t *testing.T) {
engine, err := xorm.NewEngine(`sqlite`, `:memory:`)
if err != nil {
t.Fatal(err)
}
defer engine.Close()
if err := engine.Sync2(new(models.User), new(models.AccountGroup), new(models.AccountGroupMember)); err != nil {
t.Fatal(err)
}
target := models.User{Account: `target`, Name: `Target`, Role: string(access.RoleUser), SessionVersion: 1, SecurityVersion: 1}
if _, err := engine.Insert(&target); err != nil {
t.Fatal(err)
}
if _, err := engine.Insert(&models.AccountGroup{ID: 1, Name: `visible`}, &models.AccountGroup{ID: 2, Name: `hidden`}); err != nil {
t.Fatal(err)
}
if _, err := engine.Insert(&models.AccountGroupMember{UserID: target.ID, GroupID: 1}, &models.AccountGroupMember{UserID: target.ID, GroupID: 2}); err != nil {
t.Fatal(err)
}
allow := func(*xorm.Session) error { return nil }
if err := SetScopedMembership(engine, target, []int{0}, []int{0, 1}, allow); err != nil {
t.Fatal(err)
}
rows := make([]models.AccountGroupMember, 0)
if err := engine.Where(`user_id = ?`, target.ID).Asc(`group_id`).Find(&rows); err != nil {
t.Fatal(err)
}
if len(rows) != 2 || rows[0].GroupID != 0 || rows[1].GroupID != 2 {
t.Fatalf(`scoped membership changed an inaccessible group: %+v`, rows)
}
}