kra-oa/internal/data/system/authority_test.go

582 lines
23 KiB
Go

package system
import (
"context"
"errors"
"testing"
"kra/internal/biz"
"kra/internal/conf"
"gorm.io/gorm"
)
func authorityUintPointer(value uint) *uint { return &value }
func enableStrictAuthorityTestMode(data *Data) {
currentData, _ := data.runtime.Values()
data.runtime.Replace(currentData, &conf.AdminBackend{System: &conf.AdminBackend_System{UseStrictAuth: true}})
}
func TestCopyAuthorityStrictPolicyValidationMatchesAdministrationContract(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID := uint(888)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, AuthorityName: "root", ParentID: authorityUintPointer(0)},
{AuthorityID: 900, AuthorityName: "source", ParentID: &rootID},
{AuthorityID: 901, AuthorityName: "stale-source", ParentID: &rootID},
}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&apiPO{Path: "/registered", Method: "GET"}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]casbinRulePO{
newPolicyRule(900, "/registered", "GET"),
newPolicyRule(901, "/stale", "POST"),
// A root role is allowed to grant registered APIs without already
// holding the policy, but a stale policy is still not a registered API.
newPolicyRule(rootID, "/stale", "POST"),
}).Error; err != nil {
t.Fatal(err)
}
repo := &authorityAccessRepo{data: data}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: rootID})
created := &biz.Authority{AuthorityID: 910, AuthorityName: "copy", ParentID: &rootID}
if err := repo.CopyAuthority(ctx, 900, created); err != nil {
t.Fatalf("root copy of a registered API failed: %v", err)
}
if exists, err := policyExists(db, created.AuthorityID, "/registered", "GET"); err != nil || !exists {
t.Fatalf("copied policy exists = %v, err = %v", exists, err)
}
staleCopy := &biz.Authority{AuthorityID: 911, AuthorityName: "stale-copy", ParentID: &rootID}
if err := repo.CopyAuthority(ctx, 901, staleCopy); err == nil || err.Error() != "存在api不在权限列表中" {
t.Fatalf("stale API copy error = %v", err)
}
var count int64
if err := db.Unscoped().Model(&authorityPO{}).Where("authority_id = ?", staleCopy.AuthorityID).Count(&count).Error; err != nil {
t.Fatal(err)
}
if count != 0 {
t.Fatalf("failed copy persisted target authority: count=%d", count)
}
}
func TestCopyAuthorityDuplicateIDWinsOverStrictParentValidation(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID := uint(888)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, AuthorityName: "root", ParentID: authorityUintPointer(0)},
{AuthorityID: 920, AuthorityName: "existing", ParentID: &rootID},
}).Error; err != nil {
t.Fatal(err)
}
invalidParent := uint(999999)
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: rootID})
err := (&authorityAccessRepo{data: data}).CopyAuthority(ctx, 0, &biz.Authority{AuthorityID: 920, AuthorityName: "duplicate", ParentID: &invalidParent})
if err == nil || err.Error() != "存在相同角色id" {
t.Fatalf("duplicate copy error = %v", err)
}
}
func TestListAuthoritiesRequiresCurrentAuthorityOutsideStrictMode(t *testing.T) {
data := newTransactionTestData(t)
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: 999999})
if _, err := (&authorityAccessRepo{data: data}).ListAuthorities(ctx); !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("missing current authority error = %v", err)
}
}
func TestDeleteAuthorityIgnoresOrphanedUserAssociation(t *testing.T) {
data := newTransactionTestData(t)
ctx := context.Background()
db := data.gormDB.WithContext(ctx)
if err := db.Create(&authorityPO{AuthorityID: 930, AuthorityName: "orphan-links"}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&userAuthorityPO{SysUserID: 999999, SysAuthorityAuthorityID: 930}).Error; err != nil {
t.Fatal(err)
}
if err := (&authorityAccessRepo{data: data}).DeleteAuthority(ctx, 930); err != nil {
t.Fatalf("delete with orphaned user association: %v", err)
}
var authority authorityPO
if err := db.Unscoped().Where("authority_id = ?", 930).First(&authority).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("deleted authority lookup error = %v", err)
}
var links int64
if err := db.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", 930).Count(&links).Error; err != nil {
t.Fatal(err)
}
if links != 0 {
t.Fatalf("orphaned user associations were not removed: %d", links)
}
}
func TestSetAuthorityUsersKeepsPrimaryRoleFallbackBehavior(t *testing.T) {
data := newTransactionTestData(t)
ctx := context.Background()
db := data.gormDB.WithContext(ctx)
if err := db.Create(&[]authorityPO{{AuthorityID: 940, AuthorityName: "removed"}, {AuthorityID: 941, AuthorityName: "fallback"}}).Error; err != nil {
t.Fatal(err)
}
users := []userPO{
{Username: "with-fallback", Password: "hash", AuthorityID: 940, Enable: 1},
{Username: "without-fallback", Password: "hash", AuthorityID: 940, Enable: 1},
}
if err := db.Create(&users).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]userAuthorityPO{
{SysUserID: users[0].ID, SysAuthorityAuthorityID: 940},
{SysUserID: users[0].ID, SysAuthorityAuthorityID: 941},
{SysUserID: users[1].ID, SysAuthorityAuthorityID: 940},
}).Error; err != nil {
t.Fatal(err)
}
if err := (&authorityAccessRepo{data: data}).SetAuthorityUsers(ctx, 940, nil); err != nil {
t.Fatal(err)
}
var persisted []userPO
if err := db.Where("id IN ?", []uint{users[0].ID, users[1].ID}).Order("id").Find(&persisted).Error; err != nil {
t.Fatal(err)
}
if len(persisted) != 2 || persisted[0].AuthorityID != 941 || persisted[1].AuthorityID != 940 {
t.Fatalf("primary authority fallback = %+v", persisted)
}
}
func TestAuthorityCustomDataScopeUsesCompatibleColumns(t *testing.T) {
data := newTransactionTestData(t)
ctx := context.Background()
repo := &authorityAccessRepo{data: data}
if err := repo.SetDataScope(ctx, 950, 5, []uint{7, 7, 8}); err != nil {
t.Fatal(err)
}
ids, err := repo.DataScopeDepartmentIDs(ctx, 950)
if err != nil {
t.Fatal(err)
}
if len(ids) != 3 || ids[0] != 7 || ids[1] != 7 || ids[2] != 8 {
t.Fatalf("custom department IDs = %v", ids)
}
if err = repo.SetDataScope(ctx, 950, 3, []uint{9}); err != nil {
t.Fatal(err)
}
ids, err = repo.DataScopeDepartmentIDs(ctx, 950)
if err != nil {
t.Fatal(err)
}
if len(ids) != 0 {
t.Fatalf("non-custom scope retained departments: %v", ids)
}
}
func TestSetAuthorityUsersStrictRejectsUsersOutsideManagedRoles(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, childID, siblingID := uint(888), uint(1000), uint(1001), uint(2000)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
{AuthorityID: childID, ParentID: &actorID},
{AuthorityID: siblingID, ParentID: &rootID},
}).Error; err != nil {
t.Fatal(err)
}
users := []userPO{
{Username: "managed", Password: "hash", AuthorityID: childID, Enable: 1},
{Username: "outside", Password: "hash", AuthorityID: siblingID, Enable: 1},
}
if err := db.Create(&users).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
if err := repo.SetAuthorityUsers(ctx, childID, []uint{users[1].ID}); err == nil {
t.Fatal("SetAuthorityUsers() accepted a user outside the managed role tree")
}
if err := repo.SetAuthorityUsers(ctx, childID, []uint{users[0].ID}); err != nil {
t.Fatalf("SetAuthorityUsers() rejected a managed user: %v", err)
}
}
func TestSetAuthorityUsersStrictRejectsMixedRoleUserAlreadyLinked(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, childID, siblingID := uint(888), uint(1100), uint(1101), uint(2100)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
{AuthorityID: childID, ParentID: &actorID},
{AuthorityID: siblingID, ParentID: &rootID},
}).Error; err != nil {
t.Fatal(err)
}
user := userPO{Username: "mixed", Password: "hash", AuthorityID: childID, Enable: 1}
if err := db.Create(&user).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]userAuthorityPO{
{SysUserID: user.ID, SysAuthorityAuthorityID: childID},
{SysUserID: user.ID, SysAuthorityAuthorityID: siblingID},
}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
if err := (&authorityAccessRepo{data: data}).SetAuthorityUsers(ctx, childID, nil); err == nil {
t.Fatal("SetAuthorityUsers() modified a linked user that also has an out-of-scope role")
}
var count int64
if err := db.Model(&userAuthorityPO{}).Where("sys_user_id = ? AND sys_authority_authority_id = ?", user.ID, childID).Count(&count).Error; err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatalf("rejected update changed the existing target-role link: count=%d", count)
}
}
func TestUpdateAuthorityStrictRejectsHierarchyCycle(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, targetID, childID := uint(888), uint(1200), uint(1201), uint(1202)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
{AuthorityID: targetID, AuthorityName: "target", ParentID: &actorID},
{AuthorityID: childID, ParentID: &targetID},
}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
err := (&authorityAccessRepo{data: data}).UpdateAuthority(ctx, &biz.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &childID})
if err == nil {
t.Fatal("UpdateAuthority() accepted a parent that forms a cycle")
}
var stored authorityPO
if err := db.Where("authority_id = ?", targetID).First(&stored).Error; err != nil {
t.Fatal(err)
}
if stored.ParentID == nil || *stored.ParentID != actorID {
t.Fatalf("rejected update changed parent_id: %+v", stored.ParentID)
}
}
func TestSetDataScopeValidatesScopeAndStrictDepartments(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, childID := uint(888), uint(1300), uint(1301)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
{AuthorityID: childID, ParentID: &actorID},
}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
if err := repo.SetDataScope(ctx, childID, 0, nil); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("SetDataScope() invalid scope error = %v", err)
}
if err := repo.SetDataScope(ctx, childID, 5, []uint{999999}); err == nil {
t.Fatal("SetDataScope() accepted a missing department in strict mode")
}
department := departmentPO{Name: "managed"}
if err := db.Create(&department).Error; err != nil {
t.Fatal(err)
}
if err := repo.SetDataScope(ctx, childID, 5, []uint{department.ID}); err != nil {
t.Fatalf("SetDataScope() rejected an existing department: %v", err)
}
}
func TestSetDataScopeStrictRejectsDepartmentsOutsideActorScope(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, childID := uint(888), uint(1350), uint(1351)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID, DataScope: 5},
{AuthorityID: childID, ParentID: &actorID},
}).Error; err != nil {
t.Fatal(err)
}
departments := []departmentPO{{Name: "allowed"}, {Name: "outside"}}
if err := db.Create(&departments).Error; err != nil {
t.Fatal(err)
}
actorUser := userPO{Username: "scope-actor", Password: "hash", AuthorityID: actorID, DeptID: departments[0].ID, Enable: 1}
if err := db.Create(&actorUser).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&authorityDepartmentPO{AuthorityID: actorID, DepartmentID: departments[0].ID}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: actorUser.ID, AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
if err := repo.SetDataScope(ctx, childID, 5, []uint{departments[1].ID}); err == nil {
t.Fatal("SetDataScope() accepted a department outside the actor's data scope")
}
if err := repo.SetDataScope(ctx, childID, 5, []uint{departments[0].ID}); err != nil {
t.Fatalf("SetDataScope() rejected a department inside the actor's data scope: %v", err)
}
}
func TestStrictDataScopeGrantRejectsBroaderChildScopes(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, sourceID, targetID := uint(888), uint(1370), uint(1371), uint(1372)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0), DataScope: 1},
{AuthorityID: actorID, ParentID: &rootID, DataScope: 3},
{AuthorityID: sourceID, ParentID: &actorID, DataScope: 4},
{AuthorityID: targetID, AuthorityName: "target", ParentID: &actorID, DataScope: 4},
}).Error; err != nil {
t.Fatal(err)
}
actorUser := userPO{Username: "limited-scope-actor", Password: "hash", AuthorityID: actorID, Enable: 1}
if err := db.Create(&actorUser).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{UserID: actorUser.ID, AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
created := &biz.Authority{AuthorityID: 1373, AuthorityName: "created", ParentID: &actorID}
if err := repo.CreateAuthority(ctx, created); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("CreateAuthority() broader scope error = %v", err)
}
if created.DataScope != 1 {
t.Fatalf("CreateAuthority() did not normalize zero scope before validation: %d", created.DataScope)
}
copied := &biz.Authority{AuthorityID: 1374, AuthorityName: "copied", ParentID: &actorID}
if err := repo.CopyAuthority(ctx, sourceID, copied); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("CopyAuthority() broader scope error = %v", err)
}
if copied.DataScope != 1 {
t.Fatalf("CopyAuthority() did not normalize zero scope before validation: %d", copied.DataScope)
}
if err := repo.UpdateAuthority(ctx, &biz.Authority{AuthorityID: targetID, AuthorityName: "target", ParentID: &actorID, DataScope: 2}); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("UpdateAuthority() broader scope error = %v", err)
}
if err := repo.SetDataScope(ctx, targetID, 1, nil); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("SetDataScope() broader scope error = %v", err)
}
allowed := &biz.Authority{AuthorityID: 1375, AuthorityName: "self-only", ParentID: &actorID, DataScope: 4}
if err := repo.CreateAuthority(ctx, allowed); err != nil {
t.Fatalf("CreateAuthority() rejected a narrower scope: %v", err)
}
var stored authorityPO
if err := db.Where("authority_id = ?", targetID).First(&stored).Error; err != nil {
t.Fatal(err)
}
if stored.DataScope != 4 {
t.Fatalf("rejected grants changed target data scope to %d", stored.DataScope)
}
}
func TestCreateAuthorityStrictDefaultsStayWithinActorPermissions(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID := uint(888), uint(1400)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
}).Error; err != nil {
t.Fatal(err)
}
dashboard := menuPO{Name: "dashboard", Path: "dashboard"}
if err := db.Create(&dashboard).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&apiPO{Path: "/menu/getMenu", Method: "POST"}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
first := &biz.Authority{AuthorityID: 1401, AuthorityName: "no-defaults", ParentID: &actorID}
if err := repo.CreateAuthority(ctx, first); err != nil {
t.Fatal(err)
}
var menuLinks int64
if err := db.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ?", first.AuthorityID).Count(&menuLinks).Error; err != nil {
t.Fatal(err)
}
if menuLinks != 0 {
t.Fatalf("new child inherited an unowned default menu: count=%d", menuLinks)
}
if exists, err := policyExists(db, first.AuthorityID, "/menu/getMenu", "POST"); err != nil || exists {
t.Fatalf("new child inherited an unowned default API: exists=%v err=%v", exists, err)
}
if err := db.Create(&authorityMenuPO{SysAuthorityAuthorityID: actorID, SysBaseMenuID: dashboard.ID}).Error; err != nil {
t.Fatal(err)
}
policy := newPolicyRule(actorID, "/menu/getMenu", "POST")
if err := db.Create(&policy).Error; err != nil {
t.Fatal(err)
}
second := &biz.Authority{AuthorityID: 1402, AuthorityName: "owned-defaults", ParentID: &actorID}
if err := repo.CreateAuthority(ctx, second); err != nil {
t.Fatal(err)
}
if err := db.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", second.AuthorityID, dashboard.ID).Count(&menuLinks).Error; err != nil {
t.Fatal(err)
}
if menuLinks != 1 {
t.Fatalf("new child did not inherit the owned dashboard menu: count=%d", menuLinks)
}
if exists, err := policyExists(db, second.AuthorityID, "/menu/getMenu", "POST"); err != nil || !exists {
t.Fatalf("new child owned default API exists=%v err=%v", exists, err)
}
}
func TestCopyAuthorityStrictValidatesCopiedMenusAndButtons(t *testing.T) {
data := newTransactionTestData(t)
enableStrictAuthorityTestMode(data)
db := data.gormDB.WithContext(context.Background())
rootID, actorID, sourceID := uint(888), uint(1500), uint(1501)
if err := db.Create(&[]authorityPO{
{AuthorityID: rootID, ParentID: authorityUintPointer(0)},
{AuthorityID: actorID, ParentID: &rootID},
{AuthorityID: sourceID, ParentID: &actorID},
}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]menuPO{{ID: 10, Name: "owned"}, {ID: 11, Name: "unowned"}}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]menuButtonPO{{ID: 31, MenuID: 10, Name: "owned"}, {ID: 32, MenuID: 10, Name: "unowned"}}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]authorityMenuPO{
{SysAuthorityAuthorityID: actorID, SysBaseMenuID: 10},
{SysAuthorityAuthorityID: sourceID, SysBaseMenuID: 10},
{SysAuthorityAuthorityID: sourceID, SysBaseMenuID: 11},
}).Error; err != nil {
t.Fatal(err)
}
if err := db.Create(&[]authorityButtonPO{
{AuthorityID: actorID, MenuID: 10, ButtonID: 31},
{AuthorityID: sourceID, MenuID: 10, ButtonID: 31},
{AuthorityID: sourceID, MenuID: 10, ButtonID: 32},
}).Error; err != nil {
t.Fatal(err)
}
ctx := biz.NewActorContext(context.Background(), biz.Actor{AuthorityID: actorID})
repo := &authorityAccessRepo{data: data}
if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1510, AuthorityName: "menu-fail", ParentID: &actorID}); err == nil {
t.Fatal("CopyAuthority() copied a menu not assigned to the actor")
}
if err := db.Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", sourceID, 11).Delete(&authorityMenuPO{}).Error; err != nil {
t.Fatal(err)
}
if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1511, AuthorityName: "button-fail", ParentID: &actorID}); err == nil {
t.Fatal("CopyAuthority() copied a button not assigned to the actor")
}
if err := db.Where("authority_id = ? AND sys_base_menu_btn_id = ?", sourceID, 32).Delete(&authorityButtonPO{}).Error; err != nil {
t.Fatal(err)
}
if err := repo.CopyAuthority(ctx, sourceID, &biz.Authority{AuthorityID: 1512, AuthorityName: "valid", ParentID: &actorID}); err != nil {
t.Fatalf("CopyAuthority() rejected owned permissions: %v", err)
}
for _, failedID := range []uint{1510, 1511} {
var count int64
if err := db.Unscoped().Model(&authorityPO{}).Where("authority_id = ?", failedID).Count(&count).Error; err != nil {
t.Fatal(err)
}
if count != 0 {
t.Fatalf("failed copy persisted authority %d", failedID)
}
}
}
func TestAuthorityCreateAndCopyNormalizeZeroDataScope(t *testing.T) {
data := newTransactionTestData(t)
db := data.gormDB.WithContext(context.Background())
repo := &authorityAccessRepo{data: data}
created := &biz.Authority{AuthorityID: 1600, AuthorityName: "created"}
if err := repo.CreateAuthority(context.Background(), created); err != nil {
t.Fatal(err)
}
if created.DataScope != 1 {
t.Fatalf("created data scope = %d, want 1", created.DataScope)
}
if err := db.Create(&authorityPO{AuthorityID: 1601, AuthorityName: "source", DataScope: 3}).Error; err != nil {
t.Fatal(err)
}
copied := &biz.Authority{AuthorityID: 1602, AuthorityName: "copied"}
if err := repo.CopyAuthority(context.Background(), 1601, copied); err != nil {
t.Fatal(err)
}
if copied.DataScope != 1 {
t.Fatalf("copied data scope = %d, want 1", copied.DataScope)
}
var stored []authorityPO
if err := db.Where("authority_id IN ?", []uint{created.AuthorityID, copied.AuthorityID}).Order("authority_id").Find(&stored).Error; err != nil {
t.Fatal(err)
}
if len(stored) != 2 || stored[0].DataScope != 1 || stored[1].DataScope != 1 {
t.Fatalf("stored normalized data scopes = %+v", stored)
}
}
func TestAuthorityMutationsRejectInvalidDataScope(t *testing.T) {
data := newTransactionTestData(t)
db := data.gormDB.WithContext(context.Background())
repo := &authorityAccessRepo{data: data}
if err := db.Create(&[]authorityPO{
{AuthorityID: 1700, AuthorityName: "source", DataScope: 3},
{AuthorityID: 1701, AuthorityName: "target", DataScope: 3},
}).Error; err != nil {
t.Fatal(err)
}
for _, scope := range []int{-1, 6} {
if err := repo.CreateAuthority(context.Background(), &biz.Authority{AuthorityID: uint(1800 + scope + 1), AuthorityName: "invalid-create", DataScope: scope}); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("CreateAuthority() scope %d error = %v", scope, err)
}
if err := repo.CopyAuthority(context.Background(), 1700, &biz.Authority{AuthorityID: uint(1900 + scope + 1), AuthorityName: "invalid-copy", DataScope: scope}); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("CopyAuthority() scope %d error = %v", scope, err)
}
if err := repo.UpdateAuthority(context.Background(), &biz.Authority{AuthorityID: 1701, AuthorityName: "invalid-update", DataScope: scope}); !errors.Is(err, errInvalidDataScope) {
t.Fatalf("UpdateAuthority() scope %d error = %v", scope, err)
}
}
}
func TestUpdateAuthorityZeroDataScopeKeepsStoredValue(t *testing.T) {
data := newTransactionTestData(t)
db := data.gormDB.WithContext(context.Background())
if err := db.Create(&authorityPO{AuthorityID: 2000, AuthorityName: "before", DataScope: 3}).Error; err != nil {
t.Fatal(err)
}
if err := (&authorityAccessRepo{data: data}).UpdateAuthority(context.Background(), &biz.Authority{AuthorityID: 2000, AuthorityName: "after", DataScope: 0}); err != nil {
t.Fatal(err)
}
var stored authorityPO
if err := db.Where("authority_id = ?", 2000).First(&stored).Error; err != nil {
t.Fatal(err)
}
if stored.DataScope != 3 || stored.AuthorityName != "after" {
t.Fatalf("updated authority = %+v, want data scope 3 and name after", stored)
}
}