1
0
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:
2026-02-23 11:23:48 +09:00
parent 919bcd27e8
commit 0ccd1db649
32 changed files with 2173 additions and 40 deletions

View File

@@ -0,0 +1,69 @@
package repository
import (
"context"
"log"
"os"
"testing"
"time"
"baron-sso-backend/internal/domain"
"github.com/testcontainers/testcontainers-go"
postgres_module "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
gorm_postgres "gorm.io/driver/postgres"
"gorm.io/gorm"
)
var testDB *gorm.DB
func TestMain(m *testing.M) {
ctx := context.Background()
// Start PostgreSQL container
dbName := "testdb"
dbUser := "user"
dbPassword := "password"
postgresContainer, err := postgres_module.Run(ctx,
"postgres:16-alpine",
postgres_module.WithDatabase(dbName),
postgres_module.WithUsername(dbUser),
postgres_module.WithPassword(dbPassword),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(30*time.Second)),
)
if err != nil {
log.Fatalf("failed to start container: %s", err)
}
defer func() {
if err := postgresContainer.Terminate(ctx); err != nil {
log.Fatalf("failed to terminate container: %s", err)
}
}()
connStr, err := postgresContainer.ConnectionString(ctx, "sslmode=disable")
if err != nil {
log.Fatalf("failed to get connection string: %s", err)
}
// Connect to test database
db, err := gorm.Open(gorm_postgres.Open(connStr), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect to database: %s", err)
}
// Auto-migrate
err = db.AutoMigrate(&domain.Tenant{}, &domain.TenantDomain{}, &domain.User{})
if err != nil {
log.Fatalf("failed to migrate database: %s", err)
}
testDB = db
os.Exit(m.Run())
}

View File

@@ -0,0 +1,122 @@
package repository
import (
"context"
"testing"
"baron-sso-backend/internal/domain"
"github.com/stretchr/testify/assert"
)
func TestTenantRepository(t *testing.T) {
repo := NewTenantRepository(testDB)
ctx := context.Background()
t.Run("Create and FindByID", func(t *testing.T) {
tenant := &domain.Tenant{
Name: "Test Tenant",
Slug: "test-tenant",
Type: domain.TenantTypeCompany,
}
err := repo.Create(ctx, tenant)
assert.NoError(t, err)
assert.NotEmpty(t, tenant.ID)
found, err := repo.FindByID(ctx, tenant.ID)
assert.NoError(t, err)
assert.Equal(t, tenant.Name, found.Name)
assert.Equal(t, tenant.Slug, found.Slug)
})
t.Run("FindBySlug", func(t *testing.T) {
tenant := &domain.Tenant{
Name: "Slug Test",
Slug: "slug-test",
Type: domain.TenantTypeCompany,
}
_ = repo.Create(ctx, tenant)
found, err := repo.FindBySlug(ctx, "slug-test")
assert.NoError(t, err)
assert.Equal(t, tenant.ID, found.ID)
})
t.Run("AddDomain and FindByDomain", func(t *testing.T) {
tenant := &domain.Tenant{
Name: "Domain Test",
Slug: "domain-test",
Type: domain.TenantTypeCompany,
}
_ = repo.Create(ctx, tenant)
err := repo.AddDomain(ctx, tenant.ID, "test-domain.com", true)
assert.NoError(t, err)
found, err := repo.FindByDomain(ctx, "test-domain.com")
assert.NoError(t, err)
assert.Equal(t, tenant.ID, found.ID)
assert.Len(t, found.Domains, 1)
assert.Equal(t, "test-domain.com", found.Domains[0].Domain)
})
t.Run("Update", func(t *testing.T) {
tenant := &domain.Tenant{
Name: "Before Update",
Slug: "before-update",
Type: domain.TenantTypeCompany,
}
_ = repo.Create(ctx, tenant)
tenant.Name = "After Update"
err := repo.Update(ctx, tenant)
assert.NoError(t, err)
found, err := repo.FindByID(ctx, tenant.ID)
assert.NoError(t, err)
assert.Equal(t, "After Update", found.Name)
})
t.Run("Hierarchy", func(t *testing.T) {
parent := &domain.Tenant{
Name: "Parent Tenant",
Slug: "parent-hierarchy",
Type: domain.TenantTypeCompanyGroup,
}
err := repo.Create(ctx, parent)
assert.NoError(t, err)
child := &domain.Tenant{
Name: "Child Tenant",
Slug: "child-hierarchy",
Type: domain.TenantTypeCompany,
ParentID: &parent.ID,
}
err = repo.Create(ctx, child)
assert.NoError(t, err)
foundChild, err := repo.FindByID(ctx, child.ID)
assert.NoError(t, err)
assert.Equal(t, parent.ID, *foundChild.ParentID)
})
t.Run("Unique Constraint on Slug", func(t *testing.T) {
slug := "unique-slug-test"
tenant1 := &domain.Tenant{
Name: "First",
Slug: slug,
Type: domain.TenantTypeCompany,
}
err := repo.Create(ctx, tenant1)
assert.NoError(t, err)
tenant2 := &domain.Tenant{
Name: "Second",
Slug: slug,
Type: domain.TenantTypeCompany,
}
err = repo.Create(ctx, tenant2)
assert.Error(t, err) // Should fail due to UNIQUE constraint
})
}

