forked from baron/baron-sso
feat: update worksmobile sync and restore planning
This commit is contained in:
@@ -113,6 +113,50 @@ func TestWorksmobileHTTPClientUpsertUserPatchesOnCreateConflictWithoutPasswordOr
|
||||
require.Equal(t, "user-1", patchPayload["userExternalKey"])
|
||||
}
|
||||
|
||||
func TestWorksmobileHTTPClientAddUserAliasEmailPostsDirectoryAliasEndpoint(t *testing.T) {
|
||||
transport := &captureRoundTripper{
|
||||
statusCode: http.StatusCreated,
|
||||
body: `{}`,
|
||||
}
|
||||
client := &WorksmobileHTTPClient{
|
||||
BaseURL: "https://works.example.test",
|
||||
DirectoryToken: "directory-token-1",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
}
|
||||
|
||||
err := client.AddUserAliasEmail(context.Background(), "ypshim@samaneng.com", "ypshim@hanmaceng.co.kr")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, transport.request)
|
||||
require.Equal(t, http.MethodPost, transport.request.Method)
|
||||
require.Equal(t, "/v1.0/users/ypshim@samaneng.com/alias-emails/ypshim@hanmaceng.co.kr", transport.request.URL.Path)
|
||||
require.Equal(t, "Bearer directory-token-1", transport.request.Header.Get("Authorization"))
|
||||
}
|
||||
|
||||
func TestWorksmobileHTTPClientResetUserPasswordPatchesPasswordConfig(t *testing.T) {
|
||||
transport := &captureRoundTripper{
|
||||
statusCode: http.StatusOK,
|
||||
body: `{}`,
|
||||
}
|
||||
client := &WorksmobileHTTPClient{
|
||||
BaseURL: "https://works.example.test",
|
||||
DirectoryToken: "directory-token-1",
|
||||
HTTPClient: &http.Client{Transport: transport},
|
||||
}
|
||||
|
||||
err := client.ResetUserPassword(context.Background(), "target@samaneng.com", "Aa1!Aa1!Aa1!Aa1!")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, transport.request)
|
||||
require.Equal(t, http.MethodPatch, transport.request.Method)
|
||||
require.Equal(t, "/v1.0/users/target@samaneng.com", transport.request.URL.Path)
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal(transport.requestBody, &payload))
|
||||
passwordConfig := payload["passwordConfig"].(map[string]any)
|
||||
require.Equal(t, "ADMIN", passwordConfig["passwordCreationType"])
|
||||
require.Equal(t, "Aa1!Aa1!Aa1!Aa1!", passwordConfig["password"])
|
||||
}
|
||||
|
||||
func TestWorksmobileHTTPClientCreateUserRequiresDirectoryToken(t *testing.T) {
|
||||
client := &WorksmobileHTTPClient{
|
||||
BaseURL: "https://works.example.test",
|
||||
@@ -472,6 +516,71 @@ func TestWorksmobileRelayWorkerProcessesUserCreateAndMarksProcessed(t *testing.T
|
||||
require.Equal(t, "tester@samaneng.com", client.createdUsers[0].Email)
|
||||
}
|
||||
|
||||
func TestWorksmobileRelayWorkerRegistersAliasEmailsAfterUserUpsert(t *testing.T) {
|
||||
repo := &fakeWorksmobileOutboxRepo{
|
||||
ready: []domain.WorksmobileOutbox{
|
||||
{
|
||||
ID: "job-1",
|
||||
ResourceType: domain.WorksmobileResourceUser,
|
||||
ResourceID: "user-1",
|
||||
Action: domain.WorksmobileActionUpsert,
|
||||
Status: domain.WorksmobileOutboxStatusPending,
|
||||
Payload: worksmobileUserOutboxPayload("root-1", WorksmobileUserPayload{
|
||||
Email: "ypshim@samaneng.com",
|
||||
UserExternalKey: "user-1",
|
||||
AliasEmails: []string{"ypshim@hanmaceng.co.kr"},
|
||||
PasswordConfig: WorksmobilePasswordConfig{
|
||||
PasswordCreationType: "ADMIN",
|
||||
Password: "Aa1!Aa1!Aa1!Aa1!",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
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, "ypshim@samaneng.com", client.createdUsers[0].Email)
|
||||
require.Empty(t, client.createdUsers[0].AliasEmails)
|
||||
require.Equal(t, []string{"ypshim@samaneng.com:ypshim@hanmaceng.co.kr"}, client.aliasEmails)
|
||||
}
|
||||
|
||||
func TestWorksmobileRelayWorkerProcessesUserPasswordResetAndMarksProcessed(t *testing.T) {
|
||||
repo := &fakeWorksmobileOutboxRepo{
|
||||
ready: []domain.WorksmobileOutbox{
|
||||
{
|
||||
ID: "job-reset",
|
||||
ResourceType: domain.WorksmobileResourceUser,
|
||||
ResourceID: "user-1",
|
||||
Action: domain.WorksmobileActionPasswordReset,
|
||||
Status: domain.WorksmobileOutboxStatusPending,
|
||||
Payload: domain.JSONMap{
|
||||
"loginEmail": "target@samaneng.com",
|
||||
"request": map[string]any{
|
||||
"email": "target@samaneng.com",
|
||||
"passwordConfig": map[string]any{
|
||||
"passwordCreationType": "ADMIN",
|
||||
"password": "Aa1!Aa1!Aa1!Aa1!",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
client := &fakeWorksmobileDirectoryClient{}
|
||||
worker := NewWorksmobileRelayWorker(repo, client)
|
||||
|
||||
err := worker.ProcessOnce(context.Background())
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"job-reset"}, repo.processedIDs)
|
||||
require.Equal(t, []string{"target@samaneng.com:Aa1!Aa1!Aa1!Aa1!"}, client.passwordResets)
|
||||
}
|
||||
|
||||
func TestWorksmobileRelayWorkerProcessesUserSuspendAndMarksProcessed(t *testing.T) {
|
||||
repo := &fakeWorksmobileOutboxRepo{
|
||||
ready: []domain.WorksmobileOutbox{
|
||||
@@ -615,7 +724,7 @@ func TestCompareWorksmobileUsersIncludesBaronAndWorksPrimaryOrg(t *testing.T) {
|
||||
require.Equal(t, "WORKS 기술기획", items[0].WorksmobilePrimaryOrgName)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileUsersMatchesByEmailWhenDirectoryAPIOmitsExternalID(t *testing.T) {
|
||||
func TestCompareWorksmobileUsersMarksEmailMatchWithoutExternalIDNeedsUpdate(t *testing.T) {
|
||||
localUsers := []domain.User{
|
||||
{ID: "user-1", Email: "tester@samaneng.com", Name: "Tester"},
|
||||
}
|
||||
@@ -626,13 +735,37 @@ func TestCompareWorksmobileUsersMatchesByEmailWhenDirectoryAPIOmitsExternalID(t
|
||||
diffOnly := compareWorksmobileUsers(localUsers, remoteUsers, false, nil)
|
||||
all := compareWorksmobileUsers(localUsers, remoteUsers, true, nil)
|
||||
|
||||
require.Empty(t, diffOnly)
|
||||
require.Len(t, diffOnly, 1)
|
||||
require.Equal(t, "needs_update", diffOnly[0].Status)
|
||||
require.Len(t, all, 1)
|
||||
require.Equal(t, "matched", all[0].Status)
|
||||
require.Equal(t, "needs_update", all[0].Status)
|
||||
require.Equal(t, "works-1", all[0].WorksmobileID)
|
||||
require.Empty(t, all[0].ExternalKey)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileUsersIncludesRecentFailedJobForMissingUser(t *testing.T) {
|
||||
localUsers := []domain.User{
|
||||
{ID: "user-1", Email: "missing@samaneng.com", Name: "Missing"},
|
||||
}
|
||||
jobSummaries := map[string]worksmobileUserJobSummary{
|
||||
"user-1": {
|
||||
Status: domain.WorksmobileOutboxStatusFailed,
|
||||
RetryCount: 3,
|
||||
LastError: "worksmobile api failed",
|
||||
LastAttemptAt: "2026-06-01T05:00:00Z",
|
||||
},
|
||||
}
|
||||
|
||||
items := compareWorksmobileUsers(localUsers, nil, false, nil, jobSummaries)
|
||||
|
||||
require.Len(t, items, 1)
|
||||
require.Equal(t, "missing_in_worksmobile", items[0].Status)
|
||||
require.Equal(t, domain.WorksmobileOutboxStatusFailed, items[0].WorksmobileJobStatus)
|
||||
require.Equal(t, 3, items[0].WorksmobileJobRetryCount)
|
||||
require.Equal(t, "worksmobile api failed", items[0].WorksmobileLastError)
|
||||
require.Equal(t, "2026-06-01T05:00:00Z", items[0].WorksmobileLastAttemptAt)
|
||||
}
|
||||
|
||||
func TestCompareWorksmobileUsersIncludesWorksOnlyRowsWithoutExternalIDWhenIncludingMatched(t *testing.T) {
|
||||
remoteUsers := []WorksmobileRemoteUser{
|
||||
{ID: "works-1", ExternalID: "", Email: "works-only@samaneng.com", DisplayName: "Works Only"},
|
||||
@@ -894,6 +1027,41 @@ func TestParseWorksmobileDirectoryUserIncludesFullNameLevelAndOrgRole(t *testing
|
||||
require.Equal(t, "팀장", user.PrimaryOrgUnitPositionName)
|
||||
require.NotNil(t, user.PrimaryOrgUnitIsManager)
|
||||
require.True(t, *user.PrimaryOrgUnitIsManager)
|
||||
require.NotNil(t, user.OrgUnitManagers["works-org-1"])
|
||||
require.True(t, *user.OrgUnitManagers["works-org-1"])
|
||||
}
|
||||
|
||||
func TestParseWorksmobileDirectoryUserIncludesAllOrgUnitManagerFlags(t *testing.T) {
|
||||
user := parseWorksmobileDirectoryUser(map[string]any{
|
||||
"userId": "works-user",
|
||||
"email": "tester@samaneng.com",
|
||||
"userName": map[string]any{
|
||||
"lastName": "홍길동",
|
||||
},
|
||||
"organizations": []any{
|
||||
map[string]any{
|
||||
"primary": true,
|
||||
"orgUnits": []any{
|
||||
map[string]any{
|
||||
"orgUnitId": "externalKey:primary-org",
|
||||
"primary": true,
|
||||
"isManager": false,
|
||||
},
|
||||
map[string]any{
|
||||
"orgUnitId": "externalKey:secondary-org",
|
||||
"primary": false,
|
||||
"isManager": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
require.Len(t, user.OrgUnitManagers, 2)
|
||||
require.NotNil(t, user.OrgUnitManagers["externalKey:primary-org"])
|
||||
require.False(t, *user.OrgUnitManagers["externalKey:primary-org"])
|
||||
require.NotNil(t, user.OrgUnitManagers["externalKey:secondary-org"])
|
||||
require.True(t, *user.OrgUnitManagers["externalKey:secondary-org"])
|
||||
}
|
||||
|
||||
func TestParseWorksmobileDirectoryGroupExtractsMailLocalPart(t *testing.T) {
|
||||
@@ -908,11 +1076,14 @@ func TestParseWorksmobileDirectoryGroupExtractsMailLocalPart(t *testing.T) {
|
||||
}
|
||||
|
||||
type fakeWorksmobileOutboxRepo struct {
|
||||
ready []domain.WorksmobileOutbox
|
||||
created []domain.WorksmobileOutbox
|
||||
processingIDs []string
|
||||
processedIDs []string
|
||||
failedIDs []string
|
||||
recent []domain.WorksmobileOutbox
|
||||
ready []domain.WorksmobileOutbox
|
||||
created []domain.WorksmobileOutbox
|
||||
credentialBatchJobs []domain.WorksmobileOutbox
|
||||
payloadUpdates []domain.JSONMap
|
||||
processingIDs []string
|
||||
processedIDs []string
|
||||
failedIDs []string
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileOutboxRepo) Create(ctx context.Context, item *domain.WorksmobileOutbox) error {
|
||||
@@ -921,7 +1092,31 @@ func (f *fakeWorksmobileOutboxRepo) Create(ctx context.Context, item *domain.Wor
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileOutboxRepo) ListRecent(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) {
|
||||
return nil, nil
|
||||
return f.recent, nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileOutboxRepo) ListCredentialBatchJobs(ctx context.Context, tenantRootID, credentialBatchID string) ([]domain.WorksmobileOutbox, error) {
|
||||
rows := make([]domain.WorksmobileOutbox, 0)
|
||||
for _, row := range f.credentialBatchJobs {
|
||||
if stringValue(row.Payload["tenantRootId"]) != tenantRootID {
|
||||
continue
|
||||
}
|
||||
if credentialBatchID != "" && stringValue(row.Payload["credentialBatchId"]) != credentialBatchID {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileOutboxRepo) UpdatePayload(ctx context.Context, id string, payload domain.JSONMap) error {
|
||||
f.payloadUpdates = append(f.payloadUpdates, payload)
|
||||
for i := range f.credentialBatchJobs {
|
||||
if f.credentialBatchJobs[i].ID == id {
|
||||
f.credentialBatchJobs[i].Payload = payload
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileOutboxRepo) ListReady(ctx context.Context, limit int) ([]domain.WorksmobileOutbox, error) {
|
||||
@@ -958,6 +1153,8 @@ type fakeWorksmobileDirectoryClient struct {
|
||||
deletedUsers []string
|
||||
activeUsers []string
|
||||
suspendedUsers []string
|
||||
aliasEmails []string
|
||||
passwordResets []string
|
||||
users []WorksmobileRemoteUser
|
||||
orgUnitMatchKeys []string
|
||||
groups []WorksmobileRemoteGroup
|
||||
@@ -1062,6 +1259,16 @@ func (f *fakeWorksmobileDirectoryClient) UpsertUser(ctx context.Context, payload
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) AddUserAliasEmail(ctx context.Context, userID string, email string) error {
|
||||
f.aliasEmails = append(f.aliasEmails, userID+":"+email)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) ResetUserPassword(ctx context.Context, userID string, password string) error {
|
||||
f.passwordResets = append(f.passwordResets, userID+":"+password)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeWorksmobileDirectoryClient) DeleteUser(ctx context.Context, userID string) error {
|
||||
f.deletedUsers = append(f.deletedUsers, userID)
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user