feat(platform): add Groups service for organization group management - #664
feat(platform): add Groups service for organization group management#664Sarath1018 wants to merge 1 commit into
Conversation
Adds PlatformGroupService (exported as Groups) to the /platform subpath: - getAll(organizationId) — all local and built-in groups - getById(groupId, organizationId) - create(name, organizationId, options?) — the API requires a client-generated group GUID, so the SDK generates it; initial members via memberUserIds - updateById(groupId, organizationId, name, options?) — the API requires the name on every update (409 without it, live-verified), so it is a required positional; membership edits via memberUserIdsToAdd/ memberUserIdsToRemove; the bound group.update() auto-fills the current name - deleteById(groupId, organizationId) - getMembers(groupId, organizationId, options?) — paged member references with fetch-all default Groups are enriched with organizationId (not on the wire) so bound update/delete/getMembers capture full context. Numeric GroupType codes mapped to enums; members/mappedRole/scope dropped from the public shape (members is always empty on the wire — membership is served by getMembers). Verified against the live API: 2389 unit tests passing, integration 8/8 (full CRUD, membership round-trips from both group and user side, pagination), runtime E2E through packed dist bundles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
|
||
| await expect(group.delete()).rejects.toThrow('Group ID is undefined'); | ||
| expect(mockService.deleteById).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
Missing organizationId validation test for the delete bound method. The implementation guards both groupData.id and groupData.organizationId, but only the first guard is exercised here. Convention: "Test both success and error scenarios for every public method."
| }); | |
| }); | |
| it('should throw when the organization ID is missing', async () => { | |
| const group = createPlatformGroupWithMethods(createTransformedGroup({ organizationId: '' }), mockService); | |
| await expect(group.delete()).rejects.toThrow('Group organization ID is undefined'); | |
| expect(mockService.deleteById).not.toHaveBeenCalled(); | |
| }); | |
| }); |
|
|
||
| await expect(group.getMembers()).rejects.toThrow('Group ID is undefined'); | ||
| expect(mockService.getMembers).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
Same gap as delete: getMembers guards organizationId too, but the test only covers the groupId check.
| }); | |
| }); | |
| it('should throw when the organization ID is missing', async () => { | |
| const group = createPlatformGroupWithMethods(createTransformedGroup({ organizationId: '' }), mockService); | |
| await expect(group.getMembers()).rejects.toThrow('Group organization ID is undefined'); | |
| expect(mockService.getMembers).not.toHaveBeenCalled(); | |
| }); | |
| }); |
|
Two missing error-scenario tests in the model test file; everything else looks good. New findings this run:
Both inline comments include a drop-in suggestion. Service-level and integration tests are complete; the transform pipeline, wire-name mapping, fetch-all loop, GUID generation, pagination, JSDoc, and docs pages are all convention-compliant. |
Summary
PR 2/4 in the platform RBAC stack (Users → Groups → Directory → Roles). Stacked on #663 — merge that first; this PR's diff is Groups-only.
Adds
PlatformGroupService(exported asGroups) to the/platformsubpath.getAll(organizationId)GET /identity_/api/Group/{org}getById(groupId, organizationId)GET /identity_/api/Group/{org}/{groupId}create(name, organizationId, options?)POST /identity_/api/GroupmemberUserIdsupdateById(groupId, organizationId, name, options?)PUT /identity_/api/Group/{groupId}nameis required by the API on every update (409 without it — live-verified); boundgroup.update()auto-fills the current namedeleteById(groupId, organizationId)DELETE /identity_/api/Group/{org}/{groupId}getMembers(groupId, organizationId, options?)GET .../Memberstop/skip); fetch-all defaultDesign decisions (validated against the live API)
organizationId(not on the wire) so boundupdate()/delete()/getMembers()capture full contextGroupTypearrives as0/1despite the spec's string enum — mapped toPlatformGroupType.BuiltIn/.Custommembers(always empty on the wire — membership served bygetMembers()),mappedRole,scope(undocumented nulls)directoryUserMemberIDs(create) vsdirectoryUserIDsToAdd/Remove(update); the SDK exposes uniformmemberUserIds*namesupdate/delete/getMembers(state-changing + contextual read); entry points not boundTesting
users.updateById+groupIdsToAdd— completes the RBAC grant/revoke flow), members pagination against a built-in groupDocs
docs/oauth-scopes.md(PM.Group),docs/pagination.md(getMembers),mkdocs.ymlnav.Stack
mainfeat/platform-usersfeat/platform-groupsfeat/platform-directory🤖 Generated with Claude Code