Skip to content

Commit 4dd3e23

Browse files
authored
Add user patch batch (#586)
* Add status to patch request * Add user patch batch
1 parent 5c8c461 commit 4dd3e23

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

lib/management/paths.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
createBatch: '/v1/mgmt/user/create/batch',
77
update: '/v1/mgmt/user/update',
88
patch: '/v1/mgmt/user/patch',
9+
patchBatch: '/v1/mgmt/user/patch/batch',
910
delete: '/v1/mgmt/user/delete',
1011
deleteBatch: '/v1/mgmt/user/delete/batch',
1112
deleteAllTestUsers: '/v1/mgmt/user/test/delete/all',

lib/management/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ export type CreateOrInviteBatchResponse = {
565565
additionalErrors: Record<string, string>;
566566
};
567567

568+
export type PatchUserBatchResponse = {
569+
patchedUsers: UserResponse[];
570+
failedUsers: UserFailedResponse[];
571+
additionalErrors: Record<string, string>;
572+
};
573+
568574
/**
569575
* Search options to filter which audit records we should retrieve.
570576
* All parameters are optional. `From` is currently limited to 30 days.

lib/management/user.test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ProviderTokenResponse,
1010
GenerateEmbeddedLinkResponse,
1111
CreateOrInviteBatchResponse,
12+
PatchUserBatchResponse,
1213
UserPasswordHashed,
1314
} from './types';
1415

@@ -36,6 +37,17 @@ const mockMgmtInviteBatchResponse = {
3637
],
3738
};
3839

40+
const mockMgmtPatchBatchResponse = {
41+
patchedUsers: [{ loginId: 'user1', email: '[email protected]' }],
42+
failedUsers: [
43+
{
44+
failure: 'user not found',
45+
user: { loginId: 'user2', email: '[email protected]' },
46+
},
47+
],
48+
additionalErrors: { user3: 'invalid email format' },
49+
};
50+
3951
describe('Management User', () => {
4052
afterEach(() => {
4153
jest.clearAllMocks();
@@ -575,6 +587,7 @@ describe('Management User', () => {
575587
verifiedPhone: false,
576588
scim: true,
577589
ssoAppIds: ['sso1', 'sso2'],
590+
status: 'invited',
578591
});
579592

580593
expect(mockHttpClient.patch).toHaveBeenCalledWith(apiPaths.user.patch, {
@@ -587,6 +600,120 @@ describe('Management User', () => {
587600
verifiedPhone: false,
588601
ssoAppIds: ['sso1', 'sso2'],
589602
scim: true,
603+
status: 'invited',
604+
});
605+
});
606+
});
607+
608+
describe('patchBatch', () => {
609+
it('should send the correct request and receive correct response', async () => {
610+
const httpResponse = {
611+
ok: true,
612+
json: () => mockMgmtPatchBatchResponse,
613+
clone: () => ({
614+
json: () => Promise.resolve(mockMgmtPatchBatchResponse),
615+
}),
616+
status: 200,
617+
};
618+
mockHttpClient.patch.mockResolvedValue(httpResponse);
619+
const resp: SdkResponse<PatchUserBatchResponse> = await management.user.patchBatch([
620+
{
621+
loginId: 'user1',
622+
623+
displayName: 'User One',
624+
roles: ['role1'],
625+
},
626+
{
627+
loginId: 'user2',
628+
phone: '+1234567890',
629+
verifiedPhone: true,
630+
customAttributes: { department: 'engineering' },
631+
},
632+
{
633+
loginId: 'user3',
634+
status: 'disabled',
635+
},
636+
]);
637+
expect(mockHttpClient.patch).toHaveBeenCalledWith(apiPaths.user.patchBatch, {
638+
users: [
639+
{
640+
loginId: 'user1',
641+
642+
displayName: 'User One',
643+
roleNames: ['role1'],
644+
},
645+
{
646+
loginId: 'user2',
647+
phone: '+1234567890',
648+
verifiedPhone: true,
649+
customAttributes: { department: 'engineering' },
650+
},
651+
{
652+
loginId: 'user3',
653+
status: 'disabled',
654+
},
655+
],
656+
});
657+
expect(resp).toEqual({
658+
code: 200,
659+
data: mockMgmtPatchBatchResponse,
660+
ok: true,
661+
response: httpResponse,
662+
});
663+
});
664+
665+
it('should handle empty user array', async () => {
666+
const httpResponse = {
667+
ok: true,
668+
json: () => ({ patchedUsers: [], failedUsers: [], additionalErrors: {} }),
669+
clone: () => ({
670+
json: () => Promise.resolve({ patchedUsers: [], failedUsers: [], additionalErrors: {} }),
671+
}),
672+
status: 200,
673+
};
674+
mockHttpClient.patch.mockResolvedValue(httpResponse);
675+
const resp: SdkResponse<PatchUserBatchResponse> = await management.user.patchBatch([]);
676+
expect(mockHttpClient.patch).toHaveBeenCalledWith(apiPaths.user.patchBatch, {
677+
users: [],
678+
});
679+
expect(resp).toEqual({
680+
code: 200,
681+
data: { patchedUsers: [], failedUsers: [], additionalErrors: {} },
682+
ok: true,
683+
response: httpResponse,
684+
});
685+
});
686+
687+
it('should only include defined fields in patch request', async () => {
688+
const httpResponse = {
689+
ok: true,
690+
json: () => mockMgmtPatchBatchResponse,
691+
clone: () => ({
692+
json: () => Promise.resolve(mockMgmtPatchBatchResponse),
693+
}),
694+
status: 200,
695+
};
696+
mockHttpClient.patch.mockResolvedValue(httpResponse);
697+
await management.user.patchBatch([
698+
{
699+
loginId: 'user1',
700+
701+
phone: undefined,
702+
displayName: 'Updated Name',
703+
roles: undefined,
704+
verifiedEmail: true,
705+
verifiedPhone: undefined,
706+
},
707+
]);
708+
expect(mockHttpClient.patch).toHaveBeenCalledWith(apiPaths.user.patchBatch, {
709+
users: [
710+
{
711+
loginId: 'user1',
712+
713+
displayName: 'Updated Name',
714+
verifiedEmail: true,
715+
},
716+
],
590717
});
591718
});
592719
});

lib/management/user.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
UserStatus,
1818
User,
1919
CreateOrInviteBatchResponse,
20+
PatchUserBatchResponse,
2021
TemplateOptions,
2122
ProviderTokenOptions,
2223
UserOptions,
@@ -373,11 +374,11 @@ const withUser = (httpClient: HttpClient) => {
373374
/* Update User End */
374375

375376
/**
376-
* Patches an existing user.
377+
* Helper function to build patch request body from options
377378
* @param loginId The login ID of the user
378-
* @param options The fields to update. Only the provided ones will be updated.
379+
* @param options The fields to update
379380
*/
380-
function patch(loginId: string, options: PatchUserOptions): Promise<SdkResponse<UserResponse>> {
381+
function buildPatchRequestBody(loginId: string, options: PatchUserOptions): any {
381382
const body = {
382383
loginId,
383384
} as any;
@@ -424,13 +425,44 @@ const withUser = (httpClient: HttpClient) => {
424425
if (options.scim !== undefined) {
425426
body.scim = options.scim;
426427
}
428+
if (options.status !== undefined) {
429+
body.status = options.status;
430+
}
431+
432+
return body;
433+
}
434+
435+
/**
436+
* Patches an existing user.
437+
* @param loginId The login ID of the user
438+
* @param options The fields to update. Only the provided ones will be updated.
439+
*/
440+
function patch(loginId: string, options: PatchUserOptions): Promise<SdkResponse<UserResponse>> {
441+
const body = buildPatchRequestBody(loginId, options);
427442

428443
return transformResponse<SingleUserResponse, UserResponse>(
429444
httpClient.patch(apiPaths.user.patch, body),
430445
(data) => data.user,
431446
);
432447
}
433448

449+
/**
450+
* Patches multiple users in batch.
451+
* @param users Array of patch requests, each containing loginId and the fields to update
452+
*/
453+
function patchBatch(
454+
users: Array<{ loginId: string } & PatchUserOptions>,
455+
): Promise<SdkResponse<PatchUserBatchResponse>> {
456+
const body = {
457+
users: users.map((user) => buildPatchRequestBody(user.loginId, user)),
458+
};
459+
460+
return transformResponse<PatchUserBatchResponse, PatchUserBatchResponse>(
461+
httpClient.patch(apiPaths.user.patchBatch, body),
462+
(data) => data,
463+
);
464+
}
465+
434466
return {
435467
create,
436468
/**
@@ -476,6 +508,7 @@ const withUser = (httpClient: HttpClient) => {
476508
transformResponse(httpClient.post(apiPaths.user.deleteBatch, { userIds })),
477509
update,
478510
patch,
511+
patchBatch,
479512
/**
480513
* Delete an existing user.
481514
* @param loginId The login ID of the user
@@ -968,6 +1001,7 @@ export interface PatchUserOptions {
9681001
familyName?: string;
9691002
ssoAppIds?: string[];
9701003
scim?: boolean;
1004+
status?: UserStatus;
9711005
}
9721006

9731007
export default withUser;

0 commit comments

Comments
 (0)