1
0
forked from baron/baron-sso

동기화 기초구조 마련

This commit is contained in:
2026-05-12 12:25:31 +09:00
parent 3063450ee0
commit 5e649c279f
33 changed files with 3364 additions and 408 deletions

View File

@@ -139,6 +139,19 @@ func (m *MockTenantServiceForUser) GetTenant(ctx context.Context, id string) (*d
return args.Get(0).(*domain.Tenant), args.Error(1)
}
func (m *MockTenantServiceForUser) GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) {
for _, call := range m.ExpectedCalls {
if call.Method == "GetTenantByDomain" {
args := m.Called(ctx, emailDomain)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Tenant), args.Error(1)
}
}
return nil, nil
}
func (m *MockTenantServiceForUser) ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
@@ -432,6 +445,217 @@ func TestUserHandler_BulkCreateUsers(t *testing.T) {
})
}
func TestUserHandler_BulkCreateUsers_ResolvesAdditionalAppointment(t *testing.T) {
app := fiber.New()
mockKratos := new(MockKratosAdmin)
mockOry := new(MockOryProvider)
mockTenant := new(MockTenantServiceForUser)
h := &UserHandler{
KratosAdmin: mockKratos,
OryProvider: mockOry,
TenantService: mockTenant,
}
app.Post("/users/bulk", h.BulkCreateUsers)
mockTenant.On("GetTenantBySlug", mock.Anything, "test-tenant").Return(&domain.Tenant{
ID: "t-primary",
Slug: "test-tenant",
Name: "Primary Tenant",
Config: domain.JSONMap{},
}, nil).Once()
mockTenant.On("GetTenant", mock.Anything, "t-primary").Return(&domain.Tenant{
ID: "t-primary",
Slug: "test-tenant",
Name: "Primary Tenant",
Config: domain.JSONMap{},
}, nil)
mockTenant.On("GetTenantBySlug", mock.Anything, "second-tenant").Return(&domain.Tenant{
ID: "t-second",
Slug: "second-tenant",
Name: "Second Tenant",
Config: domain.JSONMap{},
}, nil).Once()
mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil)
mockOry.On("CreateUser", mock.MatchedBy(func(user *domain.BrokerUser) bool {
appointments, ok := user.Attributes["additionalAppointments"].([]any)
if !ok || len(appointments) != 1 {
return false
}
appointment, ok := appointments[0].(map[string]any)
if !ok {
return false
}
metadata, _ := appointment["metadata"].(map[string]any)
return appointment["tenantId"] == "t-second" &&
appointment["tenantSlug"] == "second-tenant" &&
appointment["tenantName"] == "Second Tenant" &&
appointment["department"] == "센터" &&
appointment["grade"] == "수석" &&
appointment["jobTitle"] == "Architecture" &&
metadata["employee_id"] == "EMP002"
}), mock.Anything).Return("u-appointment", nil).Once()
payload := map[string]interface{}{
"users": []map[string]interface{}{
{
"email": "dual@test.com",
"name": "Dual User",
"tenantSlug": "test-tenant",
"metadata": map[string]interface{}{"employee_id": "EMP001"},
"additionalAppointments": []map[string]interface{}{
{
"tenantSlug": "second-tenant",
"department": "센터",
"grade": "수석",
"jobTitle": "Architecture",
"metadata": map[string]interface{}{"employee_id": "EMP002"},
},
},
},
},
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/users/bulk", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
assert.Equal(t, 200, resp.StatusCode)
mockTenant.AssertExpectations(t)
mockOry.AssertExpectations(t)
}
func TestUserHandler_BulkCreateUsers_AppendsEmailDomainTenantAtLowestPriority(t *testing.T) {
app := fiber.New()
mockKratos := new(MockKratosAdmin)
mockOry := new(MockOryProvider)
mockTenant := new(MockTenantServiceForUser)
h := &UserHandler{
KratosAdmin: mockKratos,
OryProvider: mockOry,
TenantService: mockTenant,
}
app.Post("/users/bulk", h.BulkCreateUsers)
mockTenant.On("GetTenantBySlug", mock.Anything, "gpdtdc").Return(&domain.Tenant{
ID: "t-gpdtdc",
Slug: "gpdtdc",
Name: "GPDTDC",
Config: domain.JSONMap{},
}, nil).Once()
mockTenant.On("GetTenant", mock.Anything, "t-gpdtdc").Return(&domain.Tenant{
ID: "t-gpdtdc",
Slug: "gpdtdc",
Name: "GPDTDC",
Config: domain.JSONMap{},
}, nil)
mockTenant.On("GetTenantByDomain", mock.Anything, "samaneng.com").Return(&domain.Tenant{
ID: "t-saman",
Slug: "saman",
Name: "삼안",
Status: domain.TenantStatusActive,
Config: domain.JSONMap{},
}, nil).Once()
mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil)
mockOry.On("CreateUser", mock.MatchedBy(func(user *domain.BrokerUser) bool {
if user.Attributes["tenant_id"] != "t-gpdtdc" || user.Attributes["companyCode"] != "gpdtdc" {
return false
}
appointments, ok := user.Attributes["additionalAppointments"].([]any)
if !ok || len(appointments) != 1 {
return false
}
appointment, ok := appointments[0].(map[string]any)
if !ok {
return false
}
return appointment["tenantId"] == "t-saman" &&
appointment["tenantSlug"] == "saman" &&
appointment["tenantName"] == "삼안" &&
appointment["assignmentSource"] == "email_domain" &&
appointment["sourceDomain"] == "samaneng.com"
}), mock.Anything).Return("u-domain-assigned", nil).Once()
payload := map[string]interface{}{
"users": []map[string]interface{}{
{
"email": "user@samaneng.com",
"name": "Domain User",
"tenantSlug": "gpdtdc",
},
},
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/users/bulk", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
results := result["results"].([]interface{})
assert.True(t, results[0].(map[string]interface{})["success"].(bool))
mockTenant.AssertExpectations(t)
mockOry.AssertExpectations(t)
}
func TestUserHandler_BulkCreateUsers_UsesEmailDomainTenantAsPrimaryWhenExplicitTenantMissing(t *testing.T) {
app := fiber.New()
mockKratos := new(MockKratosAdmin)
mockOry := new(MockOryProvider)
mockTenant := new(MockTenantServiceForUser)
h := &UserHandler{
KratosAdmin: mockKratos,
OryProvider: mockOry,
TenantService: mockTenant,
}
app.Post("/users/bulk", h.BulkCreateUsers)
mockTenant.On("GetTenantByDomain", mock.Anything, "samaneng.com").Return(&domain.Tenant{
ID: "t-saman",
Slug: "saman",
Name: "삼안",
Status: domain.TenantStatusActive,
Config: domain.JSONMap{},
}, nil).Once()
mockTenant.On("GetTenant", mock.Anything, "t-saman").Return(&domain.Tenant{
ID: "t-saman",
Slug: "saman",
Name: "삼안",
Status: domain.TenantStatusActive,
Config: domain.JSONMap{},
}, nil)
mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil)
mockOry.On("CreateUser", mock.MatchedBy(func(user *domain.BrokerUser) bool {
return user.Attributes["tenant_id"] == "t-saman" &&
user.Attributes["companyCode"] == "saman" &&
user.Attributes["additionalAppointments"] == nil
}), mock.Anything).Return("u-domain-primary", nil).Once()
payload := map[string]interface{}{
"users": []map[string]interface{}{
{
"email": "user@samaneng.com",
"name": "Domain Primary User",
},
},
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/users/bulk", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
assert.Equal(t, http.StatusOK, resp.StatusCode)
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
results := result["results"].([]interface{})
assert.True(t, results[0].(map[string]interface{})["success"].(bool))
mockTenant.AssertExpectations(t)
mockOry.AssertExpectations(t)
}
func TestUserHandler_ListUsersReturnsServiceUnavailableWhenKratosFails(t *testing.T) {
app := fiber.New()
mockKratos := new(MockKratosAdmin)