Skip to content

feat(identity): add updateById, deleteById and create to Users service#617

Draft
Sarath1018 wants to merge 2 commits into
feat/identity-users-getbyidfrom
feat/identity-users-crud
Draft

feat(identity): add updateById, deleteById and create to Users service#617
Sarath1018 wants to merge 2 commits into
feat/identity-users-getbyidfrom
feat/identity-users-crud

Conversation

@Sarath1018

Copy link
Copy Markdown
Collaborator

Summary

PR 2 of 3 splitting the Identity user management onboarding (stacked on #616; #609 is the final slice). Adds the write operations and bound entity methods:

SDK method Endpoint
updateById(userId, options) PUT /api/User/{userId}
deleteById(userId) DELETE /api/User/{userId}
create(users, organizationId, options?) POST /api/User/BulkCreate

getById() and create() now return users with bound entity methods (user.update(), user.delete()) via createUserWithMethods.

Design notes

  • Live-API findings encoded (Swagger spec is wrong on these): BulkCreate returns { result, users[] } with the full created users, not a bare IdentityResult; userName is required for create (email alone is rejected with InvalidUserName).
  • Outbound renames via transformRequest: groupIds→groupIDs, groupIdsToAdd/Remove→groupIDsToAdd/Remove.
  • organizationId is a required positional param on create() (SDK term for the API's partitionGlobalId).

Testing

  • Unit: 12 new service tests + 6 bound-method model tests (tests/unit/models/identity/)
  • Integration: 6/6 passing against live alpha — the suite now self-provisions its fixture user via create() and cleans up with deleteById() (replaces the transient IDENTITY_TEST_USER_ID env var from PR 1 with UIPATH_ORGANIZATION_ID)
  • typecheck ✓, oxlint ✓, full unit suite passing

Stack

  1. feat(identity): onboard Users service with getById #616 — service scaffold + getById
  2. This PRupdateById, deleteById, create + bound entity methods
  3. feat/identity-users (feat(identity): add invite to Users service #609) — invite

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://UiPath.github.io/uipath-typescript/pr-preview/pr-617/

Built to branch gh-pages at 2026-07-21 07:00 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

updateById: vi.fn(),
deleteById: vi.fn(),
create: vi.fn(),
invite: vi.fn(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invite is not part of UserServiceModel in this PR — it arrives in PR #609. Keeping it here is a dead mock that the conventions explicitly forbid:

NEVER leave unused mock methods in mock objects — dead mocks obscure what the test actually exercises and accumulate as the API evolves.

Suggested change
invite: vi.fn(),
};

* });
* ```
*/
updateById(userId: string, options: UserUpdateOptions): Promise<UserUpdateResponse>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per convention, {Entity}{Operation}Options bags — whose fields are all optional — must also have the parameter itself marked ?:

{Entity}{Operation}Options — bag of optional fields. Always the last parameter. Always marked ?. Contains only optional fields.

Same applies to UserMethods.update(options: UserUpdateOptions) at line 163, and the implementation signature in users.ts.

Suggested change
updateById(userId: string, options: UserUpdateOptions): Promise<UserUpdateResponse>;
updateById(userId: string, options?: UserUpdateOptions): Promise<UserUpdateResponse>;

IDENTITY_TEST_USER_ID=
# Organization GUID (from UiPath Cloud > Admin > Organization Settings)
# Required by the Identity Users tests (user create operations)
UIPATH_ORGANIZATION_ID=3aa10965-a82d-4d9e-8366-0eff8e87bf7a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A real organization GUID is committed to the example env file (same value also ends up in tests/utils/constants/users.ts). Example files are templates — this should be blank or use an obvious placeholder so no real tenant identifier is embedded in the public repo.

Suggested change
UIPATH_ORGANIZATION_ID=3aa10965-a82d-4d9e-8366-0eff8e87bf7a
UIPATH_ORGANIZATION_ID=

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review findings

Three issues found this run:

  1. Dead mock methodinvite: vi.fn() in tests/unit/models/identity/users.test.ts:19 references a method not yet on UserServiceModel in this slice. Violates the "never leave unused mock methods" rule.

  2. Options parameter should be ?updateById(userId, options: UserUpdateOptions) in users.models.ts:97 (and the matching UserMethods.update at line 163 + the users.ts implementation) must mark the options bag optional per convention: "{Entity}{Operation}Options — Always marked ?".

  3. Real org GUID in example env filetests/.env.integration.example:90 commits 3aa10965-a82d-4d9e-8366-0eff8e87bf7a (same value mirrored into tests/utils/constants/users.ts). Example files are templates; this should be blank or a placeholder.

@Sarath1018
Sarath1018 force-pushed the feat/identity-users-crud branch 2 times, most recently from 5e31db9 to 3e38f55 Compare July 21, 2026 04:48
*
* `result` reflects the request as a whole; `users` contains the created users.
*/
export interface UserCreateResponse {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per convention, response types must use type, not interface:

Always use type for response types (intersections, unions, composed types). The only place interface extends is required is single-type aliases (type X = Y), which break TypeDoc.

UserCreateResponse is a plain multi-field object type — it's not a single-type alias extending another type, so the TypeDoc exception doesn't apply.

Suggested change
export interface UserCreateResponse {
export type UserCreateResponse = {

and close with }; on line 30.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

One new issue found this run: UserCreateResponse should use type not interface in src/models/identity/users.models.ts line 25. Convention says to always use type for response types.

@Sarath1018
Sarath1018 force-pushed the feat/identity-users-crud branch from 3e38f55 to b94fcec Compare July 21, 2026 05:08
});

it('should throw when the user ID is undefined', async () => {
const user = createUserWithMethods(createBasicUser({ id: undefined as any }), mockService);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as any is unnecessary here. createBasicUser already accepts Partial<RawUserGetResponse>, and Partial<> makes every field optional, so id?: string | undefined — TypeScript accepts undefined without a cast. Convention: "No any type".

Same applies to line 69 (the user.delete() guard test).

Suggested change
const user = createUserWithMethods(createBasicUser({ id: undefined as any }), mockService);
const user = createUserWithMethods(createBasicUser({ id: undefined }), mockService);

}

/**
* Error entry from a failed user operation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention: "Always use type for response types." UserOperationError and UserOperationResult (line 87) both describe API response shapes and should be type, not interface.

(Note: UserUpdateResponse extends UserOperationResult {} is the correct TypeDoc single-alias exception — that pattern works whether UserOperationResult is an interface or a type.)

Suggested change
* Error entry from a failed user operation.
export type UserOperationError = {


// Organization (partition) GUID — required by create()
ORGANIZATION_ID: '3aa10965-a82d-4d9e-8366-0eff8e87bf7a',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same real production GUID noted in the open thread on tests/.env.integration.example:90. Unit test constants should use invented placeholder UUIDs (like USER_ID and GROUP_ID above), not real tenant/organization identifiers.

Suggested change
ORGANIZATION_ID: '00000000-0000-0000-0000-000000000001',

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review findings

Three new issues this run:

  1. Unnecessary as any casttests/unit/models/identity/users.test.ts:51 (and line 69). createBasicUser already takes Partial<RawUserGetResponse>, so id: undefined is valid without a cast. Violates the "No any type" rule.

  2. interface instead of type for response typessrc/models/identity/users.types.ts:77. UserOperationError (line 77) and UserOperationResult (line 87) describe API response shapes and should use type, per convention.

  3. Real production GUID in unit test constantstests/utils/constants/users.ts:11. ORGANIZATION_ID carries the same live tenant UUID called out in the open thread on .env.integration.example:90. Test constants should use invented placeholder UUIDs.

Sarath1018 and others added 2 commits July 21, 2026 12:29
Second slice of the Identity user management onboarding (follows the getById
scaffold PR):

- updateById(userId, options)            PUT    /api/User/{userId}
- deleteById(userId)                     DELETE /api/User/{userId}
- create(users, organizationId, opts?)   POST   /api/User/BulkCreate

Introduces bound entity methods (user.update(), user.delete()) attached to
getById() and create() results via createUserWithMethods, with model tests
for the delegation.

Live-API finding encoded: BulkCreate returns { result, users[] } with the
full created users, not the bare IdentityResult the Swagger spec declares;
userName is required (email alone is rejected).

The integration suite now self-provisions its fixture user via create() and
cleans up with deleteById() — the transient IDENTITY_TEST_USER_ID env var
from the previous slice is replaced by UIPATH_ORGANIZATION_ID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The identity suite now self-provisions its fixture user via create(), so the
IDENTITY_TEST_USER_ID mapping is replaced by the organization GUID.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Sarath1018
Sarath1018 force-pushed the feat/identity-users-crud branch from b94fcec to 7288fc2 Compare July 21, 2026 06:59
const response = await this.post<RawUserCreateResponse>(IDENTITY_USER_ENDPOINTS.BULK_CREATE, {
users,
partitionGlobalId: organizationId,
groupIDs: options?.groupIds,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention: Outbound requests (SDK → API) must use transformRequest(data, {Entity}Map) to reverse the field map — not inline manual renames. updateById in the same file does this correctly:

transformRequest(options, UserMap)

Here the groupIds → groupIDs rename is hard-coded directly. If the map ever changes (or additional options fields are added that need renaming) create() silently diverges.

Suggested change
groupIDs: options?.groupIds,
...transformRequest(options ?? {}, UserMap),

(remove groupIDs: options?.groupIds, on this line and spread the transform result instead)

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review findings

One new issue this run:

create() bypasses transformRequest for outbound mapping (src/services/identity/users.ts:71) — groupIDs: options?.groupIds hard-codes the API field name inline. updateById in the same file correctly delegates to transformRequest(options, UserMap); create should do the same via ...transformRequest(options ?? {}, UserMap) so any future additions to UserMap are picked up automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant