forked from baron/baron-sso
test(backend): add unit tests for user group management and fix interface inconsistencies
This commit is contained in:
202
backend/internal/service/user_group_service_test.go
Normal file
202
backend/internal/service/user_group_service_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// --- Mocks for Repositories ---
|
||||
|
||||
type MockUserGroupRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockUserGroupRepository) Create(ctx context.Context, group *domain.UserGroup) error {
|
||||
return m.Called(ctx, group).Error(0)
|
||||
}
|
||||
func (m *MockUserGroupRepository) Update(ctx context.Context, group *domain.UserGroup) error {
|
||||
return m.Called(ctx, group).Error(0)
|
||||
}
|
||||
func (m *MockUserGroupRepository) Delete(ctx context.Context, id string) error {
|
||||
return m.Called(ctx, id).Error(0)
|
||||
}
|
||||
func (m *MockUserGroupRepository) FindByID(ctx context.Context, id string) (*domain.UserGroup, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.UserGroup), args.Error(1)
|
||||
}
|
||||
func (m *MockUserGroupRepository) ListByTenantID(ctx context.Context, tenantID string) ([]domain.UserGroup, error) {
|
||||
args := m.Called(ctx, tenantID)
|
||||
return args.Get(0).([]domain.UserGroup), args.Error(1)
|
||||
}
|
||||
|
||||
type MockUserRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
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) 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
|
||||
}
|
||||
func (m *MockUserRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
return args.Get(0).([]domain.User), args.Error(1)
|
||||
}
|
||||
func (m *MockUserRepository) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockUserRepository) List(ctx context.Context, offset, limit int, search string) ([]domain.User, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
type MockTenantRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockTenantRepository) Create(ctx context.Context, tenant *domain.Tenant) error { return nil }
|
||||
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
|
||||
}
|
||||
func (m *MockTenantRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) {
|
||||
args := m.Called(ctx, ids)
|
||||
return args.Get(0).([]domain.Tenant), args.Error(1)
|
||||
}
|
||||
func (m *MockTenantRepository) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockTenantRepository) FindByName(ctx context.Context, name string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockTenantRepository) FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *MockTenantRepository) AddDomain(ctx context.Context, tenantID string, domainName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestUserGroupService_Create(t *testing.T) {
|
||||
mockRepo := new(MockUserGroupRepository)
|
||||
mockKeto := new(MockKetoService)
|
||||
// We don't need userRepo or tenantRepo for Create
|
||||
svc := NewUserGroupService(mockRepo, nil, nil, mockKeto, nil)
|
||||
|
||||
group := &domain.UserGroup{
|
||||
ID: "group-1",
|
||||
TenantID: "tenant-1",
|
||||
Name: "Test Group",
|
||||
}
|
||||
|
||||
mockRepo.On("Create", mock.Anything, group).Return(nil)
|
||||
mockKeto.On("CreateRelation", mock.Anything, "UserGroup", group.ID, "parent_tenant", "Tenant:"+group.TenantID).Return(nil)
|
||||
|
||||
err := svc.Create(context.Background(), group)
|
||||
assert.NoError(t, err)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_AddMember(t *testing.T) {
|
||||
mockKeto := new(MockKetoService)
|
||||
svc := NewUserGroupService(nil, nil, nil, mockKeto, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
userID := "user-1"
|
||||
|
||||
mockKeto.On("CreateRelation", mock.Anything, "UserGroup", groupID, "members", "User:"+userID).Return(nil)
|
||||
|
||||
err := svc.AddMember(context.Background(), groupID, userID)
|
||||
assert.NoError(t, err)
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_AssignRoleToTenant(t *testing.T) {
|
||||
mockKeto := new(MockKetoService)
|
||||
svc := NewUserGroupService(nil, nil, nil, mockKeto, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
tenantID := "tenant-alpha"
|
||||
relation := "manage"
|
||||
|
||||
expectedSubject := "UserGroup:" + groupID + "#members"
|
||||
mockKeto.On("CreateRelation", mock.Anything, "Tenant", tenantID, relation, expectedSubject).Return(nil)
|
||||
|
||||
err := svc.AssignRoleToTenant(context.Background(), groupID, tenantID, relation)
|
||||
assert.NoError(t, err)
|
||||
mockKeto.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserGroupService_ListRoles(t *testing.T) {
|
||||
mockKeto := new(MockKetoService)
|
||||
mockTenantRepo := new(MockTenantRepository)
|
||||
svc := NewUserGroupService(nil, nil, mockTenantRepo, mockKeto, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
subject := "UserGroup:" + groupID + "#members"
|
||||
|
||||
// Mock Keto relations
|
||||
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"},
|
||||
}
|
||||
mockTenantRepo.On("FindByIDs", mock.Anything, []string{"t1", "t2"}).Return(tenants, nil)
|
||||
|
||||
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) {
|
||||
// This tests the logic where a user is in Keto but not in local DB
|
||||
mockRepo := new(MockUserGroupRepository)
|
||||
mockKeto := new(MockKetoService)
|
||||
mockUserRepo := new(MockUserRepository)
|
||||
// We need a way to mock KratosAdminService but it's a struct, not an interface.
|
||||
// For this POC test, we'll focus on the Keto and UserRepo parts.
|
||||
// If needed, we can refactor KratosAdminService to an interface.
|
||||
|
||||
svc := NewUserGroupService(mockRepo, mockUserRepo, nil, mockKeto, nil)
|
||||
|
||||
groupID := "group-1"
|
||||
mockRepo.On("FindByID", mock.Anything, groupID).Return(&domain.UserGroup{ID: groupID, Name: "Test"}, nil)
|
||||
|
||||
tuples := []RelationTuple{
|
||||
{Object: groupID, Relation: "members", SubjectID: "User:u1"},
|
||||
}
|
||||
mockKeto.On("ListRelations", mock.Anything, "UserGroup", groupID, "members", "").Return(tuples, nil)
|
||||
|
||||
// User u1 not in local DB
|
||||
mockUserRepo.On("FindByIDs", mock.Anything, []string{"u1"}).Return([]domain.User{}, nil)
|
||||
|
||||
group, err := svc.Get(context.Background(), groupID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, group)
|
||||
// Members should be empty since Kratos is nil in this test setup
|
||||
assert.Len(t, group.Members, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user