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 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 { if m.users == nil { return 0 } count := 0 for _, member := range m.users.users { if member.RoleID == roleID { count++ } } return count } 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 } if m.users == nil { return 0, nil } moved := 0 for id, member := range m.users.users { if member.RoleID == from { member.RoleID = to m.users.users[id] = member 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{}