240 lines
5.1 KiB
Go
240 lines
5.1 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/role"
|
|
"slices"
|
|
)
|
|
|
|
const (
|
|
ownerRoleID uint32 = 1
|
|
adminRoleID uint32 = 2
|
|
writerRoleID uint32 = 3
|
|
)
|
|
|
|
type mockRoleRepo struct {
|
|
roles map[uint32]role.Role
|
|
nextID uint32
|
|
fail failSwitch
|
|
users *mockUserRepo
|
|
invites *inviteFakeRepo
|
|
calls map[string]int
|
|
beforeSave func()
|
|
}
|
|
|
|
func newMockRoleRepo() *mockRoleRepo {
|
|
return &mockRoleRepo{roles: map[uint32]role.Role{}, calls: map[string]int{}}
|
|
}
|
|
|
|
func newSeededMockRoleRepo() *mockRoleRepo {
|
|
repo := newMockRoleRepo()
|
|
for _, builtIn := range role.BuiltInRoles() {
|
|
_, _ = repo.Create(builtIn)
|
|
}
|
|
return repo
|
|
}
|
|
|
|
func (m *mockRoleRepo) byName(name string) role.Role {
|
|
found, _ := m.GetByName(name)
|
|
return found
|
|
}
|
|
|
|
func (m *mockRoleRepo) sortedIDs() []uint32 {
|
|
ids := make([]uint32, 0, len(m.roles))
|
|
for id := range m.roles {
|
|
ids = append(ids, id)
|
|
}
|
|
slices.Sort(ids)
|
|
return ids
|
|
}
|
|
|
|
func (m *mockRoleRepo) List() ([]role.Role, error) {
|
|
m.calls["List"]++
|
|
if err := m.fail.trigger("List"); err != nil {
|
|
return nil, err
|
|
}
|
|
listed := []role.Role{}
|
|
for _, id := range m.sortedIDs() {
|
|
listed = append(listed, m.roles[id])
|
|
}
|
|
return listed, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) Get(id uint32) (role.Role, error) {
|
|
m.calls["Get"]++
|
|
if err := m.fail.trigger("Get"); err != nil {
|
|
return role.Role{}, err
|
|
}
|
|
found, ok := m.roles[id]
|
|
if !ok {
|
|
return role.Role{}, role.ErrRoleNotFound
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) GetByName(name string) (role.Role, error) {
|
|
m.calls["GetByName"]++
|
|
if err := m.fail.trigger("GetByName"); err != nil {
|
|
return role.Role{}, err
|
|
}
|
|
var match *role.Role
|
|
for _, id := range m.sortedIDs() {
|
|
candidate := m.roles[id]
|
|
if candidate.Name == name && (match == nil || candidate.BuiltIn && !match.BuiltIn) {
|
|
match = &candidate
|
|
}
|
|
}
|
|
if match == nil {
|
|
return role.Role{}, role.ErrRoleNotFound
|
|
}
|
|
return *match, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) Create(created role.Role) (role.Role, error) {
|
|
m.calls["Create"]++
|
|
if err := m.fail.trigger("Create"); err != nil {
|
|
return role.Role{}, err
|
|
}
|
|
m.nextID++
|
|
created.ID = m.nextID
|
|
m.roles[created.ID] = created
|
|
return created, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) Save(saved role.Role) (role.Role, error) {
|
|
m.calls["Save"]++
|
|
if err := m.fail.trigger("Save"); err != nil {
|
|
return role.Role{}, err
|
|
}
|
|
if m.beforeSave != nil {
|
|
hook := m.beforeSave
|
|
m.beforeSave = nil
|
|
hook()
|
|
}
|
|
stored, ok := m.roles[saved.ID]
|
|
if !ok {
|
|
return role.Role{}, role.ErrRoleNotFound
|
|
}
|
|
if stored.Version != saved.Version {
|
|
return role.Role{}, role.ErrRoleConflict
|
|
}
|
|
stored.Name = saved.Name
|
|
stored.Description = saved.Description
|
|
stored.Version++
|
|
stored.Grants = slices.Clone(saved.Grants)
|
|
stored.UpdatedAt = saved.UpdatedAt
|
|
stored.UpdatedBy = saved.UpdatedBy
|
|
m.roles[saved.ID] = stored
|
|
return stored, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) memberCount(roleID uint32) int {
|
|
count := 0
|
|
if m.users != nil {
|
|
for _, member := range m.users.users {
|
|
if member.RoleID == roleID {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
if m.invites != nil {
|
|
for _, pending := range m.invites.invites {
|
|
if pending.RoleID == roleID && pending.AcceptedAt == nil {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (m *mockRoleRepo) MemberCounts() (map[uint32]int, error) {
|
|
m.calls["MemberCounts"]++
|
|
if err := m.fail.trigger("MemberCounts"); err != nil {
|
|
return nil, err
|
|
}
|
|
counts := map[uint32]int{}
|
|
if m.users != nil {
|
|
for _, member := range m.users.users {
|
|
if member.RoleID != 0 {
|
|
counts[member.RoleID]++
|
|
}
|
|
}
|
|
}
|
|
if m.invites != nil {
|
|
for _, pending := range m.invites.invites {
|
|
if pending.RoleID != 0 && pending.AcceptedAt == nil {
|
|
counts[pending.RoleID]++
|
|
}
|
|
}
|
|
}
|
|
return counts, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) Delete(id uint32) error {
|
|
m.calls["Delete"]++
|
|
if err := m.fail.trigger("Delete"); err != nil {
|
|
return err
|
|
}
|
|
if _, ok := m.roles[id]; !ok {
|
|
return role.ErrRoleNotFound
|
|
}
|
|
if m.memberCount(id) > 0 {
|
|
return role.ErrRoleHasMembers
|
|
}
|
|
delete(m.roles, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) ReassignMembers(from uint32, to uint32) (int, error) {
|
|
m.calls["ReassignMembers"]++
|
|
if err := m.fail.trigger("ReassignMembers"); err != nil {
|
|
return 0, err
|
|
}
|
|
moved := 0
|
|
if m.users != nil {
|
|
for id, member := range m.users.users {
|
|
if member.RoleID == from {
|
|
member.RoleID = to
|
|
m.users.users[id] = member
|
|
moved++
|
|
}
|
|
}
|
|
}
|
|
if m.invites != nil {
|
|
for id, pending := range m.invites.invites {
|
|
if pending.RoleID == from && pending.AcceptedAt == nil {
|
|
pending.RoleID = to
|
|
m.invites.invites[id] = pending
|
|
moved++
|
|
}
|
|
}
|
|
}
|
|
return moved, nil
|
|
}
|
|
|
|
func (m *mockRoleRepo) RemoveTypedGrants(typeKey string) (int, error) {
|
|
m.calls["RemoveTypedGrants"]++
|
|
if err := m.fail.trigger("RemoveTypedGrants"); err != nil {
|
|
return 0, err
|
|
}
|
|
doomed := role.TypedKeys(typeKey)
|
|
affected := 0
|
|
for id, stored := range m.roles {
|
|
kept := []role.Grant{}
|
|
for _, grant := range stored.Grants {
|
|
if !slices.Contains(doomed, grant.Key) {
|
|
kept = append(kept, grant)
|
|
}
|
|
}
|
|
if len(kept) == len(stored.Grants) {
|
|
continue
|
|
}
|
|
stored.Grants = kept
|
|
stored.Version++
|
|
m.roles[id] = stored
|
|
affected++
|
|
}
|
|
return affected, nil
|
|
}
|
|
|
|
var _ role.Repository = &mockRoleRepo{}
|