forked from baron/baron-sso
refactor: backend tenant_group 제거 및 리팩터 반영
This commit is contained in:
251
backend/internal/handler/auth_handler_async_test.go
Normal file
251
backend/internal/handler/auth_handler_async_test.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"baron-sso-backend/internal/service"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// --- Async Test Mocks ---
|
||||
|
||||
type AsyncMockIdpProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *AsyncMockIdpProvider) Name() string { return "mock-idp" }
|
||||
func (m *AsyncMockIdpProvider) GetMetadata() (*domain.IDPMetadata, error) {
|
||||
return &domain.IDPMetadata{}, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) UserExists(loginID string) (bool, error) {
|
||||
args := m.Called(loginID)
|
||||
return args.Bool(0), args.Error(1)
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) CreateUser(user *domain.BrokerUser, password string) (string, error) {
|
||||
args := m.Called(user, password)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) SignIn(loginID, password string) (*domain.AuthInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) IssueSession(loginID string) (*domain.AuthInfo, error) { return nil, nil }
|
||||
func (m *AsyncMockIdpProvider) InitiateLinkLogin(loginID, returnTo string) (*domain.LinkLoginInit, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) VerifyLoginCode(loginID, flowID, code string) (*domain.AuthInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) GetPasswordPolicy() (*domain.PasswordPolicy, error) {
|
||||
return &domain.PasswordPolicy{MinLength: 12}, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) InitiatePasswordReset(loginID, redirectUrl string) error { return nil }
|
||||
func (m *AsyncMockIdpProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockIdpProvider) UpdateUserPassword(loginID, newPassword string, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type AsyncMockUserRepo struct {
|
||||
mock.Mock
|
||||
createCalled chan bool
|
||||
}
|
||||
|
||||
func (m *AsyncMockUserRepo) Create(ctx context.Context, user *domain.User) error {
|
||||
// Simulate DB latency
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
args := m.Called(ctx, user)
|
||||
if m.createCalled != nil {
|
||||
m.createCalled <- true
|
||||
}
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *AsyncMockUserRepo) Update(ctx context.Context, user *domain.User) error { return nil }
|
||||
func (m *AsyncMockUserRepo) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockUserRepo) FindByID(ctx context.Context, id string) (*domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockUserRepo) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockUserRepo) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockUserRepo) List(ctx context.Context, offset, limit int, search string) ([]domain.User, int64, error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
type AsyncMockRedisRepo struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *AsyncMockRedisRepo) Set(key string, value string, expiration time.Duration) error {
|
||||
args := m.Called(key, value, expiration)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *AsyncMockRedisRepo) Get(key string) (string, error) {
|
||||
args := m.Called(key)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
func (m *AsyncMockRedisRepo) Delete(key string) error {
|
||||
args := m.Called(key)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *AsyncMockRedisRepo) StoreVerificationCode(phone, code string) error { return nil }
|
||||
func (m *AsyncMockRedisRepo) GetVerificationCode(phone string) (string, error) { return "", nil }
|
||||
func (m *AsyncMockRedisRepo) DeleteVerificationCode(phone string) error { return nil }
|
||||
|
||||
type AsyncMockTenantService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *AsyncMockTenantService) RegisterTenant(ctx context.Context, name, slug, description string, domains []string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) RequestRegistration(ctx context.Context, name, slug, description string, domainName string, adminEmail string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) {
|
||||
args := m.Called(ctx, emailDomain)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*domain.Tenant), args.Error(1)
|
||||
}
|
||||
func (m *AsyncMockTenantService) GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) GetTenant(ctx context.Context, id string) (*domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) ApproveTenant(ctx context.Context, id string) error { return nil }
|
||||
func (m *AsyncMockTenantService) SetKetoService(keto service.KetoService) {}
|
||||
func (m *AsyncMockTenantService) AddTenantAdmin(ctx context.Context, tenantID, userID string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) RemoveTenantAdmin(ctx context.Context, tenantID, userID string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *AsyncMockTenantService) ListTenantAdmins(ctx context.Context, tenantID string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type AsyncMockKetoService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *AsyncMockKetoService) CreateRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
args := m.Called(ctx, namespace, object, relation, subject)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *AsyncMockKetoService) DeleteRelation(ctx context.Context, namespace, object, relation, subject string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *AsyncMockKetoService) CheckPermission(ctx context.Context, namespace, object, relation, subject string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
func (m *AsyncMockKetoService) ListObjects(ctx context.Context, namespace, relation, subject string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (m *AsyncMockKetoService) ListRelations(ctx context.Context, namespace, object, relation, subject string) ([]service.RelationTuple, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func TestSignup_AsyncDB_Isolation(t *testing.T) {
|
||||
mockIdp := new(AsyncMockIdpProvider)
|
||||
mockUserRepo := new(AsyncMockUserRepo)
|
||||
mockRedis := new(AsyncMockRedisRepo)
|
||||
mockTenant := new(AsyncMockTenantService)
|
||||
mockKeto := new(AsyncMockKetoService)
|
||||
|
||||
h := &AuthHandler{
|
||||
IdpProvider: mockIdp,
|
||||
UserRepo: mockUserRepo,
|
||||
RedisService: mockRedis,
|
||||
TenantService: mockTenant,
|
||||
KetoService: mockKeto,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Post("/signup", h.Signup)
|
||||
|
||||
t.Run("SoT_DB_Failure_Ignored_And_Async", func(t *testing.T) {
|
||||
email := "test@example.com"
|
||||
phone := "010-1234-5678"
|
||||
emailKey := "signup:email:" + email
|
||||
phoneKey := "signup:phone:" + "01012345678"
|
||||
|
||||
// Redis Mocks
|
||||
mockRedis.On("Get", emailKey).Return(`{"verified": true, "expires_at": 9999999999}`, nil)
|
||||
mockRedis.On("Get", phoneKey).Return(`{"verified": true, "expires_at": 9999999999}`, nil)
|
||||
mockRedis.On("Delete", emailKey).Return(nil)
|
||||
mockRedis.On("Delete", phoneKey).Return(nil)
|
||||
|
||||
// Tenant Mocks
|
||||
mockTenant.On("GetTenantByDomain", mock.Anything, "example.com").Return(nil, errors.New("not found"))
|
||||
|
||||
// Kratos Mocks (Success)
|
||||
mockIdp.On("CreateUser", mock.Anything, "Password123!").Return("new-user-uuid", nil)
|
||||
|
||||
// UserRepo Mocks (Async & Failure)
|
||||
mockUserRepo.createCalled = make(chan bool, 1)
|
||||
mockUserRepo.On("Create", mock.Anything, mock.MatchedBy(func(u *domain.User) bool {
|
||||
return u.Email == email
|
||||
})).Return(errors.New("db connection error"))
|
||||
|
||||
// Keto Mocks (Optional, since it's also async)
|
||||
// We won't block on this either
|
||||
|
||||
body, _ := json.Marshal(domain.SignupRequest{
|
||||
Email: email,
|
||||
Password: "Password123!",
|
||||
Name: "Test User",
|
||||
Phone: phone,
|
||||
TermsAccepted: true,
|
||||
})
|
||||
req := httptest.NewRequest("POST", "/signup", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
start := time.Now()
|
||||
resp, err := app.Test(req, 5000)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
// Ensure API responded faster than DB latency (50ms)
|
||||
assert.Less(t, int64(elapsed), int64(60*time.Millisecond), "API should return before DB timeout")
|
||||
|
||||
// Wait for async execution
|
||||
select {
|
||||
case <-mockUserRepo.createCalled:
|
||||
// Pass
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("UserRepo.Create was not called asynchronously")
|
||||
}
|
||||
|
||||
mockRedis.AssertExpectations(t)
|
||||
mockIdp.AssertExpectations(t)
|
||||
mockUserRepo.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user