forked from baron/baron-sso
테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// --- Mocks ---
|
||||
@@ -118,6 +119,22 @@ func (f *fakeUserHandlerWorksmobileSyncer) EnqueueUserDeleteIfInScope(ctx contex
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSanitizeUserMetadataRemovesLegacyClassificationFlags(t *testing.T) {
|
||||
metadata := map[string]any{
|
||||
"hanmacFamily": true,
|
||||
"userType": "hanmac",
|
||||
"employeeId": "E001",
|
||||
}
|
||||
|
||||
sanitized := sanitizeUserMetadata(metadata)
|
||||
|
||||
assert.NotContains(t, sanitized, "hanmacFamily")
|
||||
assert.NotContains(t, sanitized, "userType")
|
||||
assert.Equal(t, "E001", sanitized["employeeId"])
|
||||
assert.Contains(t, metadata, "hanmacFamily")
|
||||
assert.Contains(t, metadata, "userType")
|
||||
}
|
||||
|
||||
type MockTenantServiceForUser struct {
|
||||
mock.Mock
|
||||
service.TenantService
|
||||
@@ -693,6 +710,40 @@ func TestUserHandler_ListUsersReturnsServiceUnavailableWhenKratosFails(t *testin
|
||||
mockKratos.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_ListUsersReturnsNextCursorWhenMoreRowsExist(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
createdAt := time.Date(2026, 5, 13, 8, 0, 0, 0, time.UTC)
|
||||
|
||||
h := &UserHandler{KratosAdmin: mockKratos}
|
||||
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{
|
||||
Role: domain.RoleSuperAdmin,
|
||||
})
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/users", h.ListUsers)
|
||||
|
||||
mockKratos.On("ListIdentities", mock.Anything).Return([]service.KratosIdentity{
|
||||
{ID: "u-3", State: "active", CreatedAt: createdAt, Traits: map[string]interface{}{"email": "c@example.com", "name": "C"}},
|
||||
{ID: "u-2", State: "active", CreatedAt: createdAt.Add(-time.Minute), Traits: map[string]interface{}{"email": "b@example.com", "name": "B"}},
|
||||
{ID: "u-1", State: "active", CreatedAt: createdAt.Add(-2 * time.Minute), Traits: map[string]interface{}{"email": "a@example.com", "name": "A"}},
|
||||
}, nil).Once()
|
||||
|
||||
req := httptest.NewRequest("GET", "/users?limit=2", nil)
|
||||
resp, err := app.Test(req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var res userListResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&res))
|
||||
require.Len(t, res.Items, 2)
|
||||
require.NotEmpty(t, res.NextCursor)
|
||||
require.Equal(t, int64(3), res.Total)
|
||||
}
|
||||
|
||||
func TestUserHandler_BulkCreateUsers_HanmacEmailPolicy(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
@@ -904,6 +955,27 @@ func TestUserHandler_BulkUpdateUsers(t *testing.T) {
|
||||
assert.Equal(t, "u-1", worksmobile.upserts[0].ID)
|
||||
assert.Equal(t, domain.UserStatusInactive, worksmobile.upserts[0].Status)
|
||||
})
|
||||
|
||||
t.Run("Fail - Tenant admin cannot update role", func(t *testing.T) {
|
||||
app := fiber.New()
|
||||
h := &UserHandler{KratosAdmin: new(MockKratosAdmin)}
|
||||
app.Put("/users/bulk", func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{Role: domain.RoleTenantAdmin})
|
||||
return h.BulkUpdateUsers(c)
|
||||
})
|
||||
|
||||
role := domain.RoleSuperAdmin
|
||||
payload := map[string]interface{}{
|
||||
"userIds": []string{"u-1"},
|
||||
"role": &role,
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
req := httptest.NewRequest("PUT", "/users/bulk", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, _ := app.Test(req)
|
||||
assert.Equal(t, fiber.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserHandler_BulkDeleteUsers(t *testing.T) {
|
||||
@@ -1381,7 +1453,8 @@ func TestUserHandler_CreateUser_UsesAdditionalAppointmentAsPrimaryTenant(t *test
|
||||
mockOry.On("CreateUser", mock.MatchedBy(func(user *domain.BrokerUser) bool {
|
||||
return user.Attributes["tenant_id"] == tenantID &&
|
||||
user.Attributes["companyCode"] == "saman" &&
|
||||
user.Attributes["additionalAppointments"] != nil
|
||||
user.Attributes["additionalAppointments"] != nil &&
|
||||
user.Attributes["userType"] == nil
|
||||
}), mock.Anything).Return("u-appointment", nil).Once()
|
||||
mockKratos.On("GetIdentity", mock.Anything, "u-appointment").Return(&service.KratosIdentity{
|
||||
ID: "u-appointment",
|
||||
@@ -1498,6 +1571,171 @@ func TestUserHandler_CreateUser_AutoCreatesPersonalTenantWhenAssignmentMissing(t
|
||||
mockKratos.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_CreateUserAcceptsTenantSlugWithoutCompanyCode(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", h.CreateUser)
|
||||
|
||||
mockOry.On("GetPasswordPolicy").Return(&domain.PasswordPolicy{MinLength: 8}, nil).Once()
|
||||
mockTenant.On("GetTenantBySlug", mock.Anything, "test-tenant").Return(&domain.Tenant{
|
||||
ID: "tenant-id",
|
||||
Slug: "test-tenant",
|
||||
}, nil).Twice()
|
||||
mockTenant.On("GetTenant", mock.Anything, "tenant-id").Return(&domain.Tenant{
|
||||
ID: "tenant-id",
|
||||
Slug: "test-tenant",
|
||||
Config: domain.JSONMap{},
|
||||
}, nil).Once()
|
||||
mockOry.On("CreateUser", mock.MatchedBy(func(user *domain.BrokerUser) bool {
|
||||
return user.Attributes["companyCode"] == "test-tenant" &&
|
||||
user.Attributes["tenant_id"] == "tenant-id"
|
||||
}), "Password1!").Return("user-id", nil).Once()
|
||||
mockKratos.On("GetIdentity", mock.Anything, "user-id").Return(&service.KratosIdentity{
|
||||
ID: "user-id",
|
||||
State: "active",
|
||||
Traits: map[string]interface{}{
|
||||
"email": "user@test.com",
|
||||
"name": "Test User",
|
||||
"companyCode": "test-tenant",
|
||||
"tenant_id": "tenant-id",
|
||||
"role": domain.RoleUser,
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
body := `{"email":"user@test.com","password":"Password1!","name":"Test User","tenantSlug":"test-tenant"}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := app.Test(req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
mockTenant.AssertExpectations(t)
|
||||
mockOry.AssertExpectations(t)
|
||||
mockKratos.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_UpdateUserAcceptsTenantSlugWithoutCompanyCode(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
mockTenant := new(MockTenantServiceForUser)
|
||||
h := &UserHandler{
|
||||
KratosAdmin: mockKratos,
|
||||
TenantService: mockTenant,
|
||||
}
|
||||
app.Put("/users/:id", h.UpdateUser)
|
||||
|
||||
identity := &service.KratosIdentity{
|
||||
ID: "user-id",
|
||||
State: "active",
|
||||
Traits: map[string]interface{}{
|
||||
"email": "user@test.com",
|
||||
"name": "Test User",
|
||||
"companyCode": "old-tenant",
|
||||
"tenant_id": "old-tenant-id",
|
||||
"role": domain.RoleUser,
|
||||
},
|
||||
}
|
||||
mockKratos.On("GetIdentity", mock.Anything, "user-id").Return(identity, nil).Once()
|
||||
mockTenant.On("GetTenantBySlug", mock.Anything, "new-tenant").Return(&domain.Tenant{
|
||||
ID: "new-tenant-id",
|
||||
Slug: "new-tenant",
|
||||
}, nil).Twice()
|
||||
mockTenant.On("GetTenant", mock.Anything, "new-tenant-id").Return(&domain.Tenant{
|
||||
ID: "new-tenant-id",
|
||||
Slug: "new-tenant",
|
||||
Config: domain.JSONMap{},
|
||||
}, nil).Once()
|
||||
mockKratos.On("UpdateIdentity", mock.Anything, "user-id", mock.MatchedBy(func(traits map[string]interface{}) bool {
|
||||
return traits["companyCode"] == "new-tenant" &&
|
||||
traits["tenant_id"] == "new-tenant-id"
|
||||
}), "").Return(&service.KratosIdentity{
|
||||
ID: "user-id",
|
||||
State: "active",
|
||||
Traits: map[string]interface{}{
|
||||
"email": "user@test.com",
|
||||
"name": "Test User",
|
||||
"companyCode": "new-tenant",
|
||||
"tenant_id": "new-tenant-id",
|
||||
"role": domain.RoleUser,
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
body := `{"tenantSlug":"new-tenant"}`
|
||||
req := httptest.NewRequest(http.MethodPut, "/users/user-id", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := app.Test(req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
mockTenant.AssertExpectations(t)
|
||||
mockKratos.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_BulkUpdateUsersAcceptsTenantSlugWithoutCompanyCode(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
mockTenant := new(MockTenantServiceForUser)
|
||||
h := &UserHandler{
|
||||
KratosAdmin: mockKratos,
|
||||
TenantService: mockTenant,
|
||||
}
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{
|
||||
ID: "admin-id",
|
||||
Role: domain.RoleSuperAdmin,
|
||||
})
|
||||
return c.Next()
|
||||
})
|
||||
app.Put("/users/bulk", h.BulkUpdateUsers)
|
||||
|
||||
mockKratos.On("GetIdentity", mock.Anything, "user-id").Return(&service.KratosIdentity{
|
||||
ID: "user-id",
|
||||
State: "active",
|
||||
Traits: map[string]interface{}{
|
||||
"email": "user@test.com",
|
||||
"name": "Test User",
|
||||
"companyCode": "old-tenant",
|
||||
"tenant_id": "old-tenant-id",
|
||||
"role": domain.RoleUser,
|
||||
},
|
||||
}, nil).Once()
|
||||
mockTenant.On("GetTenantBySlug", mock.Anything, "new-tenant").Return(&domain.Tenant{
|
||||
ID: "new-tenant-id",
|
||||
Slug: "new-tenant",
|
||||
}, nil).Once()
|
||||
mockKratos.On("UpdateIdentity", mock.Anything, "user-id", mock.MatchedBy(func(traits map[string]interface{}) bool {
|
||||
return traits["companyCode"] == "new-tenant" &&
|
||||
traits["tenant_id"] == "new-tenant-id"
|
||||
}), "active").Return(&service.KratosIdentity{
|
||||
ID: "user-id",
|
||||
State: "active",
|
||||
Traits: map[string]interface{}{
|
||||
"email": "user@test.com",
|
||||
"name": "Test User",
|
||||
"companyCode": "new-tenant",
|
||||
"tenant_id": "new-tenant-id",
|
||||
"role": domain.RoleUser,
|
||||
},
|
||||
}, nil).Once()
|
||||
|
||||
body := `{"userIds":["user-id"],"tenantSlug":"new-tenant"}`
|
||||
req := httptest.NewRequest(http.MethodPut, "/users/bulk", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := app.Test(req)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
mockTenant.AssertExpectations(t)
|
||||
mockKratos.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_MapToLocalUserKeepsRoleAndGradeSeparate(t *testing.T) {
|
||||
handler := &UserHandler{}
|
||||
identity := service.KratosIdentity{
|
||||
|
||||
Reference in New Issue
Block a user