forked from baron/baron-sso
네이버 웍스 연동기능 개선
This commit is contained in:
@@ -751,6 +751,7 @@ func main() {
|
||||
admin.Get("/tenants/:tenantId/worksmobile/initial-passwords.csv", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.DownloadInitialPasswordsCSV)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/backfill/dry-run", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.BackfillDryRun)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/orgunits/:orgUnitId/sync", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.SyncOrgUnit)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/orgunits/:orgUnitId/delete", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.DeleteOrgUnit)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/users/:userId/sync", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.SyncUser)
|
||||
admin.Post("/tenants/:tenantId/worksmobile/jobs/:jobId/retry", requireAdmin, middleware.RequireKetoPermission(middleware.RBACConfig{AuthHandler: authHandler, KetoService: ketoService}, "Tenant", "manage"), worksmobileHandler.RetryJob)
|
||||
|
||||
|
||||
@@ -61,6 +61,15 @@ func (h *WorksmobileHandler) SyncOrgUnit(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusAccepted).JSON(job)
|
||||
}
|
||||
|
||||
func (h *WorksmobileHandler) DeleteOrgUnit(c *fiber.Ctx) error {
|
||||
orgUnitID := strings.TrimSpace(c.Params("orgUnitId"))
|
||||
job, err := h.Service.EnqueueOrgUnitDelete(c.Context(), strings.TrimSpace(c.Params("tenantId")), orgUnitID)
|
||||
if err != nil {
|
||||
return worksmobileGuardError(c, err, "delete_orgunit", "org_unit_id", orgUnitID)
|
||||
}
|
||||
return c.Status(fiber.StatusAccepted).JSON(job)
|
||||
}
|
||||
|
||||
func (h *WorksmobileHandler) SyncUser(c *fiber.Ctx) error {
|
||||
userID := strings.TrimSpace(c.Params("userId"))
|
||||
job, err := h.Service.EnqueueUserSync(c.Context(), strings.TrimSpace(c.Params("tenantId")), userID)
|
||||
|
||||
@@ -112,6 +112,10 @@ func (f *fakeWorksmobileAdminService) EnqueueOrgUnitSync(ctx context.Context, te
|
||||
return &domain.WorksmobileOutbox{ID: "job-orgunit", ResourceID: orgUnitID}, nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileAdminService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error) {
|
||||
return &domain.WorksmobileOutbox{ID: "job-orgunit-delete", ResourceID: orgUnitID, Action: domain.WorksmobileActionDelete}, nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileAdminService) EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error) {
|
||||
if f.syncUserErr != nil {
|
||||
return nil, f.syncUserErr
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
type WorksmobileDirectoryClient interface {
|
||||
CreateOrgUnit(ctx context.Context, payload WorksmobileOrgUnitPayload) error
|
||||
UpsertOrgUnit(ctx context.Context, payload WorksmobileOrgUnitPayload, matchLocalPart string) error
|
||||
DeleteOrgUnit(ctx context.Context, orgUnitID string) error
|
||||
CreateUser(ctx context.Context, payload WorksmobileUserPayload) error
|
||||
UpsertUser(ctx context.Context, payload WorksmobileUserPayload) error
|
||||
DeleteUser(ctx context.Context, userID string) error
|
||||
@@ -186,6 +187,9 @@ func NewWorksmobileHTTPClientWithAuth(directoryToken string, scimToken string, o
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) CreateOrgUnit(ctx context.Context, payload WorksmobileOrgUnitPayload) error {
|
||||
if payload.DisplayOrder < 1 {
|
||||
payload.DisplayOrder = 1
|
||||
}
|
||||
return c.sendDirectoryJSON(ctx, http.MethodPost, "/v1.0/orgunits", payload)
|
||||
}
|
||||
|
||||
@@ -198,11 +202,12 @@ func (c *WorksmobileHTTPClient) UpsertOrgUnit(ctx context.Context, payload Works
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) BackfillOrgUnitExternalKeyByLocalPart(ctx context.Context, payload WorksmobileOrgUnitPayload, matchLocalPart string) error {
|
||||
localPart := worksmobileMailLocalPart(matchLocalPart)
|
||||
groups, err := c.ListGroups(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedMatchLocalPart := worksmobileMailLocalPart(matchLocalPart)
|
||||
var localPartMatch *WorksmobileRemoteGroup
|
||||
for _, group := range groups {
|
||||
if payload.DomainID > 0 && group.DomainID > 0 && payload.DomainID != group.DomainID {
|
||||
continue
|
||||
@@ -216,43 +221,24 @@ func (c *WorksmobileHTTPClient) BackfillOrgUnitExternalKeyByLocalPart(ctx contex
|
||||
}
|
||||
return c.PatchOrgUnit(ctx, group.ID, NewWorksmobileOrgUnitPatchPayload(payload))
|
||||
}
|
||||
}
|
||||
if localPart == "" {
|
||||
return fmt.Errorf("worksmobile orgunit local-part match key is required")
|
||||
}
|
||||
matches := make([]WorksmobileRemoteGroup, 0, 1)
|
||||
for _, group := range groups {
|
||||
if payload.DomainID > 0 && group.DomainID > 0 && payload.DomainID != group.DomainID {
|
||||
continue
|
||||
}
|
||||
if group.MailLocalPart == localPart {
|
||||
matches = append(matches, group)
|
||||
if normalizedMatchLocalPart != "" && worksmobileMailLocalPart(group.MailLocalPart) == normalizedMatchLocalPart {
|
||||
matched := group
|
||||
if localPartMatch != nil && localPartMatch.ID != matched.ID {
|
||||
return fmt.Errorf("worksmobile orgunit local-part match is ambiguous: %s", normalizedMatchLocalPart)
|
||||
}
|
||||
localPartMatch = &matched
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return fmt.Errorf("worksmobile orgunit local-part match not found: %s", localPart)
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return fmt.Errorf("worksmobile orgunit local-part match is ambiguous: %s", localPart)
|
||||
}
|
||||
remote := matches[0]
|
||||
if strings.TrimSpace(remote.ID) == "" {
|
||||
return fmt.Errorf("worksmobile orgunit id is missing for local-part: %s", localPart)
|
||||
}
|
||||
if strings.TrimSpace(remote.ExternalID) != "" {
|
||||
if remote.ExternalID == payload.OrgUnitExternalKey {
|
||||
if localPartMatch != nil {
|
||||
if strings.TrimSpace(localPartMatch.ID) == "" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("worksmobile orgunit external key already exists for local-part %s: %s", localPart, remote.ExternalID)
|
||||
if delay := c.orgUnitWriteDelay(); delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return c.PatchOrgUnit(ctx, localPartMatch.ID, NewWorksmobileOrgUnitPatchPayload(payload))
|
||||
}
|
||||
if delay := c.orgUnitWriteDelay(); delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
patch := NewWorksmobileOrgUnitPatchPayload(payload)
|
||||
if patch.Email == "" {
|
||||
patch.Email = remote.Email
|
||||
}
|
||||
return c.PatchOrgUnit(ctx, remote.ID, patch)
|
||||
return fmt.Errorf("worksmobile orgunit external key match not found after create conflict: %s", payload.OrgUnitExternalKey)
|
||||
}
|
||||
|
||||
func (c *WorksmobileHTTPClient) PatchOrgUnit(ctx context.Context, orgUnitID string, payload WorksmobileOrgUnitPatchPayload) error {
|
||||
|
||||
@@ -325,6 +325,7 @@ func TestWorksmobileHTTPClientUpsertOrgUnitBackfillsExternalKeyByMailLocalPart(t
|
||||
require.Len(t, transport.requests, 3)
|
||||
require.Equal(t, http.MethodPost, transport.requests[0].Method)
|
||||
require.Equal(t, "/v1.0/orgunits", transport.requests[0].URL.Path)
|
||||
require.Contains(t, string(transport.requestBodies[0]), `"displayOrder":1`)
|
||||
require.Equal(t, http.MethodGet, transport.requests[1].Method)
|
||||
require.Equal(t, "/v1.0/orgunits", transport.requests[1].URL.Path)
|
||||
require.Equal(t, http.MethodPatch, transport.requests[2].Method)
|
||||
@@ -332,6 +333,34 @@ func TestWorksmobileHTTPClientUpsertOrgUnitBackfillsExternalKeyByMailLocalPart(t
|
||||
require.Contains(t, string(transport.requestBodies[1]), `"orgUnitExternalKey":"tenant-tech-dev-center"`)
|
||||
}
|
||||
|
||||
func TestWorksmobileHTTPClientUpsertOrgUnitDoesNotBackfillExternalKeyByName(t *testing.T) {
|
||||
transport := &captureRoundTripper{
|
||||
responses: []captureResponse{
|
||||
{statusCode: http.StatusConflict, body: `{"code":"CONFLICT"}`},
|
||||
{statusCode: http.StatusOK, body: `{"orgUnits":[{"orgUnitId":"works-org-1","orgUnitName":"기술개발센터","email":"legacy-tech@samaneng.com"}],"responseMetaData":{}}`},
|
||||
},
|
||||
}
|
||||
client := &WorksmobileHTTPClient{
|
||||
BaseURL: "https://works.example.test",
|
||||
DirectoryToken: "directory-token-1",
|
||||
DomainIDs: []int64{300285955},
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
OrgUnitWriteDelay: -1,
|
||||
}
|
||||
|
||||
err := client.UpsertOrgUnit(context.Background(), WorksmobileOrgUnitPayload{
|
||||
DomainID: 300285955,
|
||||
OrgUnitName: "기술개발센터",
|
||||
OrgUnitExternalKey: "tenant-tech-dev-center",
|
||||
DisplayOrder: 1,
|
||||
}, "tech-dev-center")
|
||||
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "external key match not found")
|
||||
require.Len(t, transport.requests, 2)
|
||||
require.Equal(t, http.MethodGet, transport.requests[1].Method)
|
||||
}
|
||||
|
||||
func TestWorksmobileHTTPClientUpsertOrgUnitTreatsExistingExternalKeyConflictAsSuccess(t *testing.T) {
|
||||
transport := &captureRoundTripper{
|
||||
responses: []captureResponse{
|
||||
@@ -463,6 +492,29 @@ func TestWorksmobileRelayWorkerProcessesActiveUserUpsertAndReactivates(t *testin
|
||||
require.Equal(t, []string{"tester@samaneng.com"}, client.activeUsers)
|
||||
}
|
||||
|
||||
func TestWorksmobileRelayWorkerProcessesOrgUnitDeleteAndMarksProcessed(t *testing.T) {
|
||||
repo := &fakeWorksmobileOutboxRepo{
|
||||
ready: []domain.WorksmobileOutbox{
|
||||
{
|
||||
ID: "job-1",
|
||||
ResourceType: domain.WorksmobileResourceOrgUnit,
|
||||
ResourceID: "works-org-1",
|
||||
Action: domain.WorksmobileActionDelete,
|
||||
Status: domain.WorksmobileOutboxStatusPending,
|
||||
Payload: domain.JSONMap{"worksmobileId": "works-org-1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
client := &fakeWorksmobileDirectoryClient{}
|
||||
worker := NewWorksmobileRelayWorker(repo, client)
|
||||
|
||||
err := worker.ProcessOnce(context.Background())
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"job-1"}, repo.processedIDs)
|
||||
require.Equal(t, []string{"works-org-1"}, client.deletedOrgUnits)
|
||||
}
|
||||
|
||||
func TestRedactWorksmobileOutboxPayloadsRemovesInitialPasswordFromOverview(t *testing.T) {
|
||||
jobs := []domain.WorksmobileOutbox{
|
||||
{
|
||||
@@ -564,8 +616,8 @@ func TestCompareWorksmobileGroupsIncludesBaronAndWorksParentOrg(t *testing.T) {
|
||||
parentID := "tenant-parent"
|
||||
childID := "tenant-child"
|
||||
localTenants := []domain.Tenant{
|
||||
{ID: parentID, Name: "기술본부", Type: domain.TenantTypeOrganization},
|
||||
{ID: childID, Name: "기술기획", Type: domain.TenantTypeOrganization, ParentID: &parentID},
|
||||
{ID: parentID, Slug: "tech-hq", Name: "기술본부", Type: domain.TenantTypeOrganization},
|
||||
{ID: childID, Slug: "tech-planning", Name: "기술기획", Type: domain.TenantTypeOrganization, ParentID: &parentID},
|
||||
}
|
||||
remoteGroups := []WorksmobileRemoteGroup{
|
||||
{
|
||||
@@ -589,7 +641,9 @@ func TestCompareWorksmobileGroupsIncludesBaronAndWorksParentOrg(t *testing.T) {
|
||||
items := compareWorksmobileGroups(localTenants, remoteGroups, true)
|
||||
|
||||
require.Len(t, items, 2)
|
||||
require.Equal(t, "tech-planning", items[1].BaronSlug)
|
||||
require.Equal(t, parentID, items[1].BaronParentID)
|
||||
require.Equal(t, "tech-hq", items[1].BaronParentSlug)
|
||||
require.Equal(t, "기술본부", items[1].BaronParentName)
|
||||
require.Equal(t, int64(300286337), items[1].WorksmobileDomainID)
|
||||
require.Equal(t, "총괄기획&기술개발센터", items[1].WorksmobileDomainName)
|
||||
@@ -638,7 +692,7 @@ func TestCompareWorksmobileGroupsIncludesWorksOnlyRowsWithoutExternalIDWhenInclu
|
||||
require.Equal(t, "WORKS 전용 조직", items[0].WorksmobileName)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileGroupsMatchesBySlugLocalPartWhenExternalIDMissing(t *testing.T) {
|
||||
func TestCompareWorksmobileGroupsDoesNotMatchBySlugLocalPartWhenExternalIDMissing(t *testing.T) {
|
||||
localTenants := []domain.Tenant{
|
||||
{
|
||||
ID: "tenant-tech-dev-center",
|
||||
@@ -659,12 +713,75 @@ func TestCompareWorksmobileGroupsMatchesBySlugLocalPartWhenExternalIDMissing(t *
|
||||
diffOnly := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
||||
all := compareWorksmobileGroups(localTenants, remoteGroups, true)
|
||||
|
||||
require.Empty(t, diffOnly)
|
||||
require.Len(t, all, 1)
|
||||
require.Equal(t, "matched", all[0].Status)
|
||||
require.Len(t, diffOnly, 2)
|
||||
require.Equal(t, "missing_in_worksmobile", diffOnly[0].Status)
|
||||
require.Equal(t, "tenant-tech-dev-center", diffOnly[0].BaronID)
|
||||
require.Equal(t, "missing_external_key", diffOnly[1].Status)
|
||||
require.Equal(t, "works-org-1", diffOnly[1].WorksmobileID)
|
||||
require.Len(t, all, 2)
|
||||
require.Equal(t, "missing_in_worksmobile", all[0].Status)
|
||||
require.Equal(t, "tenant-tech-dev-center", all[0].BaronID)
|
||||
require.Equal(t, "works-org-1", all[0].WorksmobileID)
|
||||
require.Empty(t, all[0].ExternalKey)
|
||||
require.Equal(t, "missing_external_key", all[1].Status)
|
||||
require.Equal(t, "works-org-1", all[1].WorksmobileID)
|
||||
require.Empty(t, all[1].ExternalKey)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileGroupsDoesNotMatchByNameWhenExternalIDAndSlugAreMissing(t *testing.T) {
|
||||
localTenants := []domain.Tenant{
|
||||
{
|
||||
ID: "tenant-tech-dev-center",
|
||||
Slug: "tech-dev-center",
|
||||
Name: "기술개발센터",
|
||||
Type: domain.TenantTypeOrganization,
|
||||
},
|
||||
}
|
||||
remoteGroups := []WorksmobileRemoteGroup{
|
||||
{
|
||||
ID: "works-org-1",
|
||||
DisplayName: "기술개발센터",
|
||||
},
|
||||
}
|
||||
|
||||
items := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
||||
|
||||
require.Len(t, items, 2)
|
||||
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
||||
require.Equal(t, "tenant-tech-dev-center", items[0].BaronID)
|
||||
require.Equal(t, "missing_external_key", items[1].Status)
|
||||
require.Equal(t, "works-org-1", items[1].WorksmobileID)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileGroupsListsExternalKeyMissingRowsAsDeleteCandidatesAcrossDomains(t *testing.T) {
|
||||
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
||||
t.Setenv("HANMAC_DOMAIN_ID", "1002")
|
||||
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
||||
t.Setenv("BARONGROUP_DOMAIN_ID", "1004")
|
||||
rootID := "root-tenant"
|
||||
samanID := "company-saman"
|
||||
hanmacID := "company-hanmac"
|
||||
localTenants := []domain.Tenant{
|
||||
{ID: rootID, Slug: HanmacFamilyTenantSlug, Name: "한맥가족", Type: domain.TenantTypeCompanyGroup},
|
||||
{ID: samanID, Slug: "saman", Name: "삼안", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "samaneng.com"}}},
|
||||
{ID: hanmacID, Slug: "hanmac", Name: "한맥기술", Type: domain.TenantTypeCompany, ParentID: &rootID, Domains: []domain.TenantDomain{{Domain: "hanmaceng.co.kr"}}},
|
||||
{ID: "tenant-saman-planning", Slug: "planning", Name: "기획팀", Type: domain.TenantTypeOrganization, ParentID: &samanID},
|
||||
{ID: "tenant-hanmac-planning", Slug: "planning", Name: "기획팀", Type: domain.TenantTypeOrganization, ParentID: &hanmacID},
|
||||
}
|
||||
remoteGroups := []WorksmobileRemoteGroup{
|
||||
{ID: "works-saman-planning", DomainID: 1001, DisplayName: "기획팀", MailLocalPart: "planning"},
|
||||
{ID: "works-hanmac-planning", DomainID: 1002, DisplayName: "기획팀", MailLocalPart: "planning"},
|
||||
}
|
||||
|
||||
items := compareWorksmobileGroups(localTenants, remoteGroups, false)
|
||||
|
||||
require.Len(t, items, 4)
|
||||
require.Equal(t, "tenant-saman-planning", items[0].BaronID)
|
||||
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
||||
require.Equal(t, "tenant-hanmac-planning", items[1].BaronID)
|
||||
require.Equal(t, "missing_in_worksmobile", items[1].Status)
|
||||
require.Equal(t, "works-saman-planning", items[2].WorksmobileID)
|
||||
require.Equal(t, "missing_external_key", items[2].Status)
|
||||
require.Equal(t, "works-hanmac-planning", items[3].WorksmobileID)
|
||||
require.Equal(t, "missing_external_key", items[3].Status)
|
||||
}
|
||||
|
||||
func TestParseWorksmobileRemoteUserUsesUserNameEmailWhenEmailsAreEmpty(t *testing.T) {
|
||||
@@ -802,11 +919,13 @@ func (f *fakeWorksmobileOutboxRepo) MarkFailed(ctx context.Context, id string, m
|
||||
|
||||
type fakeWorksmobileDirectoryClient struct {
|
||||
createdOrgUnits []WorksmobileOrgUnitPayload
|
||||
deletedOrgUnits []string
|
||||
createdUsers []WorksmobileUserPayload
|
||||
deletedUsers []string
|
||||
activeUsers []string
|
||||
suspendedUsers []string
|
||||
orgUnitMatchKeys []string
|
||||
groups []WorksmobileRemoteGroup
|
||||
}
|
||||
|
||||
type captureRoundTripper struct {
|
||||
@@ -880,6 +999,11 @@ func (f *fakeWorksmobileDirectoryClient) UpsertOrgUnit(ctx context.Context, payl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) DeleteOrgUnit(ctx context.Context, orgUnitID string) error {
|
||||
f.deletedOrgUnits = append(f.deletedOrgUnits, orgUnitID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) CreateUser(ctx context.Context, payload WorksmobileUserPayload) error {
|
||||
f.createdUsers = append(f.createdUsers, payload)
|
||||
return nil
|
||||
@@ -909,5 +1033,5 @@ func (f *fakeWorksmobileDirectoryClient) ListUsers(ctx context.Context) ([]Works
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) ListGroups(ctx context.Context) ([]WorksmobileRemoteGroup, error) {
|
||||
return nil, nil
|
||||
return f.groups, nil
|
||||
}
|
||||
|
||||
@@ -126,6 +126,13 @@ func TestWorksmobileLiveGPDTDCOrgUnitProvisioning(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorksmobileLiveBaronGroupOrgUnitProvisioning(t *testing.T) {
|
||||
if os.Getenv("WORKSMOBILE_LIVE_BARONGROUP_ORGUNIT_PROVISIONING") != "1" {
|
||||
t.Skip("live Worksmobile Baron Group orgunit provisioning is disabled")
|
||||
}
|
||||
runWorksmobileLiveBaronGroupOrgUnitProvisioning(t)
|
||||
}
|
||||
|
||||
func TestWorksmobileLiveSyncHanmacFamilyOrgUnits(t *testing.T) {
|
||||
if os.Getenv("WORKSMOBILE_LIVE_SYNC_HANMAC_FAMILY_ORGUNITS") != "1" {
|
||||
t.Skip("live Worksmobile Hanmac family orgunit sync is disabled")
|
||||
@@ -548,6 +555,142 @@ func runWorksmobileLiveCompanyOrgUnitProvisioning(t *testing.T, companySlug stri
|
||||
}
|
||||
}
|
||||
|
||||
func runWorksmobileLiveBaronGroupOrgUnitProvisioning(t *testing.T) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
db, err := gorm.Open(postgres.Open(worksmobileLiveDSN()), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tenantRepo := repository.NewTenantRepository(db)
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
userGroupRepo := repository.NewUserGroupRepository(db)
|
||||
tenantService := NewTenantService(tenantRepo, userRepo, userGroupRepo, nil)
|
||||
client := NewWorksmobileHTTPClientWithAuth("", os.Getenv("SAMAN_SCIM_LONGLIVE_TOKEN"), WorksmobileOAuthConfig{
|
||||
ClientID: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_ID"),
|
||||
ClientSecret: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SECRET"),
|
||||
ServiceAccount: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_SERVICE_ACCOUNT"),
|
||||
PrivateKey: os.Getenv("WORKS_ADMIN_OAUTH_CLIENT_PRIVATE_KEY"),
|
||||
Scope: getenvDefault("WORKS_ADMIN_OAUTH_SCOPE", "directory"),
|
||||
})
|
||||
|
||||
baronGroup, err := tenantService.GetTenantBySlug(ctx, "baron-group")
|
||||
require.NoError(t, err)
|
||||
tenants, err := listWorksmobileLiveTenantScope(db, baronGroup.ID)
|
||||
require.NoError(t, err)
|
||||
domainID, ok := worksmobileDomainIDFromEnv("BARONGROUP_DOMAIN_ID")
|
||||
require.True(t, ok, "missing BARONGROUP_DOMAIN_ID")
|
||||
mailDomain := getenvDefault("BARONGROUP_MAIL_DOMAIN", getenvDefault("WORKS_DEFAULT_DOMAIN_BARONGROUP", "brsw.kr"))
|
||||
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*baronGroup}, tenants...))
|
||||
targets := worksmobileLiveBaronGroupOrgUnitTargets(t, tenants, tenantByID, *baronGroup, domainID, mailDomain)
|
||||
targetByID := map[string]worksmobileLiveOrgUnitTarget{}
|
||||
for _, target := range targets {
|
||||
targetByID[target.Tenant.ID] = target
|
||||
}
|
||||
|
||||
remoteGroups, err := client.ListGroups(ctx)
|
||||
require.NoError(t, err)
|
||||
remoteByExternalID, duplicateExternalKeys := worksmobileLiveRemoteByExternalID(remoteGroups)
|
||||
require.Empty(t, duplicateExternalKeys, "duplicate Worksmobile external keys")
|
||||
|
||||
for _, target := range sortWorksmobileLiveTargetsTopologically(targets, tenantByID) {
|
||||
remote, found := remoteByExternalID[target.Tenant.ID]
|
||||
if found && remote.DomainID != target.Payload.DomainID {
|
||||
t.Logf("REKEY conflicting external key slug=%s external=%s worksID=%s currentDomain=%d expectedDomain=%d", target.Tenant.Slug, target.Tenant.ID, remote.ID, remote.DomainID, target.Payload.DomainID)
|
||||
if err := client.ClearOrgUnitExternalKey(ctx, remote.ID, remote.DomainID); err != nil {
|
||||
legacyPatch := WorksmobileOrgUnitPatchPayload{
|
||||
DomainID: remote.DomainID,
|
||||
OrgUnitExternalKey: "legacy-" + remote.ID,
|
||||
}
|
||||
require.NoError(t, client.PatchOrgUnit(ctx, remote.ID, legacyPatch))
|
||||
}
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
remoteGroups, err = client.ListGroups(ctx)
|
||||
require.NoError(t, err)
|
||||
remoteByExternalID, duplicateExternalKeys = worksmobileLiveRemoteByExternalID(remoteGroups)
|
||||
require.Empty(t, duplicateExternalKeys, "duplicate Worksmobile external keys")
|
||||
remote, found = remoteByExternalID[target.Tenant.ID]
|
||||
if found && remote.DomainID != target.Payload.DomainID {
|
||||
require.Failf(t, "external key is attached to a different Worksmobile domain after rekey", "slug=%s external=%s currentDomain=%d expectedDomain=%d", target.Tenant.Slug, target.Tenant.ID, remote.DomainID, target.Payload.DomainID)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
remote, found = findWorksmobileLiveRemoteByPath(remoteGroups, worksmobileLiveRemoteByID(remoteGroups), target.Payload.DomainID, worksmobileLiveTenantOrgPath(target.Tenant, tenantByID))
|
||||
}
|
||||
if found {
|
||||
t.Logf("PATCH orgunit slug=%s id=%s worksID=%s email=%s parent=%s", target.Tenant.Slug, target.Tenant.ID, remote.ID, target.Payload.Email, target.Payload.ParentOrgUnitID)
|
||||
require.NoError(t, patchWorksmobileLiveOrgUnit(ctx, client, remote.ID, target.Payload))
|
||||
} else {
|
||||
t.Logf("CREATE orgunit slug=%s id=%s email=%s parent=%s", target.Tenant.Slug, target.Tenant.ID, target.Payload.Email, target.Payload.ParentOrgUnitID)
|
||||
require.NoError(t, client.UpsertOrgUnit(ctx, target.Payload, target.Tenant.Slug))
|
||||
}
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
|
||||
remoteGroups, err = client.ListGroups(ctx)
|
||||
require.NoError(t, err)
|
||||
remoteByExternalID, duplicateExternalKeys = worksmobileLiveRemoteByExternalID(remoteGroups)
|
||||
require.Empty(t, duplicateExternalKeys, "duplicate Worksmobile external keys")
|
||||
}
|
||||
|
||||
remoteGroups, err = client.ListGroups(ctx)
|
||||
require.NoError(t, err)
|
||||
remoteByExternalID, duplicateExternalKeys = worksmobileLiveRemoteByExternalID(remoteGroups)
|
||||
require.Empty(t, duplicateExternalKeys, "duplicate Worksmobile external keys")
|
||||
remoteByID := worksmobileLiveRemoteByID(remoteGroups)
|
||||
for _, target := range targets {
|
||||
remote, ok := remoteByExternalID[target.Tenant.ID]
|
||||
require.True(t, ok, "missing Worksmobile orgunit after sync: %s", target.Tenant.Slug)
|
||||
require.Equal(t, target.Payload.DomainID, remote.DomainID, "domain mismatch: %s", target.Tenant.Slug)
|
||||
require.Equal(t, target.Tenant.Name, remote.DisplayName, "name mismatch: %s", target.Tenant.Slug)
|
||||
require.Equal(t, worksmobileMailLocalPart(target.Payload.Email), remote.MailLocalPart, "email local-part mismatch: %s", target.Tenant.Slug)
|
||||
require.Equal(t, strings.ToLower(strings.TrimSpace(target.Payload.Email)), strings.ToLower(strings.TrimSpace(remote.Email)), "email mismatch: %s", target.Tenant.Slug)
|
||||
expectedParentID := ""
|
||||
if parentExternalKey := strings.TrimPrefix(target.Payload.ParentOrgUnitID, "externalKey:"); parentExternalKey != "" && parentExternalKey != target.Payload.ParentOrgUnitID {
|
||||
parentRemote, ok := remoteByExternalID[parentExternalKey]
|
||||
require.True(t, ok, "missing Worksmobile parent for %s", target.Tenant.Slug)
|
||||
expectedParentID = parentRemote.ID
|
||||
parentTarget, ok := targetByID[parentExternalKey]
|
||||
require.True(t, ok, "missing Baron parent target for %s", target.Tenant.Slug)
|
||||
require.Equal(t, worksmobileMailLocalPart(parentTarget.Payload.Email), parentRemote.MailLocalPart, "parent email local-part mismatch: %s", target.Tenant.Slug)
|
||||
}
|
||||
require.Equal(t, expectedParentID, remote.ParentID, "parent mismatch: %s", target.Tenant.Slug)
|
||||
require.Equal(t, worksmobileLiveTenantOrgPath(target.Tenant, tenantByID), worksmobileLiveRemotePath(remoteByID, remote), "path mismatch: %s", target.Tenant.Slug)
|
||||
}
|
||||
|
||||
t.Logf("SUMMARY synced=%d domainID=%d", len(targets), domainID)
|
||||
}
|
||||
|
||||
func worksmobileLiveBaronGroupOrgUnitTargets(t *testing.T, tenants []domain.Tenant, tenantByID map[string]domain.Tenant, root domain.Tenant, domainID int64, mailDomain string) []worksmobileLiveOrgUnitTarget {
|
||||
t.Helper()
|
||||
mailDomain = strings.ToLower(strings.TrimSpace(mailDomain))
|
||||
require.NotEmpty(t, mailDomain, "baron group mail domain is required")
|
||||
targets := make([]worksmobileLiveOrgUnitTarget, 0)
|
||||
seenExternalKeys := map[string]string{}
|
||||
seenEmails := map[string]string{}
|
||||
for index, tenant := range tenants {
|
||||
if !isWorksmobileOrgUnitTenant(tenant, tenantByID) || worksmobileLiveSkipOrgUnitTenant(tenant) {
|
||||
continue
|
||||
}
|
||||
payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(tenant, root, root.Config, index+1)
|
||||
require.NoError(t, err, "payload build failed: %s", tenant.Slug)
|
||||
payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID)
|
||||
payload.DomainID = domainID
|
||||
payload.Email = strings.ToLower(strings.TrimSpace(tenant.Slug)) + "@" + mailDomain
|
||||
require.NotEmpty(t, payload.Email, "orgunit email is required: %s", tenant.Slug)
|
||||
if owner, exists := seenExternalKeys[payload.OrgUnitExternalKey]; exists {
|
||||
require.Failf(t, "duplicate Baron external key", "external=%s owner=%s duplicate=%s", payload.OrgUnitExternalKey, owner, tenant.Slug)
|
||||
}
|
||||
seenExternalKeys[payload.OrgUnitExternalKey] = tenant.Slug
|
||||
normalizedEmail := strings.ToLower(strings.TrimSpace(payload.Email))
|
||||
if owner, exists := seenEmails[normalizedEmail]; exists {
|
||||
require.Failf(t, "duplicate Baron orgunit email", "email=%s owner=%s duplicate=%s", normalizedEmail, owner, tenant.Slug)
|
||||
}
|
||||
seenEmails[normalizedEmail] = tenant.Slug
|
||||
targets = append(targets, worksmobileLiveOrgUnitTarget{Tenant: tenant, Payload: payload})
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func createWorksmobileLiveOrgUnitIfMissing(t *testing.T, ctx context.Context, client *WorksmobileHTTPClient, tenant domain.Tenant) {
|
||||
t.Helper()
|
||||
payload, err := BuildWorksmobileOrgUnitPayload(tenant, nil, 1)
|
||||
|
||||
@@ -72,6 +72,9 @@ func BuildWorksmobileOrgUnitPayloadForDomainTenant(tenant domain.Tenant, domainT
|
||||
if err := ValidateWorksmobileExternalKey(tenant.ID); err != nil {
|
||||
return WorksmobileOrgUnitPayload{}, err
|
||||
}
|
||||
if displayOrder < 1 {
|
||||
displayOrder = 1
|
||||
}
|
||||
domainID, err := ResolveWorksmobileDomainIDFromTenant(domainTenant, rootConfig)
|
||||
if err != nil {
|
||||
return WorksmobileOrgUnitPayload{}, err
|
||||
|
||||
@@ -56,6 +56,21 @@ func TestBuildWorksmobileOrgUnitPayloadUsesWorksmobileMailDomainForBarongroup(t
|
||||
require.Equal(t, "jangheon@brsw.kr", payload.Email)
|
||||
}
|
||||
|
||||
func TestBuildWorksmobileOrgUnitPayloadDefaultsDisplayOrderToOne(t *testing.T) {
|
||||
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
||||
tenant := domain.Tenant{
|
||||
ID: "11111111-1111-1111-1111-111111111111",
|
||||
Slug: "tech-dev-center",
|
||||
Name: "기술개발센터",
|
||||
Domains: []domain.TenantDomain{{Domain: "samaneng.com"}},
|
||||
}
|
||||
|
||||
payload, err := BuildWorksmobileOrgUnitPayload(tenant, nil, 0)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, payload.DisplayOrder)
|
||||
}
|
||||
|
||||
func TestNormalizeRootChildWorksmobileOrgUnitParentClearsCrossDomainParent(t *testing.T) {
|
||||
rootID := "038326b6-954a-48a7-a85f-efd83f62b82a"
|
||||
payload := WorksmobileOrgUnitPayload{ParentOrgUnitID: "externalKey:" + rootID}
|
||||
|
||||
@@ -82,6 +82,9 @@ func (w *WorksmobileRelayWorker) dispatch(ctx context.Context, job domain.Worksm
|
||||
|
||||
switch job.ResourceType {
|
||||
case domain.WorksmobileResourceOrgUnit:
|
||||
if job.Action == domain.WorksmobileActionDelete {
|
||||
return w.client.DeleteOrgUnit(ctx, stringValue(job.Payload["worksmobileId"]))
|
||||
}
|
||||
if job.Action != domain.WorksmobileActionUpsert {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type WorksmobileAdminService interface {
|
||||
GetComparison(ctx context.Context, tenantID string, includeMatched bool) (WorksmobileComparison, error)
|
||||
EnqueueBackfillDryRun(ctx context.Context, tenantID string) (WorksmobileBackfillDryRun, error)
|
||||
EnqueueOrgUnitSync(ctx context.Context, tenantID, orgUnitID string) (*domain.WorksmobileOutbox, error)
|
||||
EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error)
|
||||
EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error)
|
||||
RetryJob(ctx context.Context, tenantID, jobID string) (*domain.WorksmobileOutbox, error)
|
||||
ListInitialPasswordCredentials(ctx context.Context, tenantID string) ([]WorksmobileInitialPasswordCredential, error)
|
||||
@@ -62,11 +63,14 @@ type WorksmobileComparison struct {
|
||||
type WorksmobileComparisonItem struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
BaronID string `json:"baronId,omitempty"`
|
||||
BaronSlug string `json:"baronSlug,omitempty"`
|
||||
BaronName string `json:"baronName,omitempty"`
|
||||
BaronEmail string `json:"baronEmail,omitempty"`
|
||||
BaronPrimaryOrgID string `json:"baronPrimaryOrgId,omitempty"`
|
||||
BaronPrimaryOrgSlug string `json:"baronPrimaryOrgSlug,omitempty"`
|
||||
BaronPrimaryOrgName string `json:"baronPrimaryOrgName,omitempty"`
|
||||
BaronParentID string `json:"baronParentId,omitempty"`
|
||||
BaronParentSlug string `json:"baronParentSlug,omitempty"`
|
||||
BaronParentName string `json:"baronParentName,omitempty"`
|
||||
WorksmobileID string `json:"worksmobileId,omitempty"`
|
||||
ExternalKey string `json:"externalKey,omitempty"`
|
||||
@@ -82,8 +86,13 @@ type WorksmobileComparisonItem struct {
|
||||
WorksmobilePrimaryOrgPositionID string `json:"worksmobilePrimaryOrgPositionId,omitempty"`
|
||||
WorksmobilePrimaryOrgPositionName string `json:"worksmobilePrimaryOrgPositionName,omitempty"`
|
||||
WorksmobilePrimaryOrgIsManager *bool `json:"worksmobilePrimaryOrgIsManager,omitempty"`
|
||||
BaronParentWorksmobileID string `json:"baronParentWorksmobileId,omitempty"`
|
||||
BaronParentWorksmobileName string `json:"baronParentWorksmobileName,omitempty"`
|
||||
BaronParentWorksmobileEmail string `json:"baronParentWorksmobileEmail,omitempty"`
|
||||
WorksmobileParentID string `json:"worksmobileParentId,omitempty"`
|
||||
WorksmobileParentName string `json:"worksmobileParentName,omitempty"`
|
||||
WorksmobileParentEmail string `json:"worksmobileParentEmail,omitempty"`
|
||||
WorksmobileParentExternalKey string `json:"worksmobileParentExternalKey,omitempty"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -243,16 +252,21 @@ func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantI
|
||||
if !isWorksmobileOrgUnitTenant(*tenant, tenantByID) {
|
||||
return nil, errors.New("target tenant is not a worksmobile orgunit tenant")
|
||||
}
|
||||
return s.enqueueOrgUnitUpsert(ctx, root, *tenant, scopeTenants)
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) enqueueOrgUnitUpsert(ctx context.Context, root *domain.Tenant, tenant domain.Tenant, scopeTenants []domain.Tenant) (*domain.WorksmobileOutbox, error) {
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
|
||||
payload, err := BuildWorksmobileOrgUnitPayloadForDomainTenant(
|
||||
*tenant,
|
||||
worksmobileDomainClassificationTenant(*tenant, tenantByID),
|
||||
tenant,
|
||||
worksmobileDomainClassificationTenant(tenant, tenantByID),
|
||||
root.Config,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = normalizeWorksmobileOrgUnitParent(payload, *tenant, tenantByID, root.ID)
|
||||
payload = normalizeWorksmobileOrgUnitParent(payload, tenant, tenantByID, root.ID)
|
||||
item := &domain.WorksmobileOutbox{
|
||||
ResourceType: domain.WorksmobileResourceOrgUnit,
|
||||
ResourceID: tenant.ID,
|
||||
@@ -269,6 +283,62 @@ func (s *worksmobileSyncService) EnqueueOrgUnitSync(ctx context.Context, tenantI
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) EnqueueOrgUnitDelete(ctx context.Context, tenantID, worksmobileOrgUnitID string) (*domain.WorksmobileOutbox, error) {
|
||||
root, err := s.hanmacRoot(ctx, tenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.client == nil {
|
||||
return nil, errors.New("worksmobile client is not configured")
|
||||
}
|
||||
scopeTenants, err := s.hanmacSubtree(ctx, root.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worksmobileOrgUnitID = strings.TrimSpace(worksmobileOrgUnitID)
|
||||
if worksmobileOrgUnitID == "" {
|
||||
return nil, errors.New("worksmobile orgunit id is required")
|
||||
}
|
||||
groups, err := s.client.ListGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var target *WorksmobileRemoteGroup
|
||||
for i := range groups {
|
||||
if strings.TrimSpace(groups[i].ID) == worksmobileOrgUnitID {
|
||||
target = &groups[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
return nil, errors.New("worksmobile orgunit not found")
|
||||
}
|
||||
tenantByID := worksmobileTenantByID(append([]domain.Tenant{*root}, scopeTenants...))
|
||||
if tenant, ok := findWorksmobileOrgUnitTenantByRemoteLocalPart(*target, scopeTenants, tenantByID); ok {
|
||||
return s.enqueueOrgUnitUpsert(ctx, root, tenant, scopeTenants)
|
||||
}
|
||||
if isProtectedWorksmobileRemoteOrgUnit(*root, scopeTenants, *target) {
|
||||
return nil, errors.New("protected worksmobile domain root orgunit cannot be deleted")
|
||||
}
|
||||
item := &domain.WorksmobileOutbox{
|
||||
ResourceType: domain.WorksmobileResourceOrgUnit,
|
||||
ResourceID: worksmobileOrgUnitID,
|
||||
Action: domain.WorksmobileActionDelete,
|
||||
DedupeKey: "orgunit:delete:works:" + worksmobileOrgUnitID,
|
||||
Payload: domain.JSONMap{
|
||||
"worksmobileId": worksmobileOrgUnitID,
|
||||
"externalKey": target.ExternalID,
|
||||
"domainId": target.DomainID,
|
||||
"name": target.DisplayName,
|
||||
"email": target.Email,
|
||||
},
|
||||
}
|
||||
if err := s.outboxRepo.Create(ctx, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *worksmobileSyncService) EnqueueUserSync(ctx context.Context, tenantID, userID string) (*domain.WorksmobileOutbox, error) {
|
||||
root, err := s.hanmacRoot(ctx, tenantID)
|
||||
if err != nil {
|
||||
@@ -585,6 +655,53 @@ func addWorksmobileLocalPart(target map[string]string, email string, owner strin
|
||||
}
|
||||
}
|
||||
|
||||
func findWorksmobileOrgUnitTenantByRemoteLocalPart(remote WorksmobileRemoteGroup, localTenants []domain.Tenant, tenantByID map[string]domain.Tenant) (domain.Tenant, bool) {
|
||||
candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
|
||||
for _, tenant := range localTenants {
|
||||
if !isWorksmobileOrgUnitTenant(tenant, tenantByID) {
|
||||
continue
|
||||
}
|
||||
if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
|
||||
return tenant, true
|
||||
}
|
||||
}
|
||||
return domain.Tenant{}, false
|
||||
}
|
||||
|
||||
func isProtectedWorksmobileRemoteOrgUnit(root domain.Tenant, localTenants []domain.Tenant, remote WorksmobileRemoteGroup) bool {
|
||||
if strings.TrimSpace(remote.ParentID) == "" {
|
||||
return true
|
||||
}
|
||||
candidates := worksmobileRemoteGroupLocalPartCandidates(remote)
|
||||
if len(candidates) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, tenant := range localTenants {
|
||||
if tenant.ParentID == nil || *tenant.ParentID != root.ID || tenant.Type != domain.TenantTypeCompany {
|
||||
continue
|
||||
}
|
||||
if candidates[normalizeWorksmobileSlugLocalPart(tenant.Slug)] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func worksmobileRemoteGroupLocalPartCandidates(remote WorksmobileRemoteGroup) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
if localPart := normalizeWorksmobileSlugLocalPart(remote.MailLocalPart); localPart != "" {
|
||||
result[localPart] = true
|
||||
}
|
||||
if localPart, err := domain.ExtractNormalizedEmailLocalPart(remote.Email); err == nil && localPart != "" {
|
||||
result[localPart] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeWorksmobileSlugLocalPart(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func isWorksmobileOrgUnitTenant(tenant domain.Tenant, tenantByID map[string]domain.Tenant) bool {
|
||||
if tenant.Type == domain.TenantTypeOrganization {
|
||||
return true
|
||||
@@ -703,6 +820,7 @@ func compareWorksmobileUsers(localUsers []domain.User, remoteUsers []Worksmobile
|
||||
BaronName: user.Name,
|
||||
BaronEmail: user.Email,
|
||||
BaronPrimaryOrgID: worksmobileUserPrimaryOrgID(user),
|
||||
BaronPrimaryOrgSlug: worksmobileUserPrimaryOrgSlug(user, localTenants),
|
||||
BaronPrimaryOrgName: worksmobileUserPrimaryOrgName(user, localTenants),
|
||||
Status: "missing_in_worksmobile",
|
||||
}
|
||||
@@ -796,24 +914,30 @@ func worksmobileUserPrimaryOrgName(user domain.User, localTenants map[string]dom
|
||||
return ""
|
||||
}
|
||||
|
||||
func worksmobileUserPrimaryOrgSlug(user domain.User, localTenants map[string]domain.Tenant) string {
|
||||
tenantID := worksmobileUserPrimaryOrgID(user)
|
||||
if tenantID == "" {
|
||||
return ""
|
||||
}
|
||||
if tenant, ok := localTenants[tenantID]; ok {
|
||||
return strings.TrimSpace(tenant.Slug)
|
||||
}
|
||||
if user.Tenant != nil && user.Tenant.ID == tenantID {
|
||||
return strings.TrimSpace(user.Tenant.Slug)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []WorksmobileRemoteGroup, includeMatched bool) []WorksmobileComparisonItem {
|
||||
remoteByExternalID := map[string]WorksmobileRemoteGroup{}
|
||||
remoteByMailLocalPart := map[string]WorksmobileRemoteGroup{}
|
||||
ambiguousMailLocalParts := map[string]bool{}
|
||||
remoteByID := map[string]WorksmobileRemoteGroup{}
|
||||
for _, remote := range remoteGroups {
|
||||
if remote.ID != "" {
|
||||
remoteByID[remote.ID] = remote
|
||||
}
|
||||
if remote.ExternalID != "" {
|
||||
remoteByExternalID[remote.ExternalID] = remote
|
||||
}
|
||||
if remote.ExternalID == "" && remote.MailLocalPart != "" {
|
||||
if _, exists := remoteByMailLocalPart[remote.MailLocalPart]; exists {
|
||||
delete(remoteByMailLocalPart, remote.MailLocalPart)
|
||||
ambiguousMailLocalParts[remote.MailLocalPart] = true
|
||||
continue
|
||||
}
|
||||
if !ambiguousMailLocalParts[remote.MailLocalPart] {
|
||||
remoteByMailLocalPart[remote.MailLocalPart] = remote
|
||||
}
|
||||
}
|
||||
}
|
||||
tenantByID := worksmobileTenantByID(localTenants)
|
||||
localByID := map[string]domain.Tenant{}
|
||||
@@ -827,9 +951,6 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
}
|
||||
localByID[tenant.ID] = tenant
|
||||
remote, matched := remoteByExternalID[tenant.ID]
|
||||
if !matched {
|
||||
remote, matched = remoteByMailLocalPart[worksmobileMailLocalPart(tenant.Slug)]
|
||||
}
|
||||
if matched && !includeMatched {
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
continue
|
||||
@@ -837,8 +958,10 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
item := WorksmobileComparisonItem{
|
||||
ResourceType: "GROUP",
|
||||
BaronID: tenant.ID,
|
||||
BaronSlug: tenant.Slug,
|
||||
BaronName: tenant.Name,
|
||||
BaronParentID: worksmobileTenantParentID(tenant),
|
||||
BaronParentSlug: worksmobileTenantParentSlug(tenant, tenantByID),
|
||||
BaronParentName: worksmobileTenantParentName(tenant, tenantByID),
|
||||
Status: "missing_in_worksmobile",
|
||||
}
|
||||
@@ -852,6 +975,19 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
item.WorksmobileDomainName = remote.DomainName
|
||||
item.WorksmobileParentID = remote.ParentID
|
||||
item.WorksmobileParentName = remote.ParentName
|
||||
if parentRemote, ok := remoteByExternalID[item.BaronParentID]; ok {
|
||||
item.BaronParentWorksmobileID = parentRemote.ID
|
||||
item.BaronParentWorksmobileName = parentRemote.DisplayName
|
||||
item.BaronParentWorksmobileEmail = parentRemote.Email
|
||||
}
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
if item.WorksmobileParentName == "" {
|
||||
item.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
item.WorksmobileParentEmail = parentRemote.Email
|
||||
item.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
item = fillWorksmobileParentFromBaronParentMatch(item)
|
||||
matchedRemoteIDs[remote.ID] = true
|
||||
}
|
||||
result = append(result, item)
|
||||
@@ -873,6 +1009,14 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
WorksmobileParentName: remote.ParentName,
|
||||
Status: "missing_external_key",
|
||||
})
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
last := &result[len(result)-1]
|
||||
if last.WorksmobileParentName == "" {
|
||||
last.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
last.WorksmobileParentEmail = parentRemote.Email
|
||||
last.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ignoredLocalByID[remote.ExternalID] {
|
||||
@@ -891,11 +1035,35 @@ func compareWorksmobileGroups(localTenants []domain.Tenant, remoteGroups []Works
|
||||
WorksmobileParentName: remote.ParentName,
|
||||
Status: "missing_in_baron",
|
||||
})
|
||||
if parentRemote, ok := remoteByID[remote.ParentID]; ok {
|
||||
last := &result[len(result)-1]
|
||||
if last.WorksmobileParentName == "" {
|
||||
last.WorksmobileParentName = parentRemote.DisplayName
|
||||
}
|
||||
last.WorksmobileParentEmail = parentRemote.Email
|
||||
last.WorksmobileParentExternalKey = parentRemote.ExternalID
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fillWorksmobileParentFromBaronParentMatch(item WorksmobileComparisonItem) WorksmobileComparisonItem {
|
||||
if item.WorksmobileParentID == "" || item.WorksmobileParentID != item.BaronParentWorksmobileID {
|
||||
return item
|
||||
}
|
||||
if item.WorksmobileParentName == "" {
|
||||
item.WorksmobileParentName = item.BaronParentWorksmobileName
|
||||
}
|
||||
if item.WorksmobileParentEmail == "" {
|
||||
item.WorksmobileParentEmail = item.BaronParentWorksmobileEmail
|
||||
}
|
||||
if item.WorksmobileParentExternalKey == "" {
|
||||
item.WorksmobileParentExternalKey = item.BaronParentID
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func worksmobileTenantByID(tenants []domain.Tenant) map[string]domain.Tenant {
|
||||
result := make(map[string]domain.Tenant, len(tenants))
|
||||
for _, tenant := range tenants {
|
||||
@@ -918,3 +1086,11 @@ func worksmobileTenantParentName(tenant domain.Tenant, tenantByID map[string]dom
|
||||
}
|
||||
return strings.TrimSpace(tenantByID[parentID].Name)
|
||||
}
|
||||
|
||||
func worksmobileTenantParentSlug(tenant domain.Tenant, tenantByID map[string]domain.Tenant) string {
|
||||
parentID := worksmobileTenantParentID(tenant)
|
||||
if parentID == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(tenantByID[parentID].Slug)
|
||||
}
|
||||
|
||||
@@ -166,10 +166,10 @@ func TestCompareWorksmobileGroupsUsesOrganizationsAndBarongroupChildCompanies(t
|
||||
[]domain.Tenant{root, hanmac, barongroup, barongroupChildCompany, organization, legacyUserGroup},
|
||||
[]WorksmobileRemoteGroup{
|
||||
{ID: "works-root", ExternalID: root.ID, DisplayName: root.Name},
|
||||
{ID: "works-hanmac", ExternalID: hanmac.ID, DisplayName: hanmac.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},
|
||||
{ID: "works-organization", ExternalID: organization.ID, DisplayName: organization.Name, ParentID: "works-hanmac"},
|
||||
{ID: "works-legacy-user-group", ExternalID: legacyUserGroup.ID, DisplayName: legacyUserGroup.Name},
|
||||
{ID: "works-orphan", ExternalID: "works-orphan", DisplayName: "WORKS 전용 조직"},
|
||||
},
|
||||
@@ -181,6 +181,13 @@ func TestCompareWorksmobileGroupsUsesOrganizationsAndBarongroupChildCompanies(t
|
||||
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, "works-orphan", items[2].ExternalKey)
|
||||
require.Equal(t, "missing_in_baron", items[2].Status)
|
||||
}
|
||||
@@ -304,6 +311,381 @@ func TestWorksmobileSyncServiceEnqueuesOrganizationOrgUnitSync(t *testing.T) {
|
||||
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 TestWorksmobileSyncServiceReconcilesWorksOnlyOrgUnitBySlugLocalPart(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.WorksmobileActionUpsert, outboxRepo.created[0].Action)
|
||||
require.Equal(t, orgID, outboxRepo.created[0].ResourceID)
|
||||
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
||||
require.Equal(t, orgID, request.OrgUnitExternalKey)
|
||||
require.Equal(t, "tech-dev-center", outboxRepo.created[0].Payload["matchLocalPart"])
|
||||
}
|
||||
|
||||
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.WorksmobileActionUpsert, outboxRepo.created[0].Action)
|
||||
require.Equal(t, orgID, outboxRepo.created[0].ResourceID)
|
||||
request := outboxRepo.created[0].Payload["request"].(WorksmobileOrgUnitPayload)
|
||||
require.Equal(t, orgID, request.OrgUnitExternalKey)
|
||||
require.Equal(t, "operations", outboxRepo.created[0].Payload["matchLocalPart"])
|
||||
}
|
||||
|
||||
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 TestWorksmobileSyncServiceTreatsHanmacFamilyChildCompaniesAsDomainRoots(t *testing.T) {
|
||||
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
||||
t.Setenv("HANMAC_DOMAIN_ID", "1002")
|
||||
t.Setenv("GPDTDC_DOMAIN_ID", "1003")
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
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 TestWorksmobileDomainClassificationUsesAncestorCompanyForGPDTDCOrganization(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user