View File

@@ -15,6 +15,7 @@ type UserRepository interface {
FindByIDs(ctx context.Context, ids []string) ([]domain.User, error)
ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error)
List(ctx context.Context, offset, limit int, search string) ([]domain.User, int64, error)
Delete(ctx context.Context, id string) error
}
type userRepository struct {
@@ -88,3 +89,7 @@ func (r *userRepository) List(ctx context.Context, offset, limit int, search str
return users, total, nil
}
func (r *userRepository) Delete(ctx context.Context, id string) error {
return r.db.WithContext(ctx).Delete(&domain.User{}, "id = ?", id).Error
}

View File

@@ -0,0 +1,77 @@
package repository
import (
"context"
"testing"
"baron-sso-backend/internal/domain"
"github.com/stretchr/testify/assert"
)
func TestUserRepository(t *testing.T) {
repo := NewUserRepository(testDB)
ctx := context.Background()
// Ensure User table exists and clean for tests
_ = testDB.AutoMigrate(&domain.User{})
t.Run("Create and FindByEmail", func(t *testing.T) {
user := &domain.User{
Email: "test@example.com",
Name: "Test User",
Role: "user",
}
err := repo.Create(ctx, user)
assert.NoError(t, err)
assert.NotEmpty(t, user.ID)
found, err := repo.FindByEmail(ctx, "test@example.com")
assert.NoError(t, err)
assert.Equal(t, user.ID, found.ID)
assert.Equal(t, "Test User", found.Name)
})
t.Run("Update User Info", func(t *testing.T) {
user := &domain.User{
Email: "update@example.com",
Name: "Before Update",
Role: "user",
}
_ = repo.Create(ctx, user)
user.Name = "After Update"
user.Phone = "010-1234-5678"
err := repo.Update(ctx, user)
assert.NoError(t, err)
found, err := repo.FindByEmail(ctx, "update@example.com")
assert.NoError(t, err)
assert.Equal(t, "After Update", found.Name)
assert.Equal(t, "010-1234-5678", found.Phone)
})
t.Run("List Users with Search", func(t *testing.T) {
// Add some users
_ = repo.Create(ctx, &domain.User{Email: "alice@test.com", Name: "Alice", Role: "user"})
_ = repo.Create(ctx, &domain.User{Email: "bob@test.com", Name: "Bob", Role: "user"})
users, total, err := repo.List(ctx, 0, 10, "Alice")
assert.NoError(t, err)
assert.True(t, total >= 1)
assert.Equal(t, "Alice", users[0].Name)
})
t.Run("Delete User", func(t *testing.T) {
user := &domain.User{Email: "delete@example.com", Name: "To Delete"}
_ = repo.Create(ctx, user)
err := repo.Delete(ctx, user.ID)
assert.NoError(t, err)
found, err := repo.FindByEmail(ctx, "delete@example.com")
assert.Error(t, err) // Should not be found
assert.Nil(t, found)
})
}