forked from baron/baron-sso
chore: snapshot local state before dev merge
This commit is contained in:
@@ -993,6 +993,22 @@ func TestUserHandler_BulkCreateUsers_ResolvesAdditionalAppointment(t *testing.T)
|
||||
mockOry.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestApplyTenantBoundGradeNormalizesDirectorLevelNames(t *testing.T) {
|
||||
tenant := &domain.Tenant{ID: "tenant-1", Slug: "tenant", Name: "Tenant"}
|
||||
|
||||
metadata := applyTenantBoundGrade(nil, tenant, "상무")
|
||||
appointments := userAppointmentSliceFromRaw(metadata["additionalAppointments"])
|
||||
require.Len(t, appointments, 1)
|
||||
appointment := appointments[0].(map[string]any)
|
||||
require.Equal(t, "상무이사", appointment["grade"])
|
||||
|
||||
metadata = applyTenantBoundGrade(metadata, tenant, "전무")
|
||||
appointments = userAppointmentSliceFromRaw(metadata["additionalAppointments"])
|
||||
require.Len(t, appointments, 1)
|
||||
appointment = appointments[0].(map[string]any)
|
||||
require.Equal(t, "전무이사", appointment["grade"])
|
||||
}
|
||||
|
||||
func TestUserHandler_BulkCreateUsers_AppendsEmailDomainTenantAtLowestPriority(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
@@ -1098,9 +1114,17 @@ func TestUserHandler_BulkCreateUsers_UsesEmailDomainTenantAsPrimaryWhenExplicitT
|
||||
|
||||
type identityMirrorRedisStub struct {
|
||||
mockRedisRepo
|
||||
pageCalls int
|
||||
fullCalls int
|
||||
failFull bool
|
||||
lastQuery service.IdentityMirrorPageQuery
|
||||
}
|
||||
|
||||
func (s *identityMirrorRedisStub) ListIdentityMirrors(ctx context.Context) ([]service.KratosIdentity, error) {
|
||||
s.fullCalls++
|
||||
if s.failFull {
|
||||
return nil, errors.New("full identity mirror materialization is forbidden")
|
||||
}
|
||||
identities := make([]service.KratosIdentity, 0, len(s.data))
|
||||
for key, raw := range s.data {
|
||||
if !strings.HasPrefix(key, "identity:mirror:") || key == "identity:mirror:state" {
|
||||
@@ -1118,6 +1142,35 @@ func (s *identityMirrorRedisStub) ListIdentityMirrors(ctx context.Context) ([]se
|
||||
return identities, nil
|
||||
}
|
||||
|
||||
func (s *identityMirrorRedisStub) ListIdentityMirrorPage(ctx context.Context, query service.IdentityMirrorPageQuery) (service.IdentityMirrorPageResult, error) {
|
||||
s.pageCalls++
|
||||
s.lastQuery = query
|
||||
identities := make([]service.KratosIdentity, 0, len(s.data))
|
||||
for key, raw := range s.data {
|
||||
if !strings.HasPrefix(key, "identity:mirror:") || key == "identity:mirror:state" {
|
||||
continue
|
||||
}
|
||||
var identity service.KratosIdentity
|
||||
if err := json.Unmarshal([]byte(raw), &identity); err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(identity.ID) == "" {
|
||||
continue
|
||||
}
|
||||
identities = append(identities, identity)
|
||||
}
|
||||
return pageIdentityMirrorSlice(identities, query)
|
||||
}
|
||||
|
||||
func (s *identityMirrorRedisStub) StoreIdentityMirror(ctx context.Context, identity service.KratosIdentity) error {
|
||||
raw, err := json.Marshal(identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.data[identityMirrorKey(identity.ID)] = string(raw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *identityMirrorRedisStub) GetIdentityCacheStatus(ctx context.Context) (domain.IdentityCacheStatus, error) {
|
||||
raw := s.data["identity:mirror:state"]
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
@@ -1174,7 +1227,7 @@ func TestUserHandler_ListUsersUsesIdentityMirrorAndDoesNotUseUserRepo(t *testing
|
||||
h := &UserHandler{
|
||||
KratosAdmin: mockKratos,
|
||||
UserRepo: mockRepo,
|
||||
IdentityCache: &identityMirrorRedisStub{mockRedisRepo{data: map[string]string{
|
||||
IdentityCache: &identityMirrorRedisStub{mockRedisRepo: mockRedisRepo{data: map[string]string{
|
||||
identityMirrorKey(mirrorIdentity.ID): string(rawMirrorIdentity),
|
||||
"identity:mirror:state": string(rawState),
|
||||
}}},
|
||||
@@ -1200,14 +1253,71 @@ func TestUserHandler_ListUsersUsesIdentityMirrorAndDoesNotUseUserRepo(t *testing
|
||||
require.Len(t, res.Items, 1)
|
||||
require.Equal(t, "mirror-user-1", res.Items[0].ID)
|
||||
require.Equal(t, "mirror1@example.com", res.Items[0].Email)
|
||||
cache := h.IdentityCache.(*identityMirrorRedisStub)
|
||||
require.Equal(t, 1, cache.pageCalls)
|
||||
require.Equal(t, 0, cache.fullCalls)
|
||||
mockRepo.AssertNotCalled(t, "List", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
mockKratos.AssertNotCalled(t, "ListIdentities", mock.Anything)
|
||||
}
|
||||
|
||||
func TestUserHandler_ListUsersPassesQueryToIdentityMirrorPageInsteadOfLoadingFullMirror(t *testing.T) {
|
||||
app := fiber.New()
|
||||
createdAt := time.Date(2026, 6, 17, 8, 50, 0, 0, time.UTC)
|
||||
identities := []service.KratosIdentity{
|
||||
{ID: "user-new", State: "active", CreatedAt: createdAt.Add(2 * time.Minute), UpdatedAt: createdAt, Traits: map[string]any{"email": "new@example.com", "name": "New User"}},
|
||||
{ID: "user-needle", State: "active", CreatedAt: createdAt.Add(time.Minute), UpdatedAt: createdAt, Traits: map[string]any{"email": "needle@example.com", "name": "Needle User"}},
|
||||
{ID: "user-old", State: "active", CreatedAt: createdAt, UpdatedAt: createdAt, Traits: map[string]any{"email": "old@example.com", "name": "Old User"}},
|
||||
}
|
||||
data := map[string]string{}
|
||||
for _, identity := range identities {
|
||||
raw, err := json.Marshal(identity)
|
||||
require.NoError(t, err)
|
||||
data[identityMirrorKey(identity.ID)] = string(raw)
|
||||
}
|
||||
state := domain.IdentityCacheStatus{
|
||||
Status: "ready",
|
||||
RedisReady: true,
|
||||
MirrorVersion: identityMirrorVersion,
|
||||
ObservedCount: int64(len(identities)),
|
||||
}
|
||||
rawState, err := json.Marshal(state)
|
||||
require.NoError(t, err)
|
||||
data["identity:mirror:state"] = string(rawState)
|
||||
|
||||
cache := &identityMirrorRedisStub{
|
||||
mockRedisRepo: mockRedisRepo{data: data},
|
||||
failFull: true,
|
||||
}
|
||||
h := &UserHandler{
|
||||
KratosAdmin: new(MockKratosAdmin),
|
||||
IdentityCache: cache,
|
||||
}
|
||||
|
||||
app.Use(func(c *fiber.Ctx) error {
|
||||
c.Locals("user_profile", &domain.UserProfileResponse{Role: domain.RoleSuperAdmin})
|
||||
return c.Next()
|
||||
})
|
||||
app.Get("/users", h.ListUsers)
|
||||
|
||||
req := httptest.NewRequest("GET", "/users?limit=1&search=needle", 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, 1)
|
||||
require.Equal(t, "user-needle", res.Items[0].ID)
|
||||
require.Equal(t, 1, cache.pageCalls)
|
||||
require.Equal(t, 0, cache.fullCalls)
|
||||
require.Equal(t, 1, cache.lastQuery.Limit)
|
||||
require.Equal(t, "needle", cache.lastQuery.Search)
|
||||
}
|
||||
|
||||
func TestUserHandler_ListUsersWarmsIdentityMirrorFromKratosWhenMirrorEmpty(t *testing.T) {
|
||||
app := fiber.New()
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo{data: map[string]string{}}}
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo: mockRedisRepo{data: map[string]string{}}}
|
||||
createdAt := time.Date(2026, 6, 8, 6, 40, 0, 0, time.UTC)
|
||||
|
||||
h := &UserHandler{
|
||||
@@ -1330,7 +1440,7 @@ func TestUserHandler_ListUsersTenantSlugFilterIncludesAdditionalAppointments(t *
|
||||
|
||||
func TestUserHandler_WarmIdentityMirrorRebuildsRedisFromKratos(t *testing.T) {
|
||||
mockKratos := new(MockKratosAdmin)
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo{data: map[string]string{
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo: mockRedisRepo{data: map[string]string{
|
||||
identityMirrorKey("stale-user"): `{"id":"stale-user"}`,
|
||||
}}}
|
||||
createdAt := time.Date(2026, 6, 12, 18, 30, 0, 0, time.UTC)
|
||||
@@ -1382,7 +1492,7 @@ func TestUserHandler_ListUsersRebuildsLegacyReadyMirrorWithoutVersion(t *testing
|
||||
}
|
||||
rawLegacyState, err := json.Marshal(legacyState)
|
||||
require.NoError(t, err)
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo{data: map[string]string{
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo: mockRedisRepo{data: map[string]string{
|
||||
identityMirrorKey(legacyIdentity.ID): string(rawLegacyIdentity),
|
||||
"identity:mirror:state": string(rawLegacyState),
|
||||
}}}
|
||||
@@ -1436,7 +1546,7 @@ func TestUserHandler_ListUsersRebuildsPartialMirrorFromKratos(t *testing.T) {
|
||||
}
|
||||
rawPartialIdentity, err := json.Marshal(partialIdentity)
|
||||
require.NoError(t, err)
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo{data: map[string]string{
|
||||
redis := &identityMirrorRedisStub{mockRedisRepo: mockRedisRepo{data: map[string]string{
|
||||
identityMirrorKey(partialIdentity.ID): string(rawPartialIdentity),
|
||||
}}}
|
||||
kratosIdentities := []service.KratosIdentity{
|
||||
@@ -3946,18 +4056,24 @@ func TestUserHandler_BulkUpdateUsersRejectsInternalDomainMoveToPersonalTenant(t
|
||||
mockTenant.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestUserHandler_MapToLocalUserKeepsRoleAndGradeSeparate(t *testing.T) {
|
||||
func TestUserHandler_MapToLocalUserPreservesTenantBoundGradeForCompatibility(t *testing.T) {
|
||||
handler := &UserHandler{}
|
||||
identity := service.KratosIdentity{
|
||||
ID: "user-grade-id",
|
||||
State: "active",
|
||||
Traits: map[string]any{
|
||||
"email": "grade@example.com",
|
||||
"name": "Grade User",
|
||||
"role": domain.RoleUser,
|
||||
"grade": "수석",
|
||||
"position": "팀장",
|
||||
"companyCode": "hanmac",
|
||||
"email": "grade@example.com",
|
||||
"name": "Grade User",
|
||||
"role": domain.RoleUser,
|
||||
"grade": "수석",
|
||||
"position": "팀장",
|
||||
"tenant_id": "tenant-1",
|
||||
"additionalAppointments": []any{
|
||||
map[string]any{
|
||||
"tenantId": "tenant-1",
|
||||
"grade": "수석",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3967,6 +4083,7 @@ func TestUserHandler_MapToLocalUserKeepsRoleAndGradeSeparate(t *testing.T) {
|
||||
assert.Equal(t, "수석", localUser.Grade)
|
||||
assert.Equal(t, "팀장", localUser.Position)
|
||||
assert.NotContains(t, localUser.Metadata, "grade")
|
||||
assert.Contains(t, localUser.Metadata, "additionalAppointments")
|
||||
}
|
||||
|
||||
func (m *MockKratosAdmin) CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user