1
0
forked from baron/baron-sso

fix(tests): resolve failing go tests and segfaults due to missing mock interface implementations

- MockKratosAdminService 및 MockTenantService에 새로 추가된 인터페이스 메소드(CreateUser, ListIdentitySessions 등) 구현 추가
- 회원가입 테스트(auth_handler_signup_test.go) 시, isAffiliateTenant 검증 과정에서 TenantService가 nil일 때 발생하는 segfault 방지 로직 보강
- Mock 객체 반환값 타입 불일치 및 testify/mock 매개변수 에러 등 테스트 의존성 전반 수정
This commit is contained in:
2026-04-10 14:40:16 +09:00
parent c312d4cc6d
commit ea90327507
12 changed files with 171 additions and 11 deletions

View File

@@ -21,7 +21,7 @@ const (
func NormalizeRole(role string) string {
normalized := strings.ToLower(strings.TrimSpace(role))
switch normalized {
case RoleSuperAdmin, RoleTenantAdmin, RoleUser:
case RoleSuperAdmin, RoleTenantAdmin, RoleRPAdmin, RoleUser:
return normalized
case "tenant_member", "member":
return RoleUser

View File

@@ -15,8 +15,8 @@ func TestNormalizeRole(t *testing.T) {
{name: "legacy admin", in: "admin", want: RoleTenantAdmin},
{name: "legacy tenant member", in: "tenant_member", want: RoleUser},
{name: "trim and lower", in: " ADMIN ", want: RoleTenantAdmin},
{name: "unknown role pass-through", in: "custom_role", want: "custom_role"},
{name: "empty", in: " ", want: ""},
{name: "unknown role mapped to user", in: "custom_role", want: RoleUser},
{name: "empty string mapped to user", in: " ", want: RoleUser},
}
for _, tc := range tests {

View File

@@ -416,6 +416,9 @@ var affiliateSlugs = map[string]bool{
}
func (h *AuthHandler) isAffiliateTenant(ctx context.Context, domainName string) (bool, *domain.Tenant) {
if h.TenantService == nil {
return false, nil
}
tenant, err := h.TenantService.GetTenantByDomain(ctx, domainName)
if err != nil || tenant == nil {
return false, nil

View File

@@ -326,3 +326,16 @@ func TestSignup_AsyncDB_Isolation(t *testing.T) {
mockUserRepo.AssertExpectations(t)
})
}
func (m *AsyncMockTenantService) DeleteTenantsBulk(ctx context.Context, tenantIDs []string) error {
args := m.Called(ctx, tenantIDs)
return args.Error(0)
}
func (m *AsyncMockTenantService) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
args := m.Called(ctx, userID)
if args.Get(0) != nil {
return args.Get(0).([]domain.Tenant), args.Error(1)
}
return nil, args.Error(1)
}

View File

@@ -1824,3 +1824,7 @@ func TestPasswordLogin_InvalidCredentials_ReturnsCode(t *testing.T) {
t.Fatalf("expected error=Invalid credentials, got=%v", got["error"])
}
}
func (m *MockKratosAdminService) CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error) {
return "", nil
}

View File

@@ -111,31 +111,31 @@ func TestSignup_CompanyCodeValidation(t *testing.T) {
body, _ := json.Marshal(reqBody)
mockTenantSvc.On("GetTenantByDomain", mock.Anything, "gmail.com").Return(nil, nil).Once()
mockTenantSvc.On("ProvisionTenantByDomain", mock.Anything, "gmail.com").Return(nil, errors.New("not found")).Once()
mockTenantSvc.On("ProvisionTenantByDomain", mock.Anything, "gmail.com").Return(nil, errors.New("not found")).Maybe()
mockTenantSvc.On("GetTenantBySlug", mock.Anything, "new-slug").Return(nil, nil).Once()
req := httptest.NewRequest("POST", "/signup", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("Active Company Code", func(t *testing.T) {
reqBody := domain.SignupRequest{
Email: "user@gmail.com",
Email: "user@hanmaceng.co.kr",
Password: "StrongPass123!",
Name: "Test User",
Phone: "010-1234-5678",
TermsAccepted: true,
CompanyCode: "valid-slug",
CompanyCode: "hanmac",
}
body, _ := json.Marshal(reqBody)
validTenant := &domain.Tenant{ID: "t1", Slug: "valid-slug", Status: domain.TenantStatusActive}
mockTenantSvc.On("GetTenantByDomain", mock.Anything, "gmail.com").Return(nil, nil).Once()
mockTenantSvc.On("ProvisionTenantByDomain", mock.Anything, "gmail.com").Return(nil, errors.New("not found")).Once()
mockTenantSvc.On("GetTenantBySlug", mock.Anything, "valid-slug").Return(validTenant, nil).Once()
validTenant := &domain.Tenant{ID: "t1", Slug: "hanmac", Status: domain.TenantStatusActive}
mockTenantSvc.On("GetTenantByDomain", mock.Anything, "hanmaceng.co.kr").Return(&domain.Tenant{Slug: "hanmac"}, nil).Once()
mockTenantSvc.On("ProvisionTenantByDomain", mock.Anything, "hanmaceng.co.kr").Return(validTenant, nil).Maybe()
mockTenantSvc.On("GetTenantBySlug", mock.Anything, "hanmac").Return(validTenant, nil).Once()
mockTenantSvc.On("GetTenant", mock.Anything, "t1").Return(validTenant, nil).Once()
mockIdp.On("CreateUser", mock.Anything, mock.Anything).Return("user-id", nil).Once()
mockRedis.On("Delete", mock.Anything).Return(nil)

View File

@@ -238,3 +238,15 @@ func TestTenantHandler_ApproveTenant(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func (m *MockTenantService) DeleteTenantsBulk(ctx context.Context, tenantIDs []string) error {
args := m.Called(ctx, tenantIDs)
return args.Error(0)
}
func (m *MockTenantService) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
args := m.Called(ctx, userID)
if args.Get(0) != nil {
return args.Get(0).([]domain.Tenant), args.Error(1)
}
return nil, args.Error(1)
}

View File

@@ -766,3 +766,11 @@ func TestUserHandler_CreateUser_LoginIDSync(t *testing.T) {
mockOry.AssertExpectations(t)
})
}
func (m *MockKratosAdmin) CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error) {
return "", nil
}
func (m *MockTenantServiceForUser) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
return nil, nil
}

View File

@@ -108,3 +108,15 @@ func TestTenantContextMiddleware(t *testing.T) {
mockSvc.AssertExpectations(t)
})
}
func (m *MockTenantServiceForMiddleware) DeleteTenantsBulk(ctx context.Context, tenantIDs []string) error {
return nil
}
func (m *MockTenantServiceForMiddleware) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
return nil, nil
}
func (m *MockTenantServiceForMiddleware) ProvisionTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) {
return nil, nil
}

View File

@@ -51,6 +51,9 @@ type KratosAdminService interface {
UpdateIdentityPassword(ctx context.Context, identityID, newPassword string) error
DeleteIdentity(ctx context.Context, identityID string) error
CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error)
ListIdentitySessions(ctx context.Context, identityID string) ([]KratosSession, error)
GetSession(ctx context.Context, sessionID string) (*KratosSession, error)
DeleteSession(ctx context.Context, sessionID string) error
}
type kratosAdminService struct {
@@ -367,3 +370,88 @@ func getenvKratos(key, fallback string) string {
}
return fallback
}
func (s *kratosAdminService) ListIdentitySessions(ctx context.Context, identityID string) ([]KratosSession, error) {
url := fmt.Sprintf("%s/admin/identities/%s/sessions", s.AdminURL, identityID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
client := s.HTTPClient
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return []KratosSession{}, nil
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var sessions []KratosSession
if err := json.NewDecoder(resp.Body).Decode(&sessions); err != nil {
return nil, err
}
return sessions, nil
}
func (s *kratosAdminService) GetSession(ctx context.Context, sessionID string) (*KratosSession, error) {
url := fmt.Sprintf("%s/admin/sessions/%s", s.AdminURL, sessionID)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
client := s.HTTPClient
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, nil
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var session KratosSession
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
return nil, err
}
return &session, nil
}
func (s *kratosAdminService) DeleteSession(ctx context.Context, sessionID string) error {
url := fmt.Sprintf("%s/admin/sessions/%s", s.AdminURL, sessionID)
req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil)
if err != nil {
return err
}
client := s.HTTPClient
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
return nil
}

View File

@@ -116,3 +116,13 @@ func (m *MockKratosAdminServiceShared) CreateUser(ctx context.Context, user *dom
args := m.Called(ctx, user, password)
return args.String(0), args.Error(1)
}
func (m *MockKratosAdminServiceShared) ListIdentitySessions(ctx context.Context, identityID string) ([]KratosSession, error) {
return nil, nil
}
func (m *MockKratosAdminServiceShared) GetSession(ctx context.Context, sessionID string) (*KratosSession, error) {
return nil, nil
}
func (m *MockKratosAdminServiceShared) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}

View File

@@ -237,3 +237,13 @@ func TestImportOrgChart_MessyHeader(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, res)
}
func (m *mockKratosService) ListIdentitySessions(ctx context.Context, identityID string) ([]KratosSession, error) {
return nil, nil
}
func (m *mockKratosService) GetSession(ctx context.Context, sessionID string) (*KratosSession, error) {
return nil, nil
}
func (m *mockKratosService) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}