첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
345
baron-sso/backend/internal/service/tenant_service_test.go
Normal file
345
baron-sso/backend/internal/service/tenant_service_test.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// --- Local Mocks to avoid collisions ---
|
||||
|
||||
type MockTenantRepoForSvc struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) Create(ctx context.Context, tenant *domain.Tenant) error {
|
||||
return m.Called(ctx, tenant).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) Update(ctx context.Context, tenant *domain.Tenant) error {
|
||||
return m.Called(ctx, tenant).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) FindByID(ctx context.Context, id string) (*domain.Tenant, error) {
|
||||
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 *MockTenantRepoForSvc) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, slug)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) FindByName(ctx context.Context, name string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, domainName)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error {
|
||||
return m.Called(ctx, tenantID, domainName, verified).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) {
|
||||
args := m.Called(ctx, limit, offset, parentID, search)
|
||||
return args.Get(0).([]domain.Tenant), args.Get(1).(int64), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) {
|
||||
args := m.Called(ctx, tenantType)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]domain.Tenant), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTenantRepoForSvc) DeleteBulk(ctx context.Context, ids []string) error {
|
||||
args := m.Called(ctx, ids)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
type MockKetoSvcForTenant struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockKetoSvcForTenant) CreateRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
return m.Called(ctx, namespace, object, relation, subject).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockKetoSvcForTenant) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
return m.Called(ctx, namespace, object, relation, subject).Error(0)
|
||||
}
|
||||
|
||||
func (m *MockKetoSvcForTenant) ListRelations(ctx context.Context, namespace, object, relation, subject string) ([]RelationTuple, error) {
|
||||
args := m.Called(ctx, namespace, object, relation, subject)
|
||||
return args.Get(0).([]RelationTuple), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockKetoSvcForTenant) ListObjects(ctx context.Context, namespace, relation, subject string) ([]string, error) {
|
||||
args := m.Called(ctx, namespace, relation, subject)
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockKetoSvcForTenant) CheckPermission(ctx context.Context, namespace, object, relation, subject string) (bool, error) {
|
||||
args := m.Called(ctx, namespace, object, relation, subject)
|
||||
return args.Bool(0), args.Error(1)
|
||||
}
|
||||
|
||||
type MockUserRepoForTenant struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) Create(ctx context.Context, user *domain.User) error { return nil }
|
||||
func (m *MockUserRepoForTenant) Update(ctx context.Context, user *domain.User) error { return nil }
|
||||
func (m *MockUserRepoForTenant) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
args := m.Called(email)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) {
|
||||
return nil, 0, "", nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) CountByTenant(ctx context.Context, tenantID string) (int64, error) {
|
||||
args := m.Called(tenantID)
|
||||
return int64(args.Int(0)), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) FindByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.User, error) {
|
||||
args := m.Called(ctx, tenantIDs)
|
||||
return args.Get(0).([]domain.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) CountByTenantIDs(ctx context.Context, tenantIDs []string) (map[string]int64, error) {
|
||||
args := m.Called(tenantIDs)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(map[string]int64), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) FindByCompanyCodes(ctx context.Context, codes []string) ([]domain.User, error) {
|
||||
args := m.Called(ctx, codes)
|
||||
return args.Get(0).([]domain.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) CountByCompanyCodes(ctx context.Context, codes []string) (map[string]int64, error) {
|
||||
args := m.Called(ctx, codes)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(map[string]int64), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) UpdateUserLoginIDs(ctx context.Context, userID string, loginIDs []domain.UserLoginID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) GetUserLoginIDs(ctx context.Context, userID string) ([]domain.UserLoginID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) IsLoginIDTaken(ctx context.Context, loginID string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) FindTenantIDByLoginID(ctx context.Context, loginID string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockUserRepoForTenant) DB() *gorm.DB {
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestTenantService_RegisterTenant_AutoVerify(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewTenantService(mockRepo, nil, nil, mockOutbox)
|
||||
|
||||
ctx := context.Background()
|
||||
name := "New Tenant"
|
||||
slug := "new-tenant"
|
||||
domains := []string{"example.com"}
|
||||
|
||||
// Use .Once() to ensure correct return values for sequential calls to FindBySlug
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(nil, nil).Once()
|
||||
mockRepo.On("Create", ctx, mock.Anything).Return(nil)
|
||||
mockRepo.On("AddDomain", ctx, mock.Anything, "example.com", true).Return(nil)
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(k *domain.KetoOutbox) bool {
|
||||
return k.Relation == "admins" && k.Subject == "System:global#super_admins"
|
||||
})).Return(nil)
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(&domain.Tenant{ID: "t1", Slug: slug}, nil).Once()
|
||||
|
||||
tenant, err := svc.RegisterTenant(ctx, name, slug, domain.TenantTypeCompany, "", domains, nil, "")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tenant)
|
||||
assert.Equal(t, "t1", tenant.ID)
|
||||
mockRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantService_RegisterTenant_WithCreator(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewTenantService(mockRepo, nil, nil, mockOutbox)
|
||||
|
||||
ctx := context.Background()
|
||||
name := "Creator Tenant"
|
||||
slug := "creator-tenant"
|
||||
creatorID := "creator-uuid"
|
||||
tenantID := "t-new"
|
||||
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(nil, nil).Once()
|
||||
mockRepo.On("Create", ctx, mock.MatchedBy(func(t *domain.Tenant) bool {
|
||||
return t.Slug == slug
|
||||
})).Run(func(args mock.Arguments) {
|
||||
t := args.Get(1).(*domain.Tenant)
|
||||
t.ID = tenantID
|
||||
}).Return(nil)
|
||||
|
||||
// Expect global super admin sync
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(k *domain.KetoOutbox) bool {
|
||||
return k.Relation == "admins" && k.Subject == "System:global#super_admins"
|
||||
})).Return(nil)
|
||||
// Expect owners sync
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "owners" && e.Subject == "User:"+creatorID
|
||||
})).Return(nil)
|
||||
// Expect admins sync
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "admins" && e.Subject == "User:"+creatorID
|
||||
})).Return(nil)
|
||||
// Expect members sync
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "members" && e.Subject == "User:"+creatorID
|
||||
})).Return(nil)
|
||||
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(&domain.Tenant{ID: tenantID, Slug: slug}, nil).Once()
|
||||
|
||||
tenant, err := svc.RegisterTenant(ctx, name, slug, domain.TenantTypeCompany, "", nil, nil, creatorID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tenant)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockOutbox.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantService_RequestRegistration_NoVerify(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
svc := NewTenantService(mockRepo, nil, nil, mockOutbox)
|
||||
|
||||
ctx := context.Background()
|
||||
name := "Public Tenant"
|
||||
slug := "public-tenant"
|
||||
domainName := "public.com"
|
||||
adminEmail := "admin@public.com"
|
||||
|
||||
mockRepo.On("Create", ctx, mock.MatchedBy(func(tenant *domain.Tenant) bool {
|
||||
return tenant.Status == domain.TenantStatusPending
|
||||
})).Return(nil)
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(k *domain.KetoOutbox) bool {
|
||||
return k.Relation == "admins" && k.Subject == "System:global#super_admins"
|
||||
})).Return(nil)
|
||||
mockRepo.On("AddDomain", ctx, mock.Anything, domainName, false).Return(nil)
|
||||
|
||||
tenant, err := svc.RequestRegistration(ctx, name, slug, "", domainName, adminEmail)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tenant)
|
||||
mockRepo.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantService_ApproveTenant_SyncAdmin(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
mockUserRepo := new(MockUserRepoForTenant)
|
||||
mockKeto := new(MockKetoSvcForTenant)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
|
||||
svc := NewTenantService(mockRepo, mockUserRepo, nil, mockOutbox)
|
||||
svc.SetKetoService(mockKeto)
|
||||
|
||||
ctx := context.Background()
|
||||
tenantID := "t1"
|
||||
adminEmail := "admin@tenant.com"
|
||||
userID := "user-uuid"
|
||||
|
||||
tenant := &domain.Tenant{
|
||||
ID: tenantID,
|
||||
Slug: "tenant-slug",
|
||||
Config: domain.JSONMap{"adminEmail": adminEmail},
|
||||
}
|
||||
|
||||
mockRepo.On("FindByID", ctx, tenantID).Return(tenant, nil)
|
||||
mockRepo.On("Update", ctx, mock.Anything).Return(nil)
|
||||
mockUserRepo.On("FindByEmail", adminEmail).Return(&domain.User{ID: userID, Email: adminEmail}, nil)
|
||||
// Now using Outbox instead of direct Keto call
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "owners" && e.Subject == "User:"+userID
|
||||
})).Return(nil)
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "admins" && e.Subject == "User:"+userID
|
||||
})).Return(nil)
|
||||
mockOutbox.On("Create", ctx, mock.MatchedBy(func(e *domain.KetoOutbox) bool {
|
||||
return e.Namespace == "Tenant" && e.Object == tenantID && e.Relation == "members" && e.Subject == "User:"+userID
|
||||
})).Return(nil)
|
||||
|
||||
err := svc.ApproveTenant(ctx, tenantID)
|
||||
assert.NoError(t, err)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockUserRepo.AssertExpectations(t)
|
||||
mockOutbox.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestTenantService_ListTenants(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
svc := NewTenantService(mockRepo, nil, nil, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
tenants := []domain.Tenant{{ID: "t1", Name: "Tenant 1"}}
|
||||
mockRepo.On("List", ctx, 10, 0, "", "").Return(tenants, int64(1), nil)
|
||||
|
||||
result, total, err := svc.ListTenants(ctx, 10, 0, "", "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, tenants, result)
|
||||
mockRepo.AssertExpectations(t)
|
||||
}
|
||||
Reference in New Issue
Block a user