1
0
forked from baron/baron-sso

유저 그룹 계층형 보기

This commit is contained in:
2026-02-23 15:53:18 +09:00
parent 1c6fb4ef83
commit 73b0453ad4
9 changed files with 705 additions and 394 deletions

View File

@@ -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)
}