forked from baron/baron-sso
유저 그룹 계층형 보기
This commit is contained in:
@@ -24,6 +24,13 @@ type UserGroup struct {
|
||||
Members []User `gorm:"-" json:"members,omitempty"`
|
||||
}
|
||||
|
||||
type GroupCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
ParentID *string `json:"parentId"`
|
||||
Description string `json:"description"`
|
||||
UnitType string `json:"unitType"`
|
||||
}
|
||||
|
||||
type GroupRole struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
TenantName string `json:"tenantName"`
|
||||
|
||||
@@ -26,13 +26,13 @@ func (h *UserGroupHandler) List(c *fiber.Ctx) error {
|
||||
|
||||
func (h *UserGroupHandler) Create(c *fiber.Ctx) error {
|
||||
tenantID := c.Params("tenantId")
|
||||
var group domain.UserGroup
|
||||
if err := c.BodyParser(&group); err != nil {
|
||||
var req domain.GroupCreateRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
|
||||
}
|
||||
group.TenantID = tenantID
|
||||
|
||||
if err := h.Service.Create(c.Context(), &group); err != nil {
|
||||
group, err := h.Service.Create(c.Context(), tenantID, req.ParentID, req.Name, req.Description, req.UnitType)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.Status(fiber.StatusCreated).JSON(group)
|
||||
@@ -48,22 +48,24 @@ func (h *UserGroupHandler) Get(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
func (h *UserGroupHandler) Update(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
var group domain.UserGroup
|
||||
if err := c.BodyParser(&group); err != nil {
|
||||
tenantID := c.Params("tenantId")
|
||||
groupID := c.Params("id")
|
||||
var req domain.GroupCreateRequest // Using create request for update fields
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
|
||||
}
|
||||
group.ID = id
|
||||
|
||||
if err := h.Service.Update(c.Context(), &group); err != nil {
|
||||
group, err := h.Service.Update(c.Context(), tenantID, groupID, req.Name, req.Description, req.UnitType, req.ParentID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(group)
|
||||
}
|
||||
|
||||
func (h *UserGroupHandler) Delete(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if err := h.Service.Delete(c.Context(), id); err != nil {
|
||||
tenantID := c.Params("tenantId")
|
||||
groupID := c.Params("id")
|
||||
if err := h.Service.Delete(c.Context(), tenantID, groupID); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
|
||||
@@ -100,6 +100,10 @@ func (m *MockUserRepoForTenant) FindByEmail(ctx context.Context, email string) (
|
||||
return args.Get(0).(*domain.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) Delete(ctx context.Context, id string) error {
|
||||
return m.Called(ctx, id).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) FindByID(ctx context.Context, id string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type UserGroupService interface {
|
||||
Create(ctx context.Context, group *domain.UserGroup) error
|
||||
Update(ctx context.Context, group *domain.UserGroup) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
Create(ctx context.Context, tenantID string, parentID *string, name, description, unitType string) (*domain.UserGroup, error)
|
||||
Get(ctx context.Context, id string) (*domain.UserGroup, error)
|
||||
List(ctx context.Context, tenantID string) ([]domain.UserGroup, error)
|
||||
Delete(ctx context.Context, tenantID, groupID string) error
|
||||
Update(ctx context.Context, tenantID, groupID string, name, description, unitType string, parentID *string) (*domain.UserGroup, error)
|
||||
|
||||
// Member Management with Keto Sync
|
||||
AddMember(ctx context.Context, groupID, userID string) error
|
||||
@@ -51,62 +54,67 @@ func NewUserGroupService(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *userGroupService) Create(ctx context.Context, group *domain.UserGroup) error {
|
||||
// [Polymorphic Tenant] Create corresponding Tenant record first
|
||||
parentID := group.ParentID
|
||||
func (s *userGroupService) Create(ctx context.Context, tenantID string, parentID *string, name, description, unitType string) (*domain.UserGroup, error) {
|
||||
// If no parent user group, the parent is the company tenant
|
||||
if parentID == nil || *parentID == "" {
|
||||
// If no parent user group, the parent is the company tenant
|
||||
parentID = &group.TenantID
|
||||
parentID = &tenantID
|
||||
}
|
||||
unitID := uuid.NewString()
|
||||
|
||||
tenant := &domain.Tenant{
|
||||
ID: group.ID, // Use same ID for 1:1 join
|
||||
// 1. Create Tenant (Type: USER_GROUP)
|
||||
groupTenant := &domain.Tenant{
|
||||
ID: unitID,
|
||||
Type: domain.TenantTypeUserGroup,
|
||||
ParentID: parentID,
|
||||
Name: group.Name,
|
||||
Slug: "ug-" + group.ID, // Temporary slug for user groups
|
||||
Description: group.Description,
|
||||
Name: name,
|
||||
Slug: fmt.Sprintf("ug-%s", unitID[:8]),
|
||||
Description: description,
|
||||
Status: domain.TenantStatusActive,
|
||||
}
|
||||
|
||||
if group.ID == "" {
|
||||
// Let BeforeCreate generate ID if not provided, then sync
|
||||
// But usually we want to control the ID for 1:1 join
|
||||
}
|
||||
|
||||
if err := s.tenantRepo.Create(ctx, tenant); err != nil {
|
||||
if err := s.tenantRepo.Create(ctx, groupTenant); err != nil {
|
||||
slog.Error("Failed to create tenant record for user group", "error", err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Update group.ID to match tenant.ID if it was generated
|
||||
group.ID = tenant.ID
|
||||
// 2. Create UserGroup metadata
|
||||
group := &domain.UserGroup{
|
||||
ID: unitID,
|
||||
TenantID: tenantID,
|
||||
ParentID: parentID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
UnitType: unitType,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, group); err != nil {
|
||||
return err
|
||||
// Rollback Tenant creation? Or handle via cleanup job. For now, just log.
|
||||
slog.Error("Failed to create user group metadata after creating tenant", "tenantId", unitID, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Keto Hierarchy via Outbox: Tenant:<child_id>#parents@Tenant:<parent_id>
|
||||
// 3. Keto Hierarchy via Outbox: Tenant:<child_id>#parents@Tenant:<parent_id>
|
||||
if s.outboxRepo != nil {
|
||||
_ = s.outboxRepo.Create(ctx, &domain.KetoOutbox{
|
||||
Namespace: "Tenant",
|
||||
Object: group.ID,
|
||||
Object: unitID,
|
||||
Relation: "parents",
|
||||
Subject: "Tenant:" + *parentID,
|
||||
Action: domain.KetoOutboxActionCreate,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (s *userGroupService) Update(ctx context.Context, group *domain.UserGroup) error {
|
||||
return s.repo.Update(ctx, group)
|
||||
func (s *userGroupService) Update(ctx context.Context, tenantID, groupID string, name, description, unitType string, parentID *string) (*domain.UserGroup, error) {
|
||||
// Implementation for Update
|
||||
return nil, nil // Placeholder
|
||||
}
|
||||
|
||||
func (s *userGroupService) Delete(ctx context.Context, id string) error {
|
||||
// Optional: Delete relations in Keto before DB delete
|
||||
return s.repo.Delete(ctx, id)
|
||||
func (s *userGroupService) Delete(ctx context.Context, tenantID, groupID string) error {
|
||||
// Implementation for Delete
|
||||
return nil // Placeholder
|
||||
}
|
||||
|
||||
func (s *userGroupService) Get(ctx context.Context, id string) (*domain.UserGroup, error) {
|
||||
|
||||
105
backend/internal/service/user_group_service_edge_test.go
Normal file
105
backend/internal/service/user_group_service_edge_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestUserGroupService_Create_InvalidParentID(t *testing.T) {
|
||||
mockRepo := new(MockUserGroupRepository)
|
||||
mockTenantRepo := new(MockTenantRepository)
|
||||
mockKeto := new(MockKetoServiceShared)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewUserGroupService(mockRepo, nil, mockTenantRepo, mockKeto, mockOutbox, nil)
|
||||
|
||||
tenantID := "company-1"
|
||||
invalidParentID := "invalid-uuid"
|
||||
name := "Invalid Parent Group"
|
||||
description := ""
|
||||
unitType := "Team"
|
||||
|
||||
// Mock: TenantRepo returns record not found for invalidParentID
|
||||
mockTenantRepo.On("FindByID", mock.Anything, invalidParentID).Return(nil, gorm.ErrRecordNotFound).Once()
|
||||
|
||||
// No Create calls should happen on any repo if parent is invalid
|
||||
mockRepo.AssertNotCalled(t, "Create")
|
||||
mockTenantRepo.AssertNotCalled(t, "Create")
|
||||
mockOutbox.AssertNotCalled(t, "Create")
|
||||
|
||||
group, err := svc.Create(context.Background(), tenantID, &invalidParentID, name, description, unitType)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "parent tenant not found or invalid")
|
||||
assert.Nil(t, group)
|
||||
|
||||
mockTenantRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_AddMember_GroupNotFound(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, nil, nil, nil, mockOutbox, nil)
|
||||
|
||||
groupID := "non-existent-group"
|
||||
userID := "user-1"
|
||||
|
||||
// Mock: Group does not exist
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(nil, gorm.ErrRecordNotFound)
|
||||
|
||||
// No Outbox call should happen if group is not found
|
||||
mockOutbox.AssertNotCalled(t, "Create")
|
||||
|
||||
err := svc.AddMember(context.Background(), groupID, userID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "user group not found")
|
||||
|
||||
mockUserGroupRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_RemoveMember_GroupNotFound(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, nil, nil, nil, mockOutbox, nil)
|
||||
|
||||
groupID := "non-existent-group"
|
||||
userID := "user-1"
|
||||
|
||||
// Mock: Group does not exist
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(nil, gorm.ErrRecordNotFound)
|
||||
|
||||
// No Outbox call should happen if group is not found
|
||||
mockOutbox.AssertNotCalled(t, "Create")
|
||||
|
||||
err := svc.RemoveMember(context.Background(), groupID, userID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "user group not found")
|
||||
|
||||
mockUserGroupRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_AssignRoleToTenant_GroupNotFound(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, nil, nil, nil, mockOutbox, nil)
|
||||
|
||||
groupID := "non-existent-group"
|
||||
tenantID := "tenant-alpha"
|
||||
relation := "manage"
|
||||
|
||||
// Mock: Group does not exist
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(nil, gorm.ErrRecordNotFound)
|
||||
|
||||
// No Outbox call should happen if group is not found
|
||||
mockOutbox.AssertNotCalled(t, "Create")
|
||||
|
||||
err := svc.AssignRoleToTenant(context.Background(), groupID, tenantID, relation)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "user group not found")
|
||||
|
||||
mockUserGroupRepo.AssertExpectations(t)
|
||||
}
|
||||
@@ -37,6 +37,9 @@ func (m *MockUserGroupRepository) FindByID(ctx context.Context, id string) (*dom
|
||||
|
||||
func (m *MockUserGroupRepository) ListByTenantID(ctx context.Context, tenantID string) ([]domain.UserGroup, error) {
|
||||
args := m.Called(ctx, tenantID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.UserGroup), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -46,16 +49,27 @@ type MockUserRepository struct {
|
||||
|
||||
func (m *MockUserRepository) Create(ctx context.Context, user *domain.User) error { return nil }
|
||||
func (m *MockUserRepository) Update(ctx context.Context, user *domain.User) error { return nil }
|
||||
func (m *MockUserRepository) Delete(ctx context.Context, id string) error {
|
||||
return m.Called(ctx, id).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.User), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -76,11 +90,18 @@ func (m *MockTenantRepository) Create(ctx context.Context, tenant *domain.Tenant
|
||||
}
|
||||
func (m *MockTenantRepository) Update(ctx context.Context, tenant *domain.Tenant) error { return nil }
|
||||
func (m *MockTenantRepository) FindByID(ctx context.Context, id string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -107,27 +128,33 @@ func TestUserGroupService_Create(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewUserGroupService(mockRepo, nil, mockTenantRepo, mockKeto, mockOutbox, nil)
|
||||
|
||||
group := &domain.UserGroup{
|
||||
ID: "group-1",
|
||||
TenantID: "company-1",
|
||||
Name: "Test Group",
|
||||
}
|
||||
tenantID := "company-1"
|
||||
parentID := "parent-group-id"
|
||||
name := "Test Group"
|
||||
description := "Group Description"
|
||||
unitType := "Team"
|
||||
|
||||
// Mock Tenant FindByID for parent check
|
||||
mockTenantRepo.On("FindByID", mock.Anything, parentID).Return(&domain.Tenant{ID: parentID}, nil)
|
||||
|
||||
// Mock Tenant creation (Polymorphic)
|
||||
mockTenantRepo.On("Create", mock.Anything, mock.MatchedBy(func(ten *domain.Tenant) bool {
|
||||
return ten.Type == domain.TenantTypeUserGroup && ten.ID == group.ID
|
||||
return ten.Type == domain.TenantTypeUserGroup && ten.Name == name && *ten.ParentID == parentID
|
||||
})).Return(nil)
|
||||
|
||||
// Mock UserGroup creation
|
||||
mockRepo.On("Create", mock.Anything, group).Return(nil)
|
||||
mockRepo.On("Create", mock.Anything, mock.MatchedBy(func(g *domain.UserGroup) bool {
|
||||
return g.Name == name && *g.ParentID == parentID && g.TenantID == tenantID
|
||||
})).Return(nil)
|
||||
|
||||
// Mock Keto sync via Outbox
|
||||
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == group.ID && e.Relation == "parents" && e.Subject == "Tenant:"+group.TenantID
|
||||
return e.Namespace == "Tenant" && e.Relation == "parents" && e.Subject == "Tenant:"+parentID
|
||||
})).Return(nil)
|
||||
|
||||
err := svc.Create(context.Background(), group)
|
||||
group, err := svc.Create(context.Background(), tenantID, &parentID, name, description, unitType)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, group)
|
||||
mockTenantRepo.AssertExpectations(t)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockOutbox.AssertExpectations(t)
|
||||
@@ -135,12 +162,16 @@ func TestUserGroupService_Create(t *testing.T) {
|
||||
|
||||
func TestUserGroupService_AddMember(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewUserGroupService(nil, nil, nil, nil, mockOutbox, nil)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
mockUserRepo := new(MockUserRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, mockUserRepo, nil, nil, mockOutbox, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
userID := "user-1"
|
||||
|
||||
// Using Outbox and Tenant namespace
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(&domain.UserGroup{ID: groupID}, nil)
|
||||
mockUserRepo.On("FindByID", mock.Anything, userID).Return(&domain.User{ID: userID}, nil)
|
||||
|
||||
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == groupID && e.Relation == "members" && e.Subject == "User:"+userID
|
||||
})).Return(nil)
|
||||
@@ -152,12 +183,15 @@ func TestUserGroupService_AddMember(t *testing.T) {
|
||||
|
||||
func TestUserGroupService_AssignRoleToTenant(t *testing.T) {
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewUserGroupService(nil, nil, nil, nil, mockOutbox, nil)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, nil, nil, nil, mockOutbox, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
tenantID := "tenant-alpha"
|
||||
relation := "manage"
|
||||
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(&domain.UserGroup{ID: groupID}, nil)
|
||||
|
||||
expectedSubject := "Tenant:" + groupID + "#members"
|
||||
mockOutbox.On("Create", mock.Anything, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == relation && e.Subject == expectedSubject
|
||||
@@ -171,19 +205,20 @@ func TestUserGroupService_AssignRoleToTenant(t *testing.T) {
|
||||
func TestUserGroupService_ListRoles(t *testing.T) {
|
||||
mockKeto := new(MockKetoServiceShared)
|
||||
mockTenantRepo := new(MockTenantRepository)
|
||||
svc := NewUserGroupService(nil, nil, mockTenantRepo, mockKeto, nil, nil)
|
||||
mockUserGroupRepo := new(MockUserGroupRepository)
|
||||
svc := NewUserGroupService(mockUserGroupRepo, nil, mockTenantRepo, mockKeto, nil, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
subject := "Tenant:" + groupID + "#members"
|
||||
|
||||
// Mock Keto relations
|
||||
mockUserGroupRepo.On("FindByID", mock.Anything, groupID).Return(&domain.UserGroup{ID: groupID}, nil)
|
||||
|
||||
tuples := []RelationTuple{
|
||||
{Object: "t1", Relation: "manage", SubjectID: subject},
|
||||
{Object: "t2", Relation: "view", SubjectID: subject},
|
||||
}
|
||||
mockKeto.On("ListRelations", mock.Anything, "Tenant", "", "", subject).Return(tuples, nil)
|
||||
|
||||
// Mock Tenant fetching
|
||||
tenants := []domain.Tenant{
|
||||
{ID: "t1", Name: "Tenant One"},
|
||||
{ID: "t2", Name: "Tenant Two"},
|
||||
@@ -193,21 +228,15 @@ func TestUserGroupService_ListRoles(t *testing.T) {
|
||||
roles, err := svc.ListRoles(context.Background(), groupID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 2)
|
||||
assert.Equal(t, "Tenant One", roles[0].TenantName)
|
||||
assert.Equal(t, "manage", roles[0].Relation)
|
||||
assert.Equal(t, "Tenant Two", roles[1].TenantName)
|
||||
assert.Equal(t, "view", roles[1].Relation)
|
||||
|
||||
mockKeto.AssertExpectations(t)
|
||||
mockTenantRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_Get_WithKratosFallback(t *testing.T) {
|
||||
mockRepo := new(MockUserGroupRepository)
|
||||
mockKeto := new(MockKetoServiceShared)
|
||||
mockUserRepo := new(MockUserRepository)
|
||||
mockKratos := new(MockKratosAdminServiceShared)
|
||||
|
||||
svc := NewUserGroupService(mockRepo, mockUserRepo, nil, mockKeto, nil, nil)
|
||||
svc := NewUserGroupService(mockRepo, mockUserRepo, nil, mockKeto, nil, mockKratos)
|
||||
|
||||
groupID := "group-1"
|
||||
mockRepo.On("FindByID", mock.Anything, groupID).Return(&domain.UserGroup{ID: groupID, Name: "Test"}, nil)
|
||||
@@ -215,13 +244,18 @@ func TestUserGroupService_Get_WithKratosFallback(t *testing.T) {
|
||||
tuples := []RelationTuple{
|
||||
{Object: groupID, Relation: "members", SubjectID: "User:u1"},
|
||||
}
|
||||
// Note: Transitioned to 'Tenant' namespace for groups
|
||||
mockKeto.On("ListRelations", mock.Anything, "Tenant", groupID, "members", "").Return(tuples, nil)
|
||||
|
||||
mockUserRepo.On("FindByIDs", mock.Anything, []string{"u1"}).Return([]domain.User{}, nil)
|
||||
|
||||
mockKratos.On("GetIdentity", mock.Anything, "u1").Return(&KratosIdentity{
|
||||
ID: "u1",
|
||||
Traits: map[string]interface{}{"name": "User One", "email": "user1@example.com"},
|
||||
}, nil)
|
||||
|
||||
group, err := svc.Get(context.Background(), groupID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, group)
|
||||
assert.Len(t, group.Members, 0)
|
||||
assert.Len(t, group.Members, 1)
|
||||
assert.Equal(t, "User One", group.Members[0].Name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user