forked from baron/baron-sso
test: 프론트엔드/백엔드 테스트 커버리지 및 시나리오 보강 (Issue #291)
- FE: Vitest 환경 구축 및 공통 UI 컴포넌트(Badge, Button) 테스트 추가 - FE: Playwright E2E 테스트(Auth, Tenant CRUD 및 Validation) 시나리오 보강 - BE: Testcontainers 기반 Repository 통합 테스트(PostgreSQL) 추가 - BE: TenantRepository 계층 구조(Hierarchy), DB 제약조건(Unique) 테스트 - BE: UserRepository 통합 테스트(CRUD, Delete) 추가 - BE: PasswordPolicy 유틸리티 테스트 보강 - BE: TenantService 엣지 케이스(중복 슬러그, 권한 등) 검증 로직 추가 - Fix: 하위 테넌트 생성 시 ParentID 누락 문제 해결
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type TenantService interface {
|
||||
RegisterTenant(ctx context.Context, name, slug, description string, domains []string) (*domain.Tenant, error)
|
||||
RegisterTenant(ctx context.Context, name, slug, description string, domains []string, parentID *string) (*domain.Tenant, error)
|
||||
RequestRegistration(ctx context.Context, name, slug, description string, domainName string, adminEmail string) (*domain.Tenant, error)
|
||||
GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error)
|
||||
GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error)
|
||||
@@ -89,7 +89,7 @@ func (s *tenantService) ListManageableTenants(ctx context.Context, userID string
|
||||
return s.repo.FindByIDs(ctx, allIDs)
|
||||
}
|
||||
|
||||
func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, description string, domains []string) (*domain.Tenant, error) {
|
||||
func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, description string, domains []string, parentID *string) (*domain.Tenant, error) {
|
||||
// Validate Slug
|
||||
if ok, msg := utils.ValidateSlug(slug); !ok {
|
||||
return nil, errors.New(msg)
|
||||
@@ -111,6 +111,7 @@ func (s *tenantService) RegisterTenant(ctx context.Context, name, slug, descript
|
||||
Slug: slug,
|
||||
Description: description,
|
||||
Status: domain.TenantStatusActive,
|
||||
ParentID: parentID,
|
||||
}
|
||||
|
||||
if err := s.repo.Create(ctx, tenant); err != nil {
|
||||
|
||||
115
backend/internal/service/tenant_service_edge_test.go
Normal file
115
backend/internal/service/tenant_service_edge_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
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 TestTenantService_RegisterTenant_DuplicateSlug(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
svc := NewTenantService(mockRepo, nil, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
slug := "duplicate-slug"
|
||||
|
||||
// Mock: slug already exists
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(&domain.Tenant{ID: "existing-id", Slug: slug}, nil)
|
||||
|
||||
tenant, err := svc.RegisterTenant(ctx, "New Name", slug, "", nil, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already exists")
|
||||
assert.Nil(t, tenant)
|
||||
}
|
||||
|
||||
func TestTenantService_RegisterTenant_InvalidSlug(t *testing.T) {
|
||||
svc := NewTenantService(nil, nil, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
// Case 1: Too short
|
||||
_, err := svc.RegisterTenant(ctx, "Name", "a", "", nil, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Case 2: Invalid characters
|
||||
_, err = svc.RegisterTenant(ctx, "Name", "Invalid Slug!", "", nil, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTenantService_RequestRegistration_EmailMismatch(t *testing.T) {
|
||||
svc := NewTenantService(nil, nil, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
// admin email domain (gmail.com) != tenant domain (company.com)
|
||||
tenant, err := svc.RequestRegistration(ctx, "Name", "slug", "", "company.com", "admin@gmail.com")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "must match")
|
||||
assert.Nil(t, tenant)
|
||||
}
|
||||
|
||||
func TestTenantService_ApproveTenant_NotFound(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
svc := NewTenantService(mockRepo, nil, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
id := "non-existent-id"
|
||||
|
||||
mockRepo.On("FindByID", ctx, id).Return(nil, gorm.ErrRecordNotFound)
|
||||
|
||||
err := svc.ApproveTenant(ctx, id)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, gorm.ErrRecordNotFound))
|
||||
}
|
||||
|
||||
func TestTenantService_GetTenantByDomain_Inactive(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
svc := NewTenantService(mockRepo, nil, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
domainName := "inactive.com"
|
||||
|
||||
mockRepo.On("FindByDomain", ctx, domainName).Return(&domain.Tenant{
|
||||
ID: "t1",
|
||||
Status: domain.TenantStatusPending,
|
||||
}, nil)
|
||||
|
||||
tenant, err := svc.GetTenantByDomain(ctx, domainName)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not active")
|
||||
assert.Nil(t, tenant)
|
||||
}
|
||||
|
||||
func TestTenantService_ApproveTenant_UserNotFound(t *testing.T) {
|
||||
mockRepo := new(MockTenantRepoForSvc)
|
||||
mockUserRepo := new(MockUserRepoForTenant)
|
||||
mockOutbox := new(MockKetoOutboxRepositoryShared)
|
||||
|
||||
svc := NewTenantService(mockRepo, mockUserRepo, mockOutbox)
|
||||
ctx := context.Background()
|
||||
tenantID := "t1"
|
||||
adminEmail := "notfound@tenant.com"
|
||||
|
||||
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)
|
||||
// User not found in DB
|
||||
mockUserRepo.On("FindByEmail", adminEmail).Return(nil, gorm.ErrRecordNotFound)
|
||||
|
||||
// Outbox should not be called since user is not found
|
||||
|
||||
err := svc.ApproveTenant(ctx, tenantID)
|
||||
assert.NoError(t, err) // Should succeed but just log that user is not found
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockUserRepo.AssertExpectations(t)
|
||||
mockOutbox.AssertNotCalled(t, "Create")
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func TestTenantService_RegisterTenant_AutoVerify(t *testing.T) {
|
||||
mockRepo.On("AddDomain", ctx, mock.Anything, "example.com", true).Return(nil)
|
||||
mockRepo.On("FindBySlug", ctx, slug).Return(&domain.Tenant{ID: "t1", Slug: slug}, nil).Once()
|
||||
|
||||
tenant, err := svc.RegisterTenant(ctx, name, slug, "", domains)
|
||||
tenant, err := svc.RegisterTenant(ctx, name, slug, "", domains, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tenant)
|
||||
assert.Equal(t, "t1", tenant.ID)
|
||||
|
||||
Reference in New Issue
Block a user