forked from baron/baron-sso
feat: update worksmobile sync and restore planning
This commit is contained in:
@@ -4,11 +4,12 @@ import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorksmobileSyncServiceRejectsAliasLocalPartAlreadyUsedByOtherUser(t *testing.T) {
|
||||
func TestWorksmobileSyncServiceRejectsAliasEmailAlreadyUsedByOtherUser(t *testing.T) {
|
||||
t.Setenv("SAMAN_DOMAIN_ID", "1001")
|
||||
rootID := "root-tenant"
|
||||
tenantID := "saman-tenant"
|
||||
@@ -36,7 +37,7 @@ func TestWorksmobileSyncServiceRejectsAliasLocalPartAlreadyUsedByOtherUser(t *te
|
||||
}
|
||||
existing := domain.User{
|
||||
ID: "existing-user",
|
||||
Email: "used@samaneng.com",
|
||||
Email: "used@hanmaceng.co.kr",
|
||||
Name: "Existing",
|
||||
TenantID: &tenantID,
|
||||
}
|
||||
@@ -48,7 +49,7 @@ func TestWorksmobileSyncServiceRejectsAliasLocalPartAlreadyUsedByOtherUser(t *te
|
||||
nil,
|
||||
)
|
||||
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID)
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "")
|
||||
|
||||
require.Nil(t, item)
|
||||
require.Error(t, err)
|
||||
@@ -88,7 +89,7 @@ func TestWorksmobileSyncServiceEnqueuesSuspendedUserStatusWithOrganizations(t *t
|
||||
nil,
|
||||
)
|
||||
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID)
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, item)
|
||||
@@ -101,6 +102,253 @@ func TestWorksmobileSyncServiceEnqueuesSuspendedUserStatusWithOrganizations(t *t
|
||||
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")
|
||||
|
||||
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"])
|
||||
}
|
||||
|
||||
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 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"
|
||||
@@ -133,7 +381,7 @@ func TestWorksmobileSyncServiceDeprovisionsArchivedUser(t *testing.T) {
|
||||
nil,
|
||||
)
|
||||
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID)
|
||||
item, err := service.EnqueueUserSync(context.Background(), rootID, target.ID, "")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, item)
|
||||
@@ -1139,6 +1387,95 @@ func TestWorksmobileSyncServiceBackfillDryRunSkipsArchivedUsers(t *testing.T) {
|
||||
require.Equal(t, 1, outboxRepo.created[0].Payload["userCount"])
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileUsersMarksManagerChangeNeedsUpdate(t *testing.T) {
|
||||
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,
|
||||
}},
|
||||
true,
|
||||
map[string]domain.Tenant{
|
||||
tenantID: {ID: tenantID, Name: "Leaf", Type: domain.TenantTypeOrganization},
|
||||
},
|
||||
)
|
||||
|
||||
require.Len(t, items, 1)
|
||||
require.Equal(t, "needs_update", items[0].Status)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileUsersMarksSecondaryManagerChangeNeedsUpdate(t *testing.T) {
|
||||
primaryTenantID := "tenant-company"
|
||||
secondaryTenantID := "tenant-gpdtdc-leaf"
|
||||
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,
|
||||
},
|
||||
}},
|
||||
true,
|
||||
map[string]domain.Tenant{
|
||||
primaryTenantID: {ID: primaryTenantID, Name: "Company", Type: domain.TenantTypeCompany},
|
||||
secondaryTenantID: {ID: secondaryTenantID, Name: "GPDTDC Leaf", Type: domain.TenantTypeOrganization},
|
||||
},
|
||||
)
|
||||
|
||||
require.Len(t, items, 1)
|
||||
require.Equal(t, "needs_update", items[0].Status)
|
||||
}
|
||||
|
||||
type fakeWorksmobileTenantService struct {
|
||||
tenants map[string]domain.Tenant
|
||||
list []domain.Tenant
|
||||
|
||||
Reference in New Issue
Block a user