forked from baron/baron-sso
3584 lines
117 KiB
Go
3584 lines
117 KiB
Go
package service
|
|
|
|
import (
|
|
"baron-sso-backend/internal/domain"
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestWorksmobileSyncServiceRejectsAliasEmailAlreadyUsedByOtherUser(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
TenantID: &tenantID,
|
|
Metadata: domain.JSONMap{
|
|
"aliasEmails": []any{"used@hanmaceng.co.kr"},
|
|
},
|
|
}
|
|
existing := domain.User{
|
|
ID: "existing-user",
|
|
Email: "used@hanmaceng.co.kr",
|
|
Name: "Existing",
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target, existing}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "", "")
|
|
|
|
require.Nil(t, item)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "이미 사용 중")
|
|
require.Empty(t, outboxRepo.created)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesSuspendedUserStatusWithOrganizations(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
userGroupID := "saman-user-group"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
userGroup := domain.Tenant{
|
|
ID: userGroupID,
|
|
Slug: "saman-team",
|
|
Name: "Saman Team",
|
|
Type: domain.TenantTypeUserGroup,
|
|
ParentID: &tenantID,
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusSuspended,
|
|
TenantID: &userGroupID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant, userGroupID: userGroup},
|
|
list: []domain.Tenant{root, tenant, userGroup},
|
|
},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "", "")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionSuspend, outboxRepo.created[0].Action)
|
|
require.Equal(t, domain.UserStatusSuspended, outboxRepo.created[0].Payload["baronStatus"])
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileUserPayload)
|
|
require.True(t, ok)
|
|
require.NotEmpty(t, request.Organizations)
|
|
require.Equal(t, "target@samaneng.com", outboxRepo.created[0].Payload["loginEmail"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesUserCredentialBatchID(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "batch-1", "InputPass1!")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, "batch-1", outboxRepo.created[0].Payload["credentialBatchId"])
|
|
require.NotEmpty(t, outboxRepo.created[0].Payload["credentialBatchCreatedAt"])
|
|
require.Equal(t, "Target", outboxRepo.created[0].Payload["displayName"])
|
|
require.Equal(t, "Saman", outboxRepo.created[0].Payload["primaryLeafOrgName"])
|
|
require.Equal(t, "InputPass1!", outboxRepo.created[0].Payload["initialPassword"])
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileUserPayload)
|
|
require.True(t, ok)
|
|
require.Equal(t, "ADMIN", request.PasswordConfig.PasswordCreationType)
|
|
require.Equal(t, "InputPass1!", request.PasswordConfig.Password)
|
|
require.NotNil(t, request.PasswordConfig.ChangePasswordAtNextLogin)
|
|
require.True(t, *request.PasswordConfig.ChangePasswordAtNextLogin)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceSkipsAdminInitialPasswordWhenEmpty(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "batch-1", "")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
initialPassword := stringValue(outboxRepo.created[0].Payload["initialPassword"])
|
|
require.Empty(t, initialPassword)
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileUserPayload)
|
|
require.True(t, ok)
|
|
require.Empty(t, request.PasswordConfig.PasswordCreationType)
|
|
require.Empty(t, request.PasswordConfig.Password)
|
|
require.Nil(t, request.PasswordConfig.ChangePasswordAtNextLogin)
|
|
require.Empty(t, stringValue(outboxRepo.created[0].Payload["credentialBatchId"]))
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceCreatesDistinctUserSyncHistoryJobs(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
first, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "", "")
|
|
require.NoError(t, err)
|
|
second, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "", "")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, first)
|
|
require.NotNil(t, second)
|
|
require.Len(t, outboxRepo.created, 2)
|
|
require.NotEqual(t, outboxRepo.created[0].DedupeKey, outboxRepo.created[1].DedupeKey)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceCreatesDistinctAutomaticUserSyncHistoryJobs(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
require.NoError(t, service.EnqueueUserUpsertIfInScope(context.Background(), target))
|
|
require.NoError(t, service.EnqueueUserUpsertIfInScope(context.Background(), target))
|
|
|
|
require.Len(t, outboxRepo.created, 2)
|
|
require.NotEqual(t, outboxRepo.created[0].DedupeKey, outboxRepo.created[1].DedupeKey)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceUserUpdateIsUpdateOnlyWithoutInitialPassword(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
require.NoError(t, service.EnqueueUserUpdateIfInScope(context.Background(), target))
|
|
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, "update_only", outboxRepo.created[0].Payload["provisioningMode"])
|
|
require.NotContains(t, outboxRepo.created[0].Payload, "initialPassword")
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileUserPayload)
|
|
require.True(t, ok)
|
|
require.True(t, request.PasswordConfig.IsZero())
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesUserPasswordResetCredentialBatch(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-leaf"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "people-growth",
|
|
Name: "인재성장",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserPasswordReset(context.Background(), rootID, target.ID, "reset-batch-1")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionPasswordReset, outboxRepo.created[0].Action)
|
|
require.Equal(t, "reset-batch-1", outboxRepo.created[0].Payload["credentialBatchId"])
|
|
require.Equal(t, "worksmobile_password_reset", outboxRepo.created[0].Payload["credentialOperation"])
|
|
require.NotEmpty(t, outboxRepo.created[0].Payload["credentialBatchCreatedAt"])
|
|
require.Equal(t, "target@samaneng.com", outboxRepo.created[0].Payload["loginEmail"])
|
|
require.Equal(t, "Target", outboxRepo.created[0].Payload["displayName"])
|
|
require.Equal(t, "인재성장", outboxRepo.created[0].Payload["primaryLeafOrgName"])
|
|
require.NotEmpty(t, outboxRepo.created[0].Payload["initialPassword"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServicePasswordResetAllowsExcludedPrimaryTenant(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
excludedOrgID := "excluded-org"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
excludedOrg := domain.Tenant{
|
|
ID: excludedOrgID,
|
|
Slug: "excluded-team",
|
|
Name: "Excluded Team",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &rootID,
|
|
Config: domain.JSONMap{"worksmobileExcluded": true},
|
|
}
|
|
target := domain.User{
|
|
ID: "target-user",
|
|
Email: "target@samaneng.com",
|
|
Name: "Target",
|
|
Status: domain.UserStatusActive,
|
|
TenantID: &excludedOrgID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, excludedOrgID: excludedOrg}, list: []domain.Tenant{root, excludedOrg}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserPasswordReset(context.Background(), rootID, target.ID, "reset-batch-1")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionPasswordReset, outboxRepo.created[0].Action)
|
|
require.Equal(t, "target@samaneng.com", outboxRepo.created[0].Payload["loginEmail"])
|
|
require.Equal(t, "Target", outboxRepo.created[0].Payload["displayName"])
|
|
require.Empty(t, outboxRepo.created[0].Payload["primaryLeafOrgName"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceFiltersInitialPasswordsByCredentialBatchID(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{
|
|
credentialBatchJobs: []domain.WorksmobileOutbox{
|
|
{
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": rootID,
|
|
"loginEmail": "batch-user@samaneng.com",
|
|
"displayName": "Batch User",
|
|
"primaryLeafOrgName": "인재성장",
|
|
"initialPassword": "BatchPass1!",
|
|
"credentialBatchId": "batch-1",
|
|
},
|
|
},
|
|
{
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": rootID,
|
|
"loginEmail": "other-user@samaneng.com",
|
|
"initialPassword": "OtherPass1!",
|
|
"credentialBatchId": "batch-2",
|
|
},
|
|
},
|
|
{
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": rootID,
|
|
"loginEmail": "legacy-user@samaneng.com",
|
|
"initialPassword": "LegacyPass1!",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}, list: []domain.Tenant{root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
credentials, err := service.ListInitialPasswordCredentials(context.Background(), rootID, "batch-1")
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []WorksmobileInitialPasswordCredential{
|
|
{
|
|
Email: "batch-user@samaneng.com",
|
|
Name: "Batch User",
|
|
PrimaryLeafOrgName: "인재성장",
|
|
InitialPassword: "BatchPass1!",
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
},
|
|
}, credentials)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceDeletesCredentialBatchPasswordsButKeepsHistory(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{
|
|
credentialBatchJobs: []domain.WorksmobileOutbox{
|
|
{
|
|
ID: "job-1",
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": rootID,
|
|
"loginEmail": "batch-user@samaneng.com",
|
|
"initialPassword": "BatchPass1!",
|
|
"credentialBatchId": "batch-1",
|
|
"credentialOperation": "worksmobile_user_sync",
|
|
"request": map[string]any{"passwordConfig": map[string]any{"password": "BatchPass1!"}},
|
|
},
|
|
},
|
|
{
|
|
ID: "job-2",
|
|
ResourceID: "failed-user",
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
Status: domain.WorksmobileOutboxStatusFailed,
|
|
RetryCount: 2,
|
|
LastError: "worksmobile api failed",
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": rootID,
|
|
"loginEmail": "failed-user@samaneng.com",
|
|
"initialPassword": "FailedPass1!",
|
|
"credentialBatchId": "batch-1",
|
|
"credentialOperation": "worksmobile_user_sync",
|
|
"request": map[string]any{"passwordConfig": map[string]any{"password": "FailedPass1!"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}, list: []domain.Tenant{root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
before, err := service.ListCredentialBatches(context.Background(), rootID)
|
|
require.NoError(t, err)
|
|
require.Len(t, before, 1)
|
|
require.True(t, before[0].HasPasswords)
|
|
require.Equal(t, 1, before[0].FailedCount)
|
|
require.Len(t, before[0].Failures, 1)
|
|
require.Equal(t, "failed-user", before[0].Failures[0].UserID)
|
|
require.Equal(t, "failed-user@samaneng.com", before[0].Failures[0].Email)
|
|
require.Equal(t, "worksmobile api failed", before[0].Failures[0].LastError)
|
|
|
|
after, err := service.DeleteCredentialBatchPasswords(context.Background(), rootID, "batch-1")
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "batch-1", after.BatchID)
|
|
require.False(t, after.HasPasswords)
|
|
require.Equal(t, 2, after.UserCount)
|
|
require.NotEmpty(t, after.DeletedAt)
|
|
require.Len(t, outboxRepo.payloadUpdates, 2)
|
|
require.Empty(t, stringValue(outboxRepo.payloadUpdates[0]["initialPassword"]))
|
|
require.Empty(t, stringValue(outboxRepo.payloadUpdates[1]["initialPassword"]))
|
|
request := outboxRepo.payloadUpdates[0]["request"].(map[string]any)
|
|
passwordConfig := request["passwordConfig"].(map[string]any)
|
|
require.Empty(t, stringValue(passwordConfig["password"]))
|
|
}
|
|
|
|
func TestAggregateWorksmobileCredentialBatchesUsesCredentialBatchCreatedAt(t *testing.T) {
|
|
oldCreatedAt := time.Date(2026, 5, 29, 1, 4, 15, 0, time.UTC)
|
|
batchCreatedAt := time.Date(2026, 6, 1, 7, 20, 0, 0, time.UTC)
|
|
|
|
batches := aggregateWorksmobileCredentialBatches([]domain.WorksmobileOutbox{
|
|
{
|
|
ID: "job-1",
|
|
CreatedAt: oldCreatedAt,
|
|
UpdatedAt: batchCreatedAt.Add(time.Minute),
|
|
Status: domain.WorksmobileOutboxStatusPending,
|
|
Payload: domain.JSONMap{
|
|
"credentialBatchId": "batch-1",
|
|
"credentialOperation": "worksmobile_user_sync",
|
|
"credentialBatchCreatedAt": batchCreatedAt.Format(time.RFC3339),
|
|
},
|
|
},
|
|
})
|
|
|
|
require.Len(t, batches, 1)
|
|
require.Equal(t, batchCreatedAt, batches[0].CreatedAt)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceDeprovisionsArchivedUser(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
tenantID := "saman-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "Hanmac Family",
|
|
}
|
|
tenant := domain.Tenant{
|
|
ID: tenantID,
|
|
Slug: "saman",
|
|
Name: "Saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
target := domain.User{
|
|
ID: "archived-user",
|
|
Email: "archived@samaneng.com",
|
|
Name: "Archived",
|
|
Status: domain.UserStatusArchived,
|
|
TenantID: &tenantID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, tenantID: tenant}, list: []domain.Tenant{root, tenant}},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{target.ID: target}, byTenant: []domain.User{target}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "", "")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Equal(t, domain.WorksmobileActionDelete, item.Action)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
|
|
err = service.EnqueueUserUpsertIfInScope(context.Background(), target)
|
|
require.NoError(t, err)
|
|
require.Len(t, outboxRepo.created, 2)
|
|
require.Equal(t, domain.WorksmobileActionDelete, outboxRepo.created[1].Action)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceOverviewExposesAdminTenantIDForPasswordManageLink(t *testing.T) {
|
|
t.Setenv("WORKS_ADMIN_TENANT_ID", "works-tenant-1")
|
|
root := domain.Tenant{
|
|
ID: "root-tenant",
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{root.ID: root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
&fakeWorksmobileOutboxRepo{},
|
|
nil,
|
|
)
|
|
|
|
overview, err := service.GetTenantOverview(context.Background(), root.ID)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, "works-tenant-1", overview.Config.AdminTenantID)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceOverviewKeepsSafeRecentJobChangeLogPayload(t *testing.T) {
|
|
root := domain.Tenant{
|
|
ID: "root-tenant",
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{
|
|
recent: []domain.WorksmobileOutbox{
|
|
{
|
|
ID: "job-user-upsert",
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
ResourceID: "user-1",
|
|
Action: domain.WorksmobileActionUpsert,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": root.ID,
|
|
"loginEmail": "changed@example.com",
|
|
"displayName": "변경 사용자",
|
|
"primaryLeafOrgName": "인재성장",
|
|
"initialPassword": "Secret123!",
|
|
"request": WorksmobileUserPayload{
|
|
Email: "changed@example.com",
|
|
UserExternalKey: "user-1",
|
|
UserName: WorksmobileUserName{LastName: "변경 사용자"},
|
|
PasswordConfig: WorksmobilePasswordConfig{Password: "Secret123!"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
ID: "job-org-upsert",
|
|
ResourceType: domain.WorksmobileResourceOrgUnit,
|
|
ResourceID: "org-1",
|
|
Action: domain.WorksmobileActionUpsert,
|
|
Status: domain.WorksmobileOutboxStatusProcessed,
|
|
Payload: domain.JSONMap{
|
|
"tenantRootId": root.ID,
|
|
"matchLocalPart": "people-growth",
|
|
"request": WorksmobileOrgUnitPayload{
|
|
OrgUnitName: "인재성장",
|
|
Email: "people-growth@example.com",
|
|
OrgUnitExternalKey: "org-1",
|
|
ParentOrgUnitID: "externalKey:parent-1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{root.ID: root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
overview, err := service.GetTenantOverview(context.Background(), root.ID)
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, overview.RecentJobs, 2)
|
|
userPayload := overview.RecentJobs[0].Payload
|
|
require.Equal(t, "changed@example.com", userPayload["loginEmail"])
|
|
require.Equal(t, "변경 사용자", userPayload["displayName"])
|
|
require.Equal(t, "인재성장", userPayload["primaryLeafOrgName"])
|
|
require.NotContains(t, userPayload, "initialPassword")
|
|
require.NotContains(t, userPayload, "request")
|
|
require.Equal(t, domain.JSONMap{
|
|
"email": "changed@example.com",
|
|
"displayName": "변경 사용자",
|
|
"userExternalKey": "user-1",
|
|
}, userPayload["requestSummary"])
|
|
|
|
orgPayload := overview.RecentJobs[1].Payload
|
|
require.Equal(t, "people-growth", orgPayload["matchLocalPart"])
|
|
require.NotContains(t, orgPayload, "request")
|
|
require.Equal(t, domain.JSONMap{
|
|
"email": "people-growth@example.com",
|
|
"orgUnitName": "인재성장",
|
|
"orgUnitExternalKey": "org-1",
|
|
"parentOrgUnitId": "externalKey:parent-1",
|
|
}, orgPayload["requestSummary"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceOverviewScopesRecentJobsToTenantRoot(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
childID := "child-org"
|
|
otherRootID := "other-root"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
child := domain.Tenant{
|
|
ID: childID,
|
|
Slug: "structure-planning",
|
|
Name: "구조물계획",
|
|
Type: domain.TenantTypeUserGroup,
|
|
ParentID: &rootID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{
|
|
recent: []domain.WorksmobileOutbox{
|
|
{
|
|
ID: "job-root-user-failed",
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
ResourceID: "user-1",
|
|
Status: domain.WorksmobileOutboxStatusFailed,
|
|
Payload: domain.JSONMap{"tenantRootId": rootID},
|
|
LastError: "worksmobile api failed",
|
|
},
|
|
{
|
|
ID: "job-child-org-legacy",
|
|
ResourceType: domain.WorksmobileResourceOrgUnit,
|
|
ResourceID: childID,
|
|
Status: domain.WorksmobileOutboxStatusFailed,
|
|
LastError: "legacy org job without tenantRootId",
|
|
},
|
|
{
|
|
ID: "job-other-root",
|
|
ResourceType: domain.WorksmobileResourceUser,
|
|
ResourceID: "user-2",
|
|
Status: domain.WorksmobileOutboxStatusFailed,
|
|
Payload: domain.JSONMap{"tenantRootId": otherRootID},
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, childID: child},
|
|
list: []domain.Tenant{child},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
overview, err := service.GetTenantOverview(context.Background(), rootID)
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, overview.RecentJobs, 2)
|
|
require.Equal(t, "job-root-user-failed", overview.RecentJobs[0].ID)
|
|
require.Equal(t, "job-child-org-legacy", overview.RecentJobs[1].ID)
|
|
require.Equal(t, "legacy org job without tenantRootId", overview.RecentJobs[1].LastError)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueueTenantUpsertReflectsChangedParentOrgUnit(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
companyID := "saman-tenant"
|
|
newParentID := "new-parent-org"
|
|
childID := "child-org"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
Type: domain.TenantTypeCompanyGroup,
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
newParent := domain.Tenant{
|
|
ID: newParentID,
|
|
Slug: "planning",
|
|
Name: "총괄기획",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &companyID,
|
|
}
|
|
child := domain.Tenant{
|
|
ID: childID,
|
|
Slug: "people-growth",
|
|
Name: "인재성장",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &newParentID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{
|
|
rootID: root,
|
|
companyID: company,
|
|
newParentID: newParent,
|
|
childID: child,
|
|
},
|
|
list: []domain.Tenant{root, company, newParent, child},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
err := service.EnqueueTenantUpsertIfInScope(context.Background(), child)
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileResourceOrgUnit, outboxRepo.created[0].ResourceType)
|
|
require.Equal(t, domain.WorksmobileActionUpsert, outboxRepo.created[0].Action)
|
|
require.Equal(t, childID, outboxRepo.created[0].ResourceID)
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.True(t, ok)
|
|
require.Equal(t, childID, request.OrgUnitExternalKey)
|
|
require.Equal(t, "externalKey:"+newParentID, request.ParentOrgUnitID)
|
|
require.Equal(t, "people-growth", outboxRepo.created[0].Payload["matchLocalPart"])
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsUsesOrganizationsAndBarongroupChildCompanies(t *testing.T) {
|
|
parentID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: parentID,
|
|
Name: "한맥가족",
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Type: domain.TenantTypeCompanyGroup,
|
|
}
|
|
hanmac := domain.Tenant{
|
|
ID: "hanmac-tenant",
|
|
Name: "한맥기술",
|
|
Slug: "hanmac",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &parentID,
|
|
}
|
|
barongroup := domain.Tenant{
|
|
ID: "barongroup-tenant",
|
|
Name: "바론그룹",
|
|
Slug: "baron-group",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &parentID,
|
|
}
|
|
barongroupChildCompany := domain.Tenant{
|
|
ID: "barongroup-child-company",
|
|
Name: "바론그룹 하위 회사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &barongroup.ID,
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: "organization-tenant",
|
|
Name: "정규 조직",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &hanmac.ID,
|
|
}
|
|
userGroup := domain.Tenant{
|
|
ID: "legacy-user-group-tenant",
|
|
Name: "사용자 그룹 조직",
|
|
Type: domain.TenantTypeUserGroup,
|
|
ParentID: &hanmac.ID,
|
|
}
|
|
|
|
items := compareWorksmobileGroups(
|
|
[]domain.Tenant{root, hanmac, barongroup, barongroupChildCompany, organization, userGroup},
|
|
[]WorksmobileRemoteGroup{
|
|
{ID: "works-root", ExternalID: root.ID, DisplayName: root.Name},
|
|
{ID: "works-hanmac", ExternalID: hanmac.ID, DisplayName: hanmac.Name, Email: "hanmac@hanmaceng.co.kr"},
|
|
{ID: "works-barongroup", ExternalID: barongroup.ID, DisplayName: barongroup.Name},
|
|
{ID: "works-barongroup-child", ExternalID: barongroupChildCompany.ID, DisplayName: barongroupChildCompany.Name},
|
|
{ID: "works-organization", ExternalID: organization.ID, DisplayName: organization.Name, ParentID: "works-hanmac"},
|
|
{ID: "works-user-group", ExternalID: userGroup.ID, DisplayName: userGroup.Name, ParentID: "works-hanmac"},
|
|
{ID: "works-orphan", ExternalID: "works-orphan", DisplayName: "WORKS 전용 조직"},
|
|
},
|
|
true,
|
|
)
|
|
|
|
require.Len(t, items, 4)
|
|
require.Equal(t, barongroupChildCompany.ID, items[0].BaronID)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.Equal(t, organization.ID, items[1].BaronID)
|
|
require.Equal(t, "matched", items[1].Status)
|
|
require.Equal(t, "works-hanmac", items[1].WorksmobileParentID)
|
|
require.Equal(t, hanmac.Name, items[1].WorksmobileParentName)
|
|
require.Equal(t, "hanmac@hanmaceng.co.kr", items[1].WorksmobileParentEmail)
|
|
require.Equal(t, hanmac.ID, items[1].WorksmobileParentExternalKey)
|
|
require.Equal(t, "works-hanmac", items[1].BaronParentWorksmobileID)
|
|
require.Equal(t, hanmac.Name, items[1].BaronParentWorksmobileName)
|
|
require.Equal(t, "hanmac@hanmaceng.co.kr", items[1].BaronParentWorksmobileEmail)
|
|
require.Equal(t, userGroup.ID, items[2].BaronID)
|
|
require.Equal(t, "matched", items[2].Status)
|
|
require.Equal(t, "works-orphan", items[3].ExternalKey)
|
|
require.Equal(t, "missing_in_baron", items[3].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsShowsUserGroupMissingInWorksmobile(t *testing.T) {
|
|
parentID := "company-tenant"
|
|
userGroup := domain.Tenant{
|
|
ID: "team-tenant",
|
|
Name: "신규 팀",
|
|
Slug: "new-team",
|
|
Type: domain.TenantTypeUserGroup,
|
|
ParentID: &parentID,
|
|
}
|
|
|
|
items := compareWorksmobileGroups(
|
|
[]domain.Tenant{
|
|
{ID: parentID, Slug: "company", Name: "계열사", Type: domain.TenantTypeCompany},
|
|
userGroup,
|
|
},
|
|
nil,
|
|
false,
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, userGroup.ID, items[0].BaronID)
|
|
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsMarksMatchedOrgUnitNeedsUpdate(t *testing.T) {
|
|
parentID := "parent-tenant"
|
|
tenant := domain.Tenant{
|
|
ID: "team-tenant",
|
|
Name: "변경된 팀명",
|
|
Slug: "team",
|
|
Type: domain.TenantTypeUserGroup,
|
|
ParentID: &parentID,
|
|
}
|
|
|
|
items := compareWorksmobileGroups(
|
|
[]domain.Tenant{
|
|
{ID: parentID, Slug: "parent", Name: "상위 조직", Type: domain.TenantTypeUserGroup},
|
|
tenant,
|
|
},
|
|
[]WorksmobileRemoteGroup{
|
|
{ID: "works-parent", ExternalID: parentID, DisplayName: "상위 조직"},
|
|
{ID: "works-team", ExternalID: tenant.ID, DisplayName: "이전 팀명", ParentID: "works-parent"},
|
|
},
|
|
false,
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, tenant.ID, items[0].BaronID)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsCoversHanmacOrganizationRegressionIDs(t *testing.T) {
|
|
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
|
samanID := "9caf62e1-297d-4e8f-870b-61780998bbeb"
|
|
hanmacID := "369c1843-56af-4344-9c21-0e01197ab861"
|
|
baronGroupID := "96369f12-6b66-4b2a-a916-d1c99d326f02"
|
|
changedID := "818c856b-9545-442f-b827-d1c569f200b0"
|
|
hanmacOnlyID := "2d217948-9c5a-42ea-805b-eef9c7421775"
|
|
baronOnlyID := "32464fd6-da51-473f-844a-ab88603ad1f0"
|
|
localTenants := []domain.Tenant{
|
|
{ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
{ID: samanID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID},
|
|
{ID: hanmacID, Slug: "hanmac", Name: "한맥기술", Type: domain.TenantTypeCompany, ParentID: &rootID},
|
|
{ID: baronGroupID, Slug: "baron-group", Name: "바론그룹", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
{ID: changedID, Slug: "rnd-saman", Name: "삼안기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &samanID},
|
|
{ID: hanmacOnlyID, Slug: "rnd-hanmac", Name: "한맥기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &hanmacID},
|
|
{ID: baronOnlyID, Slug: "rnd-baron", Name: "바론기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &baronGroupID},
|
|
}
|
|
remoteGroups := []WorksmobileRemoteGroup{
|
|
{ID: "works-saman", ExternalID: samanID, DisplayName: "삼안"},
|
|
{ID: "works-hanmac", ExternalID: hanmacID, DisplayName: "한맥기술"},
|
|
{
|
|
ID: "works-rnd-saman",
|
|
ExternalID: changedID,
|
|
DisplayName: "삼안기술개발센터(조직도용)",
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
|
itemsByBaronID := map[string]WorksmobileComparisonItem{}
|
|
for _, item := range items {
|
|
itemsByBaronID[item.BaronID] = item
|
|
}
|
|
|
|
require.Equal(t, "needs_update", itemsByBaronID[changedID].Status)
|
|
require.Equal(t, "missing_in_worksmobile", itemsByBaronID[hanmacOnlyID].Status)
|
|
require.Equal(t, "missing_in_worksmobile", itemsByBaronID[baronOnlyID].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsDoesNotMatchBaronGroupOrganizationInGPDTDCDomain(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
|
baronGroupID := "96369f12-6b66-4b2a-a916-d1c99d326f02"
|
|
orgID := "32464fd6-da51-473f-844a-ab88603ad1f0"
|
|
localTenants := []domain.Tenant{
|
|
{ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
{ID: baronGroupID, Slug: "baron-group", Name: "바론그룹", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
{ID: orgID, Slug: "rnd-baron", Name: "바론기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &baronGroupID},
|
|
}
|
|
remoteGroups := []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-rnd-baron-gpdtdc",
|
|
ExternalID: orgID,
|
|
DisplayName: "바론기술개발센터(조직도용)",
|
|
DomainID: 1003,
|
|
DomainName: "총괄기획&기술개발센터",
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, orgID, items[0].BaronID)
|
|
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
|
require.Empty(t, items[0].WorksmobileID)
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsMatchesBaronGroupOrganizationInBaronGroupDomain(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
|
baronGroupID := "96369f12-6b66-4b2a-a916-d1c99d326f02"
|
|
orgID := "32464fd6-da51-473f-844a-ab88603ad1f0"
|
|
localTenants := []domain.Tenant{
|
|
{ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
{ID: baronGroupID, Slug: "baron-group", Name: "바론그룹", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
{ID: orgID, Slug: "rnd-baron", Name: "바론기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &baronGroupID},
|
|
}
|
|
remoteGroups := []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-rnd-baron",
|
|
ExternalID: orgID,
|
|
DisplayName: "바론기술개발센터(조직도용)",
|
|
DomainID: 1004,
|
|
DomainName: "바론그룹",
|
|
},
|
|
}
|
|
|
|
diffOnly := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
|
all := compareWorksmobileGroups(localTenants, remoteGroups, true)
|
|
|
|
require.Empty(t, diffOnly)
|
|
require.Len(t, all, 1)
|
|
require.Equal(t, orgID, all[0].BaronID)
|
|
require.Equal(t, "matched", all[0].Status)
|
|
require.Equal(t, int64(1004), all[0].WorksmobileDomainID)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceRejectsDomainCompanyOrgUnitSync(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company}, list: []domain.Tenant{root, company}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, companyID)
|
|
|
|
require.Nil(t, item)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "worksmobile orgunit tenant")
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileResourceOrgUnit, outboxRepo.created[0].ResourceType)
|
|
require.Equal(t, domain.WorksmobileOutboxStatusFailed, outboxRepo.created[0].Status)
|
|
require.Equal(t, companyID, outboxRepo.created[0].ResourceID)
|
|
require.Equal(t, rootID, outboxRepo.created[0].Payload["tenantRootId"])
|
|
require.Equal(t, "target tenant is not a worksmobile orgunit tenant", outboxRepo.created[0].LastError)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesBarongroupChildCompanyOrgUnitSync(t *testing.T) {
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "root-tenant"
|
|
barongroupID := "barongroup-tenant"
|
|
companyID := "barongroup-child-company"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
barongroup := domain.Tenant{
|
|
ID: barongroupID,
|
|
Slug: "baron-group",
|
|
Name: "바론그룹",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Slug: "barongroup-child",
|
|
Name: "바론그룹 하위 회사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &barongroupID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, barongroupID: barongroup, companyID: company}, list: []domain.Tenant{root, barongroup, company}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, companyID)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.Equal(t, companyID, request.OrgUnitExternalKey)
|
|
require.Empty(t, request.ParentOrgUnitID)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesOrganizationOrgUnitSync(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
organizationID := "organization-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: organizationID,
|
|
Slug: "engineering",
|
|
Name: "기술본부",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &companyID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, companyID: company, organizationID: organization},
|
|
list: []domain.Tenant{root, company, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, organizationID)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.Equal(t, organizationID, request.OrgUnitExternalKey)
|
|
require.Empty(t, request.ParentOrgUnitID)
|
|
require.Equal(t, 1, request.DisplayOrder)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesExternalKeyMissingOrgUnitDelete(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
client := &fakeWorksmobileDirectoryClient{
|
|
groups: []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-org-1",
|
|
DisplayName: "WORKS 전용 조직",
|
|
DomainID: 1001,
|
|
ParentID: "works-parent",
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
client,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitDelete(context.Background(), rootID, "works-org-1")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionDelete, outboxRepo.created[0].Action)
|
|
require.Equal(t, "works-org-1", outboxRepo.created[0].Payload["worksmobileId"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceEnqueuesExternalKeyPresentWorksOnlyOrgUnitDelete(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
client := &fakeWorksmobileDirectoryClient{
|
|
groups: []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-org-1",
|
|
ExternalID: "baron-tenant-1",
|
|
ParentID: "works-parent",
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
client,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitDelete(context.Background(), rootID, "works-org-1")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionDelete, outboxRepo.created[0].Action)
|
|
require.Equal(t, "baron-tenant-1", outboxRepo.created[0].Payload["externalKey"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceDeletesWorksOrgUnitEvenWhenSlugLocalPartMatches(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
orgID := "baron-org-1"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: orgID,
|
|
Slug: "tech-dev-center",
|
|
Name: "기술개발센터",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &rootID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
client := &fakeWorksmobileDirectoryClient{
|
|
groups: []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-org-1",
|
|
ExternalID: "legacy-external-key",
|
|
DisplayName: "기술개발센터",
|
|
MailLocalPart: "tech-dev-center",
|
|
DomainID: 1001,
|
|
ParentID: "works-parent",
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, orgID: organization},
|
|
list: []domain.Tenant{root, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
client,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitDelete(context.Background(), rootID, "works-org-1")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionDelete, outboxRepo.created[0].Action)
|
|
require.Equal(t, "works-org-1", outboxRepo.created[0].ResourceID)
|
|
require.Equal(t, "works-org-1", outboxRepo.created[0].Payload["worksmobileId"])
|
|
require.Equal(t, "legacy-external-key", outboxRepo.created[0].Payload["externalKey"])
|
|
}
|
|
|
|
func TestCompareWorksmobileGroupsFillsParentDisplayFromBaronParentMatch(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
parent := domain.Tenant{
|
|
ID: "parent-tenant",
|
|
Name: "삼안",
|
|
Slug: "saman",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
child := domain.Tenant{
|
|
ID: "child-tenant",
|
|
Name: "업무",
|
|
Slug: "operations",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &parent.ID,
|
|
}
|
|
|
|
items := compareWorksmobileGroups(
|
|
[]domain.Tenant{
|
|
{ID: rootID, Name: "한맥가족", Slug: HanmacFamilyTenantSlug, Type: domain.TenantTypeCompanyGroup},
|
|
parent,
|
|
child,
|
|
},
|
|
[]WorksmobileRemoteGroup{
|
|
{ID: "works-parent", ExternalID: parent.ID, DisplayName: "삼안", Email: "saman@samaneng.com"},
|
|
{ID: "works-child", ExternalID: child.ID, DisplayName: "업무", ParentID: "works-parent"},
|
|
},
|
|
true,
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, child.ID, items[0].BaronID)
|
|
require.Equal(t, "works-parent", items[0].WorksmobileParentID)
|
|
require.Equal(t, "삼안", items[0].WorksmobileParentName)
|
|
require.Equal(t, "saman@samaneng.com", items[0].WorksmobileParentEmail)
|
|
require.Equal(t, parent.ID, items[0].WorksmobileParentExternalKey)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceReconcilesTopLevelWorksOnlyOrgUnitBeforeProtectedDeleteGuard(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
orgID := "baron-operations"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
samanID := "saman-tenant"
|
|
saman := domain.Tenant{
|
|
ID: samanID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: orgID,
|
|
Slug: "operations",
|
|
Name: "업무",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &samanID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
client := &fakeWorksmobileDirectoryClient{
|
|
groups: []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-operations",
|
|
ExternalID: "legacy-operations-id",
|
|
DisplayName: "업무팀",
|
|
Email: "operations@samaneng.com",
|
|
MailLocalPart: "operations",
|
|
DomainID: 1001,
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, samanID: saman, orgID: organization},
|
|
list: []domain.Tenant{root, saman, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
client,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitDelete(context.Background(), rootID, "works-operations")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileActionDelete, outboxRepo.created[0].Action)
|
|
require.Equal(t, "works-operations", outboxRepo.created[0].ResourceID)
|
|
require.Equal(t, "works-operations", outboxRepo.created[0].Payload["worksmobileId"])
|
|
require.Equal(t, "legacy-operations-id", outboxRepo.created[0].Payload["externalKey"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceRejectsProtectedDomainRootOrgUnitDelete(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
client := &fakeWorksmobileDirectoryClient{
|
|
groups: []WorksmobileRemoteGroup{
|
|
{
|
|
ID: "works-root",
|
|
DisplayName: "한맥기술",
|
|
},
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}, list: []domain.Tenant{root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
client,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitDelete(context.Background(), rootID, "works-root")
|
|
|
|
require.Nil(t, item)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "protected worksmobile domain root")
|
|
require.Empty(t, outboxRepo.created)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceDeletesPendingJobsForTenantRoot(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{deletedPendingCount: 2}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root}, list: []domain.Tenant{root}},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
result, err := service.DeletePendingJobs(context.Background(), rootID)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, result.DeletedCount)
|
|
require.Equal(t, rootID, outboxRepo.deletedPendingTenantRootID)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceTreatsHanmacFamilyChildCompaniesAsDomainRoots(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("HANMAC_DOMAIN_ID", "1002")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
t.Setenv("HALLA_DOMAIN_ID", "1005")
|
|
t.Setenv("WORKS_DEFAULT_DOMAIN_HALLA", "hallasanup.com")
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "root-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
company domain.Tenant
|
|
organization domain.Tenant
|
|
wantDomainID int64
|
|
wantEmail string
|
|
}{
|
|
{
|
|
name: "saman",
|
|
company: domain.Tenant{
|
|
ID: "company-saman",
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-saman-planning",
|
|
Slug: "saman-planning",
|
|
Name: "삼안 기획팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1001,
|
|
wantEmail: "saman-planning@samaneng.com",
|
|
},
|
|
{
|
|
name: "hanmac",
|
|
company: domain.Tenant{
|
|
ID: "company-hanmac",
|
|
Slug: "hanmac",
|
|
Name: "한맥기술",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "hanmaceng.co.kr"}},
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-hanmac-planning",
|
|
Slug: "hanmac-planning",
|
|
Name: "한맥 기획팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1002,
|
|
wantEmail: "hanmac-planning@hanmaceng.co.kr",
|
|
},
|
|
{
|
|
name: "gpdtdc",
|
|
company: domain.Tenant{
|
|
ID: "company-gpdtdc",
|
|
Slug: "gpdtdc",
|
|
Name: "총괄기획&기술개발센터",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-gpdtdc-planning",
|
|
Slug: "gpdtdc-planning",
|
|
Name: "총괄 기획팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1003,
|
|
wantEmail: "gpdtdc-planning@baroncs.co.kr",
|
|
},
|
|
{
|
|
name: "baron-group",
|
|
company: domain.Tenant{
|
|
ID: "company-barongroup",
|
|
Slug: "baron-group",
|
|
Name: "바론그룹",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-baron-planning",
|
|
Slug: "baron-planning",
|
|
Name: "바론 기획팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1004,
|
|
wantEmail: "baron-planning@brsw.kr",
|
|
},
|
|
{
|
|
name: "halla",
|
|
company: domain.Tenant{
|
|
ID: "company-halla",
|
|
Slug: "halla",
|
|
Name: "한라산업개발",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "hallasanup.com"}},
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-halla-planning",
|
|
Slug: "halla-planning",
|
|
Name: "한라 기획팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1005,
|
|
wantEmail: "halla-planning@hallasanup.com",
|
|
},
|
|
{
|
|
name: "hanlla legacy slug",
|
|
company: domain.Tenant{
|
|
ID: "company-hanlla",
|
|
Slug: "hanlla",
|
|
Name: "한라산업개발",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &rootID,
|
|
},
|
|
organization: domain.Tenant{
|
|
ID: "org-hanlla-construction-sites",
|
|
Slug: "hanlla-construction-sites",
|
|
Name: "시공현장",
|
|
Type: domain.TenantTypeOrganization,
|
|
},
|
|
wantDomainID: 1005,
|
|
wantEmail: "hanlla-construction-sites@hallasanup.com",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
organization := tt.organization
|
|
organization.ParentID = &tt.company.ID
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{
|
|
rootID: root,
|
|
tt.company.ID: tt.company,
|
|
organization.ID: organization,
|
|
},
|
|
list: []domain.Tenant{root, tt.company, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, organization.ID)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.Equal(t, organization.ID, request.OrgUnitExternalKey)
|
|
require.Equal(t, tt.wantDomainID, request.DomainID)
|
|
require.Equal(t, tt.wantEmail, request.Email)
|
|
require.Empty(t, request.ParentOrgUnitID)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceUsesBaronGroupDomainForBaronGroupChildOrganization(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
|
baronGroupID := "96369f12-6b66-4b2a-a916-d1c99d326f02"
|
|
orgID := "32464fd6-da51-473f-844a-ab88603ad1f0"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
Type: domain.TenantTypeCompanyGroup,
|
|
}
|
|
baronGroup := domain.Tenant{
|
|
ID: baronGroupID,
|
|
Slug: "baron-group",
|
|
Name: "바론그룹",
|
|
Type: domain.TenantTypeCompanyGroup,
|
|
ParentID: &rootID,
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: orgID,
|
|
Slug: "rnd-baron",
|
|
Name: "바론기술개발센터(조직도용)",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &baronGroupID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{
|
|
root.ID: root,
|
|
baronGroup.ID: baronGroup,
|
|
organization.ID: organization,
|
|
},
|
|
list: []domain.Tenant{root, baronGroup, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, organization.ID)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.Equal(t, int64(1004), request.DomainID)
|
|
require.Equal(t, "rnd-baron@brsw.kr", request.Email)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceUsesGPDTDCDomainForGPDTDCChildOrganization(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
|
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
|
gpdtdcID := "5530ca6e-c5e6-4bf0-84d6-76c6a8fb70ee"
|
|
orgID := "gpdtdc-child-organization"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
Type: domain.TenantTypeCompanyGroup,
|
|
}
|
|
gpdtdc := domain.Tenant{
|
|
ID: gpdtdcID,
|
|
Slug: "gpdtdc",
|
|
Name: "총괄기획&기술개발센터",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &rootID,
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: orgID,
|
|
Slug: "planning",
|
|
Name: "기획",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &gpdtdcID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{
|
|
root.ID: root,
|
|
gpdtdc.ID: gpdtdc,
|
|
organization.ID: organization,
|
|
},
|
|
list: []domain.Tenant{root, gpdtdc, organization},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, organization.ID)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
|
require.Equal(t, int64(1003), request.DomainID)
|
|
require.Equal(t, "planning@baroncs.co.kr", request.Email)
|
|
}
|
|
|
|
func TestWorksmobileDomainClassificationUsesAncestorCompanyForGPDTDCOrganization(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
organizationID := "organization-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Slug: "gpdtdc",
|
|
Name: "총괄기획&기술개발센터",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Domains: []domain.TenantDomain{{Domain: "baroncs.co.kr"}},
|
|
}
|
|
organization := domain.Tenant{
|
|
ID: organizationID,
|
|
Slug: "gpd",
|
|
Name: "총괄기획실",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &companyID,
|
|
}
|
|
tenantByID := worksmobileTenantByID([]domain.Tenant{root, company, organization})
|
|
|
|
domainTenant := worksmobileDomainClassificationTenant(organization, tenantByID)
|
|
payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(organization, domainTenant, nil, 1)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, companyID, domainTenant.ID)
|
|
require.Equal(t, int64(1003), payload.DomainID)
|
|
require.Equal(t, "gpd@baroncs.co.kr", payload.Email)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceKeepsCompanyUsersInComparisonScope(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
userGroupID := "user-group-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Name: "계열사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
userGroup := domain.Tenant{
|
|
ID: userGroupID,
|
|
Name: "연동 조직",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &companyID,
|
|
}
|
|
userRepo := &fakeWorksmobileUserRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company, userGroupID: userGroup}, list: []domain.Tenant{root, company, userGroup}},
|
|
userRepo,
|
|
&fakeWorksmobileOutboxRepo{},
|
|
&fakeWorksmobileDirectoryClient{},
|
|
)
|
|
|
|
_, err := service.GetComparison(context.Background(), rootID, true)
|
|
|
|
require.NoError(t, err)
|
|
require.ElementsMatch(t, []string{companyID, userGroupID}, userRepo.requestedTenantIDs)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceGetComparisonUsesIdentityMirrorWhenReady(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Name: "계열사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
userRepo := &fakeWorksmobileUserRepo{byTenant: []domain.User{{
|
|
ID: "local-only-user",
|
|
Email: "local-only@example.com",
|
|
Name: "Local Only",
|
|
TenantID: &companyID,
|
|
Status: domain.UserStatusActive,
|
|
}}}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company}, list: []domain.Tenant{root, company}},
|
|
userRepo,
|
|
&fakeWorksmobileOutboxRepo{},
|
|
&fakeWorksmobileDirectoryClient{users: []WorksmobileRemoteUser{{
|
|
ID: "works-user",
|
|
ExternalID: "mirror-user",
|
|
Email: "mirror@example.com",
|
|
DisplayName: "Mirror User",
|
|
PrimaryOrgUnitID: "externalKey:" + companyID,
|
|
}}},
|
|
)
|
|
service.SetIdentityMirror(&fakeWorksmobileIdentityMirror{
|
|
status: domain.IdentityCacheStatus{
|
|
Status: "ready",
|
|
RedisReady: true,
|
|
MirrorVersion: "kratos-full-pagination-v1",
|
|
ObservedCount: 1,
|
|
},
|
|
identities: []KratosIdentity{{
|
|
ID: "mirror-user",
|
|
State: "active",
|
|
Traits: map[string]any{
|
|
"email": "mirror@example.com",
|
|
"name": "Mirror User",
|
|
"tenant_id": companyID,
|
|
"role": domain.RoleUser,
|
|
"affiliationType": "internal",
|
|
},
|
|
}},
|
|
})
|
|
|
|
comparison, err := service.GetComparison(context.Background(), rootID, true)
|
|
|
|
require.NoError(t, err)
|
|
require.Empty(t, userRepo.requestedTenantIDs)
|
|
require.Len(t, comparison.Users, 1)
|
|
require.Equal(t, "matched", comparison.Users[0].Status)
|
|
require.Equal(t, "mirror-user", comparison.Users[0].BaronID)
|
|
require.Equal(t, "mirror-user", comparison.Users[0].ExternalKey)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceSkipsArchivedUsersInComparison(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Name: "계열사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
archived := domain.User{
|
|
ID: "archived-user",
|
|
Email: "archived@samaneng.com",
|
|
Name: "Archived",
|
|
TenantID: &companyID,
|
|
Status: domain.UserStatusArchived,
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company}, list: []domain.Tenant{root, company}},
|
|
&fakeWorksmobileUserRepo{byTenant: []domain.User{archived}},
|
|
&fakeWorksmobileOutboxRepo{},
|
|
&fakeWorksmobileDirectoryClient{users: []WorksmobileRemoteUser{{
|
|
ID: "works-archived",
|
|
ExternalID: archived.ID,
|
|
Email: archived.Email,
|
|
}}},
|
|
)
|
|
|
|
comparison, err := service.GetComparison(context.Background(), rootID, true)
|
|
|
|
require.NoError(t, err)
|
|
require.Empty(t, comparison.Users)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceSkipsSoftDeletedUsersInComparison(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Name: "계열사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
deleted := domain.User{
|
|
ID: "deleted-user",
|
|
Email: "deleted@samaneng.com",
|
|
Name: "Deleted",
|
|
TenantID: &companyID,
|
|
Status: domain.UserStatusActive,
|
|
DeletedAt: gorm.DeletedAt{
|
|
Time: time.Now(),
|
|
Valid: true,
|
|
},
|
|
}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company}, list: []domain.Tenant{root, company}},
|
|
&fakeWorksmobileUserRepo{byTenant: []domain.User{deleted}},
|
|
&fakeWorksmobileOutboxRepo{},
|
|
&fakeWorksmobileDirectoryClient{users: []WorksmobileRemoteUser{{
|
|
ID: "works-deleted",
|
|
ExternalID: deleted.ID,
|
|
Email: deleted.Email,
|
|
}}},
|
|
)
|
|
|
|
comparison, err := service.GetComparison(context.Background(), rootID, true)
|
|
|
|
require.NoError(t, err)
|
|
require.Empty(t, comparison.Users)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceBackfillDryRunSkipsArchivedUsers(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
companyID := "company-tenant"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
company := domain.Tenant{
|
|
ID: companyID,
|
|
Name: "계열사",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
active := domain.User{
|
|
ID: "active-user",
|
|
Email: "active@samaneng.com",
|
|
Name: "Active",
|
|
TenantID: &companyID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
archived := domain.User{
|
|
ID: "archived-user",
|
|
Email: "archived@samaneng.com",
|
|
Name: "Archived",
|
|
TenantID: &companyID,
|
|
Status: domain.UserStatusArchived,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{tenants: map[string]domain.Tenant{rootID: root, companyID: company}, list: []domain.Tenant{root, company}},
|
|
&fakeWorksmobileUserRepo{byTenant: []domain.User{active, archived}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
dryRun, err := service.EnqueueBackfillDryRun(context.Background(), rootID)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, dryRun.UserCount)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, 1, outboxRepo.created[0].Payload["userCount"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceBackfillDryRunSkipsWorksmobileExcludedSubtree(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
excludedCompanyID := "excluded-company"
|
|
excludedOrgID := "excluded-org"
|
|
includedCompanyID := "included-company"
|
|
includedOrgID := "included-org"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
excludedCompany := domain.Tenant{
|
|
ID: excludedCompanyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Config: domain.JSONMap{"worksmobileExcluded": true},
|
|
}
|
|
excludedOrg := domain.Tenant{
|
|
ID: excludedOrgID,
|
|
Slug: "excluded-team",
|
|
Name: "제외팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &excludedCompanyID,
|
|
}
|
|
includedCompany := domain.Tenant{
|
|
ID: includedCompanyID,
|
|
Slug: "halla",
|
|
Name: "한라산업개발",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
}
|
|
includedOrg := domain.Tenant{
|
|
ID: includedOrgID,
|
|
Slug: "included-team",
|
|
Name: "연동팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &includedCompanyID,
|
|
}
|
|
excludedUser := domain.User{
|
|
ID: "excluded-user",
|
|
Email: "excluded@samaneng.com",
|
|
Name: "Excluded User",
|
|
TenantID: &excludedOrgID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
includedUser := domain.User{
|
|
ID: "included-user",
|
|
Email: "included@hallasanup.com",
|
|
Name: "Included User",
|
|
TenantID: &includedOrgID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{
|
|
rootID: root,
|
|
excludedCompanyID: excludedCompany,
|
|
excludedOrgID: excludedOrg,
|
|
includedCompanyID: includedCompany,
|
|
includedOrgID: includedOrg,
|
|
},
|
|
list: []domain.Tenant{root, excludedCompany, excludedOrg, includedCompany, includedOrg},
|
|
},
|
|
&fakeWorksmobileUserRepo{byTenant: []domain.User{excludedUser, includedUser}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
dryRun, err := service.EnqueueBackfillDryRun(context.Background(), rootID)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, dryRun.OrgUnitCount)
|
|
require.Equal(t, 1, dryRun.UserCount)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.ElementsMatch(t, []string{includedOrgID}, outboxRepo.created[0].Payload["tenantIds"])
|
|
require.Equal(t, 1, outboxRepo.created[0].Payload["userCount"])
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceRejectsExcludedOrgUnitSync(t *testing.T) {
|
|
rootID := "root-tenant"
|
|
excludedCompanyID := "excluded-company"
|
|
excludedOrgID := "excluded-org"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
excludedCompany := domain.Tenant{
|
|
ID: excludedCompanyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Config: domain.JSONMap{"worksmobileExcluded": true},
|
|
}
|
|
excludedOrg := domain.Tenant{
|
|
ID: excludedOrgID,
|
|
Slug: "excluded-team",
|
|
Name: "제외팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &excludedCompanyID,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, excludedCompanyID: excludedCompany, excludedOrgID: excludedOrg},
|
|
list: []domain.Tenant{root, excludedCompany, excludedOrg},
|
|
},
|
|
&fakeWorksmobileUserRepo{},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
item, err := service.EnqueueOrgUnitSync(context.Background(), rootID, excludedOrgID)
|
|
|
|
require.Nil(t, item)
|
|
require.ErrorContains(t, err, "excluded from Worksmobile sync")
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileResourceOrgUnit, outboxRepo.created[0].ResourceType)
|
|
require.Equal(t, domain.WorksmobileOutboxStatusFailed, outboxRepo.created[0].Status)
|
|
require.Equal(t, excludedOrgID, outboxRepo.created[0].ResourceID)
|
|
require.Equal(t, rootID, outboxRepo.created[0].Payload["tenantRootId"])
|
|
require.Equal(t, "excluded-team", outboxRepo.created[0].Payload["matchLocalPart"])
|
|
require.Equal(t, "target tenant is excluded from Worksmobile sync", outboxRepo.created[0].LastError)
|
|
}
|
|
|
|
func TestWorksmobileSyncServiceSkipsExcludedTenantAndUserEventSync(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "root-tenant"
|
|
excludedCompanyID := "excluded-company"
|
|
excludedOrgID := "excluded-org"
|
|
root := domain.Tenant{
|
|
ID: rootID,
|
|
Slug: HanmacFamilyTenantSlug,
|
|
Name: "한맥가족",
|
|
}
|
|
excludedCompany := domain.Tenant{
|
|
ID: excludedCompanyID,
|
|
Slug: "saman",
|
|
Name: "삼안",
|
|
Type: domain.TenantTypeCompany,
|
|
ParentID: &rootID,
|
|
Config: domain.JSONMap{"worksmobileExcluded": true},
|
|
}
|
|
excludedOrg := domain.Tenant{
|
|
ID: excludedOrgID,
|
|
Slug: "excluded-team",
|
|
Name: "제외팀",
|
|
Type: domain.TenantTypeOrganization,
|
|
ParentID: &excludedCompanyID,
|
|
}
|
|
user := domain.User{
|
|
ID: "excluded-user",
|
|
Email: "excluded@samaneng.com",
|
|
Name: "Excluded User",
|
|
TenantID: &excludedOrgID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
outboxRepo := &fakeWorksmobileOutboxRepo{}
|
|
service := NewWorksmobileSyncService(
|
|
&fakeWorksmobileTenantService{
|
|
tenants: map[string]domain.Tenant{rootID: root, excludedCompanyID: excludedCompany, excludedOrgID: excludedOrg},
|
|
list: []domain.Tenant{root, excludedCompany, excludedOrg},
|
|
},
|
|
&fakeWorksmobileUserRepo{byID: map[string]domain.User{user.ID: user}},
|
|
outboxRepo,
|
|
nil,
|
|
)
|
|
|
|
require.NoError(t, service.EnqueueTenantUpsertIfInScope(context.Background(), excludedOrg))
|
|
require.NoError(t, service.EnqueueTenantDeleteIfInScope(context.Background(), excludedOrg))
|
|
require.NoError(t, service.EnqueueUserUpsertIfInScope(context.Background(), user))
|
|
item, err := service.EnqueueUserSync(context.Background(), rootID, user.ID, "", "")
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, item)
|
|
require.Len(t, outboxRepo.created, 1)
|
|
require.Equal(t, domain.WorksmobileResourceUser, outboxRepo.created[0].ResourceType)
|
|
require.Equal(t, user.ID, outboxRepo.created[0].ResourceID)
|
|
require.Equal(t, domain.WorksmobileActionUpsert, outboxRepo.created[0].Action)
|
|
require.Empty(t, outboxRepo.created[0].Status)
|
|
require.Empty(t, outboxRepo.created[0].LastError)
|
|
require.Equal(t, rootID, outboxRepo.created[0].Payload["tenantRootId"])
|
|
require.Equal(t, user.Email, outboxRepo.created[0].Payload["loginEmail"])
|
|
require.Equal(t, user.Name, outboxRepo.created[0].Payload["displayName"])
|
|
require.Empty(t, outboxRepo.created[0].Payload["primaryLeafOrgName"])
|
|
request, ok := outboxRepo.created[0].Payload["request"].(WorksmobileUserPayload)
|
|
require.True(t, ok)
|
|
require.Empty(t, request.Organizations)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksManagerChangeNeedsUpdate(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-saman"
|
|
tenantID := "tenant-leaf"
|
|
user := domain.User{
|
|
ID: "user-manager",
|
|
Email: "manager@samaneng.com",
|
|
Name: "Manager User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": tenantID,
|
|
"isPrimary": true,
|
|
"isManager": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
remoteManager := false
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-manager",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
PrimaryOrgUnitID: "externalKey:" + tenantID,
|
|
PrimaryOrgUnitIsManager: &remoteManager,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + tenantID,
|
|
Primary: true,
|
|
IsManager: &remoteManager,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &rootID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksTenantLinkedGradeChangeNeedsUpdate(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
tenantID := "tenant-gpdtdc-leaf"
|
|
user := domain.User{
|
|
ID: "user-grade",
|
|
Email: "grade@samaneng.com",
|
|
Name: "Grade User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": tenantID,
|
|
"isPrimary": true,
|
|
"grade": "책임",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-grade",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
LevelName: "",
|
|
PrimaryOrgUnitID: "externalKey:" + tenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + tenantID, Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
tenantID: {ID: tenantID, Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
require.Contains(t, items[0].UpdateReasons, "grade")
|
|
require.Equal(t, "책임 연구원", items[0].BaronGrade)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersIncludesMembershipMatchForGradeUpdate(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
hmegID := "1d74bebb-c5a1-49d4-bec4-90f0c89ad21f"
|
|
user := domain.User{
|
|
ID: "user-hmeg-researcher",
|
|
Email: "hmeg-researcher@baroncs.co.kr",
|
|
Name: "HMEG Researcher",
|
|
TenantID: &hmegID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": hmegID,
|
|
"isPrimary": true,
|
|
"grade": "책임연구원",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsersWithRemoteGroups(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-hmeg-researcher",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1003,
|
|
PrimaryOrgUnitID: "works-hmeg",
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "works-hmeg", Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
hmegID: {ID: hmegID, Slug: "hmeg", Name: "HmEG", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
[]WorksmobileRemoteGroup{{
|
|
ID: "works-hmeg",
|
|
ExternalID: hmegID,
|
|
DisplayName: "WORKS HmEG",
|
|
DomainID: 1003,
|
|
DomainName: "baroncs.co.kr",
|
|
}},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
require.Contains(t, items[0].UpdateReasons, "grade")
|
|
require.Len(t, items[0].UserMemberships, 1)
|
|
require.Equal(t, hmegID, items[0].UserMemberships[0].BaronOrgID)
|
|
require.Equal(t, "HmEG", items[0].UserMemberships[0].BaronOrgName)
|
|
require.Equal(t, "hmeg", items[0].UserMemberships[0].BaronOrgSlug)
|
|
require.Equal(t, "책임 연구원", items[0].UserMemberships[0].BaronGrade)
|
|
require.Equal(t, "works-hmeg", items[0].UserMemberships[0].WorksmobileOrgID)
|
|
require.Equal(t, "WORKS HmEG", items[0].UserMemberships[0].WorksmobileOrgName)
|
|
require.True(t, items[0].UserMemberships[0].GradeNeedsUpdate)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersDoesNotUpdateWhenWORKSMembershipIsBaronSubset(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
tenantID := "tenant-saman-leaf"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
gpdtdcTenantID := "tenant-is-3"
|
|
user := domain.User{
|
|
ID: "user-gpdtdc-grade-with-saman-works-org",
|
|
Email: "gpdtdc-grade@samaneng.com",
|
|
Name: "Research Grade User",
|
|
TenantID: &gpdtdcTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": tenantID,
|
|
"isPrimary": true,
|
|
"grade": "팀장",
|
|
},
|
|
map[string]any{
|
|
"tenantId": gpdtdcTenantID,
|
|
"grade": "책임",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-gpdtdc-grade",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
LevelName: "팀장",
|
|
PrimaryOrgUnitID: "externalKey:" + tenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + tenantID, Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Slug: "saman-leaf", Name: "삼안 조직", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
gpdtdcTenantID: {ID: gpdtdcTenantID, Slug: "is-3", Name: "기술기획", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.NotContains(t, items[0].UpdateReasons, "grade")
|
|
require.NotContains(t, items[0].UpdateReasons, "organization")
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersDoesNotCompareGPDTDCGradeAgainstSamanOrgChartLevel(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "300285955")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
samanID := "045e0b22-fae7-4229-1724-039c5af16849"
|
|
samanOrgChartID := "97a7e34d-2042-4793-27dc-03ffd68db801"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
infraBIM1ID := "432b5261-421b-4e5f-914f-32d7d22fd01f"
|
|
user := domain.User{
|
|
ID: "abaf0788-2d68-4b7d-b40a-c0251f38ae21",
|
|
Email: "hwan@samaneng.com",
|
|
Name: "안효원",
|
|
TenantID: &infraBIM1ID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": samanOrgChartID,
|
|
"grade": "선임연구원",
|
|
},
|
|
map[string]any{
|
|
"tenantId": infraBIM1ID,
|
|
"isPrimary": true,
|
|
"grade": "선임연구원",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
remoteManager := false
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "045e0b22-fae7-4229-1724-039c5af16849",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 300285955,
|
|
PrimaryOrgUnitID: "externalKey:" + samanOrgChartID,
|
|
PrimaryOrgUnitName: "삼안기술개발센터(조직도용)",
|
|
PrimaryOrgUnitIsManager: &remoteManager,
|
|
PrimaryOrgUnitPositionName: "조직장 아님",
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 300285955,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{
|
|
OrgUnitID: "externalKey:" + samanOrgChartID,
|
|
Primary: true,
|
|
IsManager: &remoteManager,
|
|
}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
samanID: {ID: samanID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
samanOrgChartID: {ID: samanOrgChartID, Slug: "rnd-saman", Name: "삼안기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &samanID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
infraBIM1ID: {ID: infraBIM1ID, Slug: "infra-bim1", Name: "인프라 BIM1", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.NotContains(t, items[0].UpdateReasons, "grade")
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersUsesPrimaryAppointmentForGPDTDCGrade(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
orgChartTenantID := "tenant-rnd-saman"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
gpdtdcTenantID := "tenant-is-3"
|
|
user := domain.User{
|
|
ID: "user-orgchart-grade",
|
|
Email: "orgchart-grade@samaneng.com",
|
|
Name: "Orgchart Grade User",
|
|
TenantID: &gpdtdcTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": orgChartTenantID,
|
|
},
|
|
map[string]any{
|
|
"tenantId": gpdtdcTenantID,
|
|
"isPrimary": true,
|
|
"grade": "수석연구원",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-orgchart-grade",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
LevelName: "수석 연구원",
|
|
PrimaryOrgUnitID: "externalKey:" + gpdtdcTenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: false,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + orgChartTenantID, Primary: true}},
|
|
},
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + gpdtdcTenantID, Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
orgChartTenantID: {ID: orgChartTenantID, Slug: "rnd-saman", Name: "삼안기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
gpdtdcTenantID: {ID: gpdtdcTenantID, Slug: "is-3", Name: "기술기획", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.NotContains(t, items[0].UpdateReasons, "grade")
|
|
require.Equal(t, "수석 연구원", items[0].BaronGrade)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksConcurrentTenantGradeChangeNeedsUpdate(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
primaryTenantID := "tenant-saman-leaf"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
gpdtdcTenantID := "tenant-is-3"
|
|
user := domain.User{
|
|
ID: "user-concurrent-grade",
|
|
Email: "concurrent-grade@samaneng.com",
|
|
Name: "Concurrent Grade User",
|
|
TenantID: &primaryTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": primaryTenantID,
|
|
"isPrimary": true,
|
|
"grade": "팀장",
|
|
},
|
|
map[string]any{
|
|
"tenantId": gpdtdcTenantID,
|
|
"grade": "책임",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-concurrent-grade",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
LevelName: "팀장",
|
|
PrimaryOrgUnitID: "externalKey:" + primaryTenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
LevelName: "팀장",
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + primaryTenantID, Primary: true}},
|
|
},
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: false,
|
|
LevelName: "선임",
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + gpdtdcTenantID, Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
primaryTenantID: {ID: primaryTenantID, Slug: "saman-leaf", Name: "삼안 조직", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
gpdtdcTenantID: {ID: gpdtdcTenantID, Slug: "is-3", Name: "기술기획", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
require.Contains(t, items[0].UpdateReasons, "grade")
|
|
require.NotContains(t, items[0].UpdateReasons, "organization")
|
|
require.Equal(t, "책임 연구원", items[0].BaronGrade)
|
|
require.Equal(t, "선임", items[0].WorksmobileLevelName)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksSecondaryManagerChangeNeedsUpdate(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-saman"
|
|
primaryTenantID := "tenant-primary"
|
|
secondaryTenantID := "tenant-secondary"
|
|
user := domain.User{
|
|
ID: "user-secondary-manager",
|
|
Email: "secondary-manager@samaneng.com",
|
|
Name: "Secondary Manager User",
|
|
TenantID: &secondaryTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": primaryTenantID,
|
|
"isPrimary": true,
|
|
},
|
|
map[string]any{
|
|
"tenantId": secondaryTenantID,
|
|
"isPrimary": false,
|
|
"isManager": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
remotePrimaryManager := false
|
|
remoteSecondaryManager := false
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-secondary-manager",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
PrimaryOrgUnitID: "externalKey:" + primaryTenantID,
|
|
PrimaryOrgUnitIsManager: &remotePrimaryManager,
|
|
OrgUnitManagers: map[string]*bool{
|
|
"externalKey:" + primaryTenantID: &remotePrimaryManager,
|
|
"externalKey:" + secondaryTenantID: &remoteSecondaryManager,
|
|
},
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + primaryTenantID,
|
|
Primary: true,
|
|
IsManager: &remotePrimaryManager,
|
|
},
|
|
{
|
|
OrgUnitID: "externalKey:" + secondaryTenantID,
|
|
Primary: false,
|
|
IsManager: &remoteSecondaryManager,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
primaryTenantID: {ID: primaryTenantID, Name: "Primary", Type: domain.TenantTypeOrganization, ParentID: &rootID},
|
|
secondaryTenantID: {ID: secondaryTenantID, Name: "Secondary", Type: domain.TenantTypeOrganization, ParentID: &rootID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksMissingSecondaryOrganizationNeedsUpdate(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
primaryTenantID := "tenant-saman"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
secondaryTenantID := "tenant-gpdtdc-leaf"
|
|
user := domain.User{
|
|
ID: "user-secondary-org",
|
|
Email: "secondary-org@samaneng.com",
|
|
Name: "Secondary Org User",
|
|
TenantID: &secondaryTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": primaryTenantID,
|
|
"isPrimary": true,
|
|
},
|
|
map[string]any{
|
|
"tenantId": secondaryTenantID,
|
|
"isPrimary": false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
remotePrimaryManager := false
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-secondary-org",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + primaryTenantID,
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
PrimaryOrgUnitID: "externalKey:" + primaryTenantID,
|
|
PrimaryOrgUnitIsManager: &remotePrimaryManager,
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
primaryTenantID: {ID: primaryTenantID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
secondaryTenantID: {ID: secondaryTenantID, Slug: "people-growth", Name: "인재성장", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersIgnoresPrimaryPriorityWhenMembershipsMatch(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
orgChartTenantID := "tenant-rnd-saman"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
gpdtdcTenantID := "tenant-gpdtdc-leaf"
|
|
user := domain.User{
|
|
ID: "user-dual-membership",
|
|
Email: "dual-membership@samaneng.com",
|
|
Name: "Dual Membership User",
|
|
TenantID: &gpdtdcTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": orgChartTenantID,
|
|
"isPrimary": false,
|
|
},
|
|
map[string]any{
|
|
"tenantId": gpdtdcTenantID,
|
|
"isPrimary": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-dual-membership",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
PrimaryOrgUnitID: "externalKey:" + orgChartTenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + orgChartTenantID, Primary: true}},
|
|
},
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: false,
|
|
OrgUnits: []WorksmobileUserOrgUnit{{OrgUnitID: "externalKey:" + gpdtdcTenantID, Primary: true}},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
orgChartTenantID: {ID: orgChartTenantID, Slug: "rnd-saman", Name: "삼안기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompany, ParentID: &rootID},
|
|
gpdtdcTenantID: {ID: gpdtdcTenantID, Slug: "people-growth", Name: "인재성장", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.NotContains(t, items[0].UpdateReasons, "organization")
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersIgnoresBaronMembershipSuperset(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
primaryTenantID := "tenant-primary"
|
|
secondaryTenantID := "tenant-secondary"
|
|
user := domain.User{
|
|
ID: "user-baron-membership-superset",
|
|
Email: "membership-superset@samaneng.com",
|
|
Name: "Membership Superset User",
|
|
TenantID: &primaryTenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": primaryTenantID,
|
|
"isPrimary": true,
|
|
},
|
|
map[string]any{
|
|
"tenantId": secondaryTenantID,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-membership-superset",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1001,
|
|
PrimaryOrgUnitID: "externalKey:" + primaryTenantID,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + primaryTenantID,
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
primaryTenantID: {ID: primaryTenantID, Slug: "primary", Name: "Primary", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
secondaryTenantID: {ID: secondaryTenantID, Slug: "secondary", Name: "Secondary", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
require.NotContains(t, items[0].UpdateReasons, "organization")
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersIgnoresOrganizationEmailWhenMembershipMatches(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
tenantID := "tenant-leaf"
|
|
user := domain.User{
|
|
ID: "user-org-email",
|
|
Email: "org-email@samaneng.com",
|
|
Name: "Org Email User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{"tenantId": tenantID},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-org-email",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + tenantID,
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Slug: "leaf", Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestWorksmobileUsersFromIdentityMirrorIncludesAdditionalAppointmentMembership(t *testing.T) {
|
|
tenantID := "tenant-gpdtdc-leaf"
|
|
identity := KratosIdentity{
|
|
ID: "64d4a839-ee04-4c47-b7b3-4ac6428c56b1",
|
|
Traits: map[string]any{
|
|
"email": "researcher@samaneng.com",
|
|
"name": "Researcher User",
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": tenantID,
|
|
"isPrimary": true,
|
|
"grade": "책임",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
users := worksmobileUsersFromIdentityMirror([]KratosIdentity{identity}, []string{tenantID})
|
|
|
|
require.Len(t, users, 1)
|
|
require.Equal(t, identity.ID, users[0].ID)
|
|
require.NotNil(t, users[0].TenantID)
|
|
require.Equal(t, tenantID, *users[0].TenantID)
|
|
require.Equal(t, "책임", worksmobileUserGrade(users[0]))
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersUsesRemoteUserDomainWhenOrganizationDomainIsMissing(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
tenantID := "tenant-leaf"
|
|
user := domain.User{
|
|
ID: "user-org-domain",
|
|
Email: "org-domain@samaneng.com",
|
|
Name: "Org Domain User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{"tenantId": tenantID},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-org-domain",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1001,
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + tenantID,
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Slug: "leaf", Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersUsesRemotePrimaryOrgUnitWhenOrganizationPrimaryFlagsAreMissing(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
tenantID := "tenant-leaf"
|
|
user := domain.User{
|
|
ID: "user-org-primary",
|
|
Email: "org-primary@samaneng.com",
|
|
Name: "Org Primary User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{"tenantId": tenantID},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsersWithRemoteGroups(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-org-primary",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1001,
|
|
PrimaryOrgUnitID: "works-leaf",
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "works-leaf",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Slug: "leaf", Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
},
|
|
[]WorksmobileRemoteGroup{{
|
|
ID: "works-leaf",
|
|
ExternalID: tenantID,
|
|
}},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMatchesRemoteOrganizationExternalKey(t *testing.T) {
|
|
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
|
rootID := "tenant-root"
|
|
companyID := "tenant-saman"
|
|
tenantID := "tenant-leaf"
|
|
user := domain.User{
|
|
ID: "user-org-external-key",
|
|
Email: "org-external-key@samaneng.com",
|
|
Name: "Org External Key User",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{"tenantId": tenantID},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsersWithRemoteGroups(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-org-external-key",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1001,
|
|
PrimaryOrgUnitID: "works-leaf",
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1001,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:" + tenantID,
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
companyID: {ID: companyID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
|
tenantID: {ID: tenantID, Slug: "leaf", Name: "Leaf", Type: domain.TenantTypeOrganization, ParentID: &companyID},
|
|
},
|
|
[]WorksmobileRemoteGroup{{
|
|
ID: "works-leaf",
|
|
ExternalID: tenantID,
|
|
}},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersDisplaysPrimaryAppointmentAsBaronPrimaryOrg(t *testing.T) {
|
|
t.Setenv("HANMAC_DOMAIN_ID", "1002")
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
hanmacCompanyID := "tenant-hanmac"
|
|
hanmacOrgID := "tenant-hanmac-org"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
gsimID := "tenant-gsim-dev"
|
|
user := domain.User{
|
|
ID: "user-gsim-primary",
|
|
Email: "gsim-primary@hanmaceng.co.kr",
|
|
Name: "GSIM Primary User",
|
|
TenantID: &hanmacOrgID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"additionalAppointments": []any{
|
|
map[string]any{
|
|
"tenantId": hanmacOrgID,
|
|
"isPrimary": false,
|
|
},
|
|
map[string]any{
|
|
"tenantId": gsimID,
|
|
"isPrimary": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
nil,
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
hanmacCompanyID: {ID: hanmacCompanyID, Slug: "hanmac", Name: "한맥기술", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "hanmaceng.co.kr"}}},
|
|
hanmacOrgID: {ID: hanmacOrgID, Slug: "rnd-hanmac", Name: "한맥기술개발센터(조직도용)", Type: domain.TenantTypeOrganization, ParentID: &hanmacCompanyID},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompany, ParentID: &rootID},
|
|
gsimID: {ID: gsimID, Slug: "gsim-dev", Name: "GSIM개발", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
|
require.Equal(t, gsimID, items[0].BaronPrimaryOrgID)
|
|
require.Equal(t, "gsim-dev", items[0].BaronPrimaryOrgSlug)
|
|
require.Equal(t, "GSIM개발", items[0].BaronPrimaryOrgName)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksMissingPrimaryOrganizationNeedsUpdate(t *testing.T) {
|
|
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
|
rootID := "tenant-root"
|
|
gpdtdcID := "tenant-gpdtdc"
|
|
peopleGrowthID := "tenant-people-growth"
|
|
user := domain.User{
|
|
ID: "user-people-growth",
|
|
Email: "people-growth@baroncs.co.kr",
|
|
Name: "People Growth User",
|
|
TenantID: &peopleGrowthID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-people-growth",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
DomainID: 1003,
|
|
PrimaryOrgUnitID: "externalKey:another-team",
|
|
Organizations: []WorksmobileUserOrganization{
|
|
{
|
|
DomainID: 1003,
|
|
Email: user.Email,
|
|
Primary: true,
|
|
OrgUnits: []WorksmobileUserOrgUnit{
|
|
{
|
|
OrgUnitID: "externalKey:another-team",
|
|
Primary: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
rootID: {ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
|
gpdtdcID: {ID: gpdtdcID, Slug: "gpdtdc", Name: "총괄기획&기술개발센터", Type: domain.TenantTypeCompanyGroup, ParentID: &rootID},
|
|
peopleGrowthID: {ID: peopleGrowthID, Slug: "people-growth", Name: "인재성장", Type: domain.TenantTypeOrganization, ParentID: &gpdtdcID},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
require.Equal(t, peopleGrowthID, items[0].BaronPrimaryOrgID)
|
|
require.Equal(t, "externalKey:another-team", items[0].WorksmobilePrimaryOrgID)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMatchesPrimaryOrganizationByWorksmobileResourceID(t *testing.T) {
|
|
peopleGrowthID := "tenant-people-growth"
|
|
user := domain.User{
|
|
ID: "user-people-growth",
|
|
Email: "people-growth@baroncs.co.kr",
|
|
Name: "People Growth User",
|
|
TenantID: &peopleGrowthID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
|
|
items := compareWorksmobileUsersWithRemoteGroups(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-people-growth",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
PrimaryOrgUnitID: "works-current-people-growth",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
peopleGrowthID: {ID: peopleGrowthID, Slug: "people-growth", Name: "인재성장", Type: domain.TenantTypeOrganization},
|
|
},
|
|
[]WorksmobileRemoteGroup{{
|
|
ID: "works-current-people-growth",
|
|
ExternalID: peopleGrowthID,
|
|
}},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksStalePrimaryOrganizationResourceIDNeedsUpdate(t *testing.T) {
|
|
peopleGrowthID := "tenant-people-growth"
|
|
user := domain.User{
|
|
ID: "user-people-growth",
|
|
Email: "people-growth@baroncs.co.kr",
|
|
Name: "People Growth User",
|
|
TenantID: &peopleGrowthID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
|
|
items := compareWorksmobileUsersWithRemoteGroups(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-people-growth",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
PrimaryOrgUnitID: "works-deleted-people-growth",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
peopleGrowthID: {ID: peopleGrowthID, Slug: "people-growth", Name: "인재성장", Type: domain.TenantTypeOrganization},
|
|
},
|
|
[]WorksmobileRemoteGroup{{
|
|
ID: "works-current-people-growth",
|
|
ExternalID: peopleGrowthID,
|
|
}},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksPhoneAndEmployeeNumberChangesNeedsUpdate(t *testing.T) {
|
|
tenantID := "tenant-saman"
|
|
user := domain.User{
|
|
ID: "user-phone-employee",
|
|
Email: "phone-employee@samaneng.com",
|
|
Name: "Phone Employee User",
|
|
Phone: "010-1234-5678",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
Metadata: domain.JSONMap{
|
|
"employeeNumber": "EMP001",
|
|
},
|
|
}
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-phone-employee",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
CellPhone: "+821099998888",
|
|
EmployeeNumber: "EMP999",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
tenantID: {ID: tenantID, Name: "삼안", Type: domain.TenantTypeCompany},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
require.Equal(t, user.Phone, items[0].BaronPhone)
|
|
require.Equal(t, "+821099998888", items[0].WorksmobilePhone)
|
|
require.Equal(t, "EMP001", items[0].BaronEmployeeNumber)
|
|
require.Equal(t, "EMP999", items[0].WorksmobileEmployeeNumber)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersMarksMalformedRemoteKoreanPhoneNeedsUpdate(t *testing.T) {
|
|
tenantID := "tenant-saman"
|
|
user := domain.User{
|
|
ID: "user-phone-canonical",
|
|
Email: "phone-canonical@samaneng.com",
|
|
Name: "Phone Canonical User",
|
|
Phone: "+821062836786",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-phone-canonical",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
CellPhone: "+82+821062836786",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
tenantID: {ID: tenantID, Name: "삼안", Type: domain.TenantTypeCompany},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "needs_update", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersIgnoresRemotePhoneWhenBaronPhoneIsEmpty(t *testing.T) {
|
|
tenantID := "tenant-halla"
|
|
user := domain.User{
|
|
ID: "edb8e4f6-3dfd-44d4-a8aa-87332f8b2b38",
|
|
Email: "cyhan4@hallasanup.com",
|
|
Name: "네이버웍스관리자",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "fe9449d1-1671-44e4-1848-033779dddbaf",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
CellPhone: "+82 01041585840",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
tenantID: {ID: tenantID, Name: "한라산업개발", Type: domain.TenantTypeCompany},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
func TestCompareWorksmobileUsersTreatsSpacedKoreanCountryCodePhoneAsMatched(t *testing.T) {
|
|
tenantID := "tenant-saman"
|
|
user := domain.User{
|
|
ID: "user-phone-spaced",
|
|
Email: "phone-spaced@samaneng.com",
|
|
Name: "Phone Spaced User",
|
|
Phone: "+821041585840",
|
|
TenantID: &tenantID,
|
|
Status: domain.UserStatusActive,
|
|
}
|
|
items := compareWorksmobileUsers(
|
|
[]domain.User{user},
|
|
[]WorksmobileRemoteUser{{
|
|
ID: "works-user-phone-spaced",
|
|
ExternalID: user.ID,
|
|
Email: user.Email,
|
|
DisplayName: user.Name,
|
|
CellPhone: "+82 1041585840",
|
|
}},
|
|
true,
|
|
map[string]domain.Tenant{
|
|
tenantID: {ID: tenantID, Name: "삼안", Type: domain.TenantTypeCompany},
|
|
},
|
|
)
|
|
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, "matched", items[0].Status)
|
|
}
|
|
|
|
type fakeWorksmobileTenantService struct {
|
|
tenants map[string]domain.Tenant
|
|
list []domain.Tenant
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) RegisterTenant(ctx context.Context, name, slug, tenantType, description string, domains []string, parentID *string, creatorID string) (*domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) RequestRegistration(ctx context.Context, name, slug, description string, domainName string, adminEmail string) (*domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) GetTenantByDomain(ctx context.Context, emailDomain string) (*domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) GetTenantBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) GetTenant(ctx context.Context, id string) (*domain.Tenant, error) {
|
|
tenant := f.tenants[id]
|
|
return &tenant, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) ListTenants(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) {
|
|
return f.list, int64(len(f.list)), nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) ListManageableTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) ListJoinedTenants(ctx context.Context, userID string) ([]domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) IsDomainAllowed(ctx context.Context, domainName string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) ApproveTenant(ctx context.Context, id string) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) ProvisionTenantByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileTenantService) SetKetoService(keto KetoService) {}
|
|
|
|
func (f *fakeWorksmobileTenantService) DeleteTenantsBulk(ctx context.Context, ids []string) error {
|
|
return nil
|
|
}
|
|
|
|
type fakeWorksmobileUserRepo struct {
|
|
byID map[string]domain.User
|
|
byTenant []domain.User
|
|
requestedTenantIDs []string
|
|
}
|
|
|
|
type fakeWorksmobileIdentityMirror struct {
|
|
status domain.IdentityCacheStatus
|
|
identities []KratosIdentity
|
|
}
|
|
|
|
func (f *fakeWorksmobileIdentityMirror) GetIdentityCacheStatus(ctx context.Context) (domain.IdentityCacheStatus, error) {
|
|
return f.status, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileIdentityMirror) ListIdentityMirrors(ctx context.Context) ([]KratosIdentity, error) {
|
|
return f.identities, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) Create(ctx context.Context, user *domain.User) error { return nil }
|
|
func (f *fakeWorksmobileUserRepo) Update(ctx context.Context, user *domain.User) error { return nil }
|
|
func (f *fakeWorksmobileUserRepo) FindByEmail(ctx context.Context, email string) (*domain.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) FindByID(ctx context.Context, id string) (*domain.User, error) {
|
|
user := f.byID[id]
|
|
return &user, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *fakeWorksmobileUserRepo) List(ctx context.Context, offset, limit int, search string, tenantIDs []string, cursor string) ([]domain.User, int64, string, error) {
|
|
return nil, 0, "", nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) CountByTenant(ctx context.Context, tenantID string) (int64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) CountByTenantIDs(ctx context.Context, tenantIDs []string) (map[string]int64, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) CountByCompanyCodes(ctx context.Context, codes []string) (map[string]int64, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) FindByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.User, error) {
|
|
f.requestedTenantIDs = append([]string(nil), tenantIDs...)
|
|
if len(tenantIDs) == 0 {
|
|
return nil, nil
|
|
}
|
|
allowed := make(map[string]bool, len(tenantIDs))
|
|
for _, tenantID := range tenantIDs {
|
|
allowed[tenantID] = true
|
|
}
|
|
users := make([]domain.User, 0, len(f.byTenant))
|
|
for _, user := range f.byTenant {
|
|
if user.TenantID == nil {
|
|
continue
|
|
}
|
|
if allowed[*user.TenantID] {
|
|
users = append(users, user)
|
|
}
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) FindByCompanyCodes(ctx context.Context, codes []string) ([]domain.User, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeWorksmobileUserRepo) Delete(ctx context.Context, id string) error { return nil }
|
|
func (f *fakeWorksmobileUserRepo) UpdateUserLoginIDs(ctx context.Context, userID string, loginIDs []domain.UserLoginID) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) GetUserLoginIDs(ctx context.Context, userID string) ([]domain.UserLoginID, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) IsLoginIDTaken(ctx context.Context, loginID string) (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) FindTenantIDByLoginID(ctx context.Context, loginID string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (f *fakeWorksmobileUserRepo) DB() *gorm.DB {
|
|
return nil
|
|
}
|