package system import ( "context" "errors" "testing" "kra/internal/conf" "kra/internal/modules/system/biz" "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) } }