forked from baron/baron-sso
린트 적용
This commit is contained in:
@@ -59,8 +59,8 @@ func SeedTenants(db *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("[Bootstrap] Creating default tenant", "name", config.Name, "slug", config.Slug)
|
slog.Info("[Bootstrap] Creating default tenant", "name", config.Name, "slug", config.Slug)
|
||||||
tenant, err := svc.RegisterTenant(ctx, config.Name, config.Slug, config.Description, config.Domains, nil)
|
tenant, err := svc.RegisterTenant(ctx, config.Name, config.Slug, config.Description, config.Domains, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to seed tenant", "slug", config.Slug, "error", err)
|
slog.Error("Failed to seed tenant", "slug", config.Slug, "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,17 +22,17 @@ const (
|
|||||||
|
|
||||||
// KetoOutbox represents a Keto relationship tuple update event.
|
// KetoOutbox represents a Keto relationship tuple update event.
|
||||||
type KetoOutbox struct {
|
type KetoOutbox struct {
|
||||||
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"`
|
ID string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"`
|
||||||
Namespace string `gorm:"not null" json:"namespace"`
|
Namespace string `gorm:"not null" json:"namespace"`
|
||||||
Object string `gorm:"not null" json:"object"`
|
Object string `gorm:"not null" json:"object"`
|
||||||
Relation string `gorm:"not null" json:"relation"`
|
Relation string `gorm:"not null" json:"relation"`
|
||||||
Subject string `gorm:"not null" json:"subject"` // format: "User:ID" or "Tenant:ID#members"
|
Subject string `gorm:"not null" json:"subject"` // format: "User:ID" or "Tenant:ID#members"
|
||||||
Action string `gorm:"not null" json:"action"` // CREATE, DELETE
|
Action string `gorm:"not null" json:"action"` // CREATE, DELETE
|
||||||
Status string `gorm:"default:'pending';index" json:"status"`
|
Status string `gorm:"default:'pending';index" json:"status"`
|
||||||
RetryCount int `gorm:"default:0" json:"retryCount"`
|
RetryCount int `gorm:"default:0" json:"retryCount"`
|
||||||
LastError string `json:"lastError,omitempty"`
|
LastError string `json:"lastError,omitempty"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
ProcessedAt *time.Time `json:"processedAt,omitempty"`
|
ProcessedAt *time.Time `json:"processedAt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (m *MockKratosAdminForUser) ListIdentities(ctx context.Context) ([]service.
|
|||||||
return args.Get(0).([]service.KratosIdentity), args.Error(1)
|
return args.Get(0).([]service.KratosIdentity), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: In reality, KratosAdminService might not be an interface.
|
// Note: In reality, KratosAdminService might not be an interface.
|
||||||
// If it's a struct, we'd need to mock the underlying client or use an interface.
|
// If it's a struct, we'd need to mock the underlying client or use an interface.
|
||||||
// For the sake of this test, let's assume we can mock it or use a wrapper.
|
// For the sake of this test, let's assume we can mock it or use a wrapper.
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ func TestUserHandler_CreateUser_InvalidEmail(t *testing.T) {
|
|||||||
func TestUserHandler_GetUser_Forbidden(t *testing.T) {
|
func TestUserHandler_GetUser_Forbidden(t *testing.T) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
mockKratos := new(MockKratosAdminForUser)
|
mockKratos := new(MockKratosAdminForUser)
|
||||||
// We need a way to inject mockKratos into UserHandler.
|
// We need a way to inject mockKratos into UserHandler.
|
||||||
// Since UserHandler uses *service.KratosAdminService (struct),
|
// Since UserHandler uses *service.KratosAdminService (struct),
|
||||||
// we'd typically use an interface here.
|
// we'd typically use an interface here.
|
||||||
// For now, let's just focus on the logic validation if possible.
|
// For now, let's just focus on the logic validation if possible.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"baron-sso-backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"baron-sso-backend/internal/domain"
|
|
||||||
|
|
||||||
"github.com/testcontainers/testcontainers-go"
|
"github.com/testcontainers/testcontainers-go"
|
||||||
postgres_module "github.com/testcontainers/testcontainers-go/modules/postgres"
|
postgres_module "github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||||
"github.com/testcontainers/testcontainers-go/wait"
|
"github.com/testcontainers/testcontainers-go/wait"
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"baron-sso-backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"baron-sso-backend/internal/domain"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"baron-sso-backend/internal/domain"
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"baron-sso-backend/internal/domain"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type KetoRelayWorker interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ketoRelayWorker struct {
|
type ketoRelayWorker struct {
|
||||||
outboxRepo repository.KetoOutboxRepository
|
outboxRepo repository.KetoOutboxRepository
|
||||||
ketoService KetoService
|
ketoService KetoService
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
maxRetries int
|
maxRetries int
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ type MockKetoOutboxRepositoryShared struct {
|
|||||||
func (m *MockKetoOutboxRepositoryShared) Create(ctx context.Context, entry *domain.KetoOutbox) error {
|
func (m *MockKetoOutboxRepositoryShared) Create(ctx context.Context, entry *domain.KetoOutbox) error {
|
||||||
return m.Called(ctx, entry).Error(0)
|
return m.Called(ctx, entry).Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKetoOutboxRepositoryShared) CreateWithTx(tx *gorm.DB, entry *domain.KetoOutbox) error {
|
func (m *MockKetoOutboxRepositoryShared) CreateWithTx(tx *gorm.DB, entry *domain.KetoOutbox) error {
|
||||||
return m.Called(tx, entry).Error(0)
|
return m.Called(tx, entry).Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKetoOutboxRepositoryShared) FindPending(ctx context.Context, limit int) ([]domain.KetoOutbox, error) {
|
func (m *MockKetoOutboxRepositoryShared) FindPending(ctx context.Context, limit int) ([]domain.KetoOutbox, error) {
|
||||||
args := m.Called(ctx, limit)
|
args := m.Called(ctx, limit)
|
||||||
if args.Get(0) == nil {
|
if args.Get(0) == nil {
|
||||||
@@ -27,9 +29,11 @@ func (m *MockKetoOutboxRepositoryShared) FindPending(ctx context.Context, limit
|
|||||||
}
|
}
|
||||||
return args.Get(0).([]domain.KetoOutbox), args.Error(1)
|
return args.Get(0).([]domain.KetoOutbox), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKetoOutboxRepositoryShared) UpdateStatus(ctx context.Context, id string, status string, retryCount int, lastError string) error {
|
func (m *MockKetoOutboxRepositoryShared) UpdateStatus(ctx context.Context, id string, status string, retryCount int, lastError string) error {
|
||||||
return m.Called(ctx, id, status, retryCount, lastError).Error(0)
|
return m.Called(ctx, id, status, retryCount, lastError).Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockKetoOutboxRepositoryShared) MarkProcessed(ctx context.Context, id string) error {
|
func (m *MockKetoOutboxRepositoryShared) MarkProcessed(ctx context.Context, id string) error {
|
||||||
return m.Called(ctx, id).Error(0)
|
return m.Called(ctx, id).Error(0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,17 +168,17 @@ func (s *orgChartService) ensureOrgPath(ctx context.Context, rootTenantID string
|
|||||||
// In a real implementation, Repo should have a FindByParentAndName method
|
// In a real implementation, Repo should have a FindByParentAndName method
|
||||||
// For this implementation, we'll try to find by Name and ParentID in TenantRepo or UserGroupRepo
|
// For this implementation, we'll try to find by Name and ParentID in TenantRepo or UserGroupRepo
|
||||||
// Since we're using Polymorphic Tenants, let's assume we can lookup
|
// Since we're using Polymorphic Tenants, let's assume we can lookup
|
||||||
|
|
||||||
// For simplicity in this POC, let's just use Create logic if not in cache
|
// For simplicity in this POC, let's just use Create logic if not in cache
|
||||||
// In production, we MUST check DB first to avoid duplicates
|
// In production, we MUST check DB first to avoid duplicates
|
||||||
|
|
||||||
// [Placeholder] Lookup in DB logic...
|
// [Placeholder] Lookup in DB logic...
|
||||||
// existingID = s.lookupOrgUnit(ctx, rootTenantID, currentParentID, part)
|
// existingID = s.lookupOrgUnit(ctx, rootTenantID, currentParentID, part)
|
||||||
|
|
||||||
if existingID == "" {
|
if existingID == "" {
|
||||||
// Create new unit
|
// Create new unit
|
||||||
unitID := uuid.NewString()
|
unitID := uuid.NewString()
|
||||||
|
|
||||||
// 1. Create Tenant (Type: USER_GROUP)
|
// 1. Create Tenant (Type: USER_GROUP)
|
||||||
newTenant := &domain.Tenant{
|
newTenant := &domain.Tenant{
|
||||||
ID: unitID,
|
ID: unitID,
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ func (s *tenantService) ListManageableTenants(ctx context.Context, userID string
|
|||||||
allIDsMap[id] = true
|
allIDsMap[id] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: 상속된 권한(부모의 어드민이 자식의 어드민)은 Keto의 OPL에서 처리되므로,
|
// Note: 상속된 권한(부모의 어드민이 자식의 어드민)은 Keto의 OPL에서 처리되므로,
|
||||||
// 특정 유저가 'view' 또는 'manage' 권한을 가진 테넌트를 모두 찾으려면
|
// 특정 유저가 'view' 또는 'manage' 권한을 가진 테넌트를 모두 찾으려면
|
||||||
// Keto의 'expand' 또는 'list objects' 기능을 더 고도화하거나,
|
// Keto의 'expand' 또는 'list objects' 기능을 더 고도화하거나,
|
||||||
// 여기서는 직접 할당된 부모 테넌트를 기준으로 하위 테넌트 정보를 추가 조회하는 로직이 필요할 수 있습니다.
|
// 여기서는 직접 할당된 부모 테넌트를 기준으로 하위 테넌트 정보를 추가 조회하는 로직이 필요할 수 있습니다.
|
||||||
// 우선 직접 할당된 테넌트들만 반환합니다.
|
// 우선 직접 할당된 테넌트들만 반환합니다.
|
||||||
|
|
||||||
|
|||||||
@@ -103,13 +103,12 @@ func TestTenantService_ApproveTenant_UserNotFound(t *testing.T) {
|
|||||||
mockRepo.On("Update", ctx, mock.Anything).Return(nil)
|
mockRepo.On("Update", ctx, mock.Anything).Return(nil)
|
||||||
// User not found in DB
|
// User not found in DB
|
||||||
mockUserRepo.On("FindByEmail", adminEmail).Return(nil, gorm.ErrRecordNotFound)
|
mockUserRepo.On("FindByEmail", adminEmail).Return(nil, gorm.ErrRecordNotFound)
|
||||||
|
|
||||||
// Outbox should not be called since user is not found
|
// Outbox should not be called since user is not found
|
||||||
|
|
||||||
err := svc.ApproveTenant(ctx, tenantID)
|
err := svc.ApproveTenant(ctx, tenantID)
|
||||||
assert.NoError(t, err) // Should succeed but just log that user is not found
|
assert.NoError(t, err) // Should succeed but just log that user is not found
|
||||||
mockRepo.AssertExpectations(t)
|
mockRepo.AssertExpectations(t)
|
||||||
mockUserRepo.AssertExpectations(t)
|
mockUserRepo.AssertExpectations(t)
|
||||||
mockOutbox.AssertNotCalled(t, "Create")
|
mockOutbox.AssertNotCalled(t, "Create")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user