-
Notifications
You must be signed in to change notification settings - Fork 379
Revokes a Viva Engage role from a user. Closes #6821 #7077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Jwaegebaert
merged 4 commits into
pnp:main
from
MartinM85:feature/6821-viva-engage-role-member-remove
Mar 9, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
1b67c6c
Revokes a Viva Engage role from a user. Closes #6821
MartinM85 791cf14
Revokes a Viva Engage role from a user. Closes #6821
MartinM85 e0017d9
Revokes a Viva Engage role from a user. Closes #6821
MartinM85 9aa714d
Revokes a Viva Engage role from a user. Closes #6821
MartinM85 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import Global from '../../_global.mdx'; | ||
| import Tabs from '@theme/Tabs'; | ||
| import TabItem from '@theme/TabItem'; | ||
|
|
||
| # viva engage role member remove | ||
|
|
||
| Removes a user from a Viva Engage role | ||
|
|
||
| ## Usage | ||
|
|
||
| ```sh | ||
| m365 viva engage role member remove [options] | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| ```md definition-list | ||
| `--roleId [roleId]` | ||
| : The id of the Viva Engage role. Specify either `roleId` or `roleName`, but not both. | ||
|
|
||
| `--roleName [roleName]` | ||
| : The name of the Viva Engage role. Specify either `roleId` or `roleName`, but not both. | ||
|
|
||
| `--userId [userId]` | ||
| : The Microsoft Entra ID of the user to remove from the specified Viva Engage role. Specify either `userId` or `userName`, but not both. | ||
|
|
||
| `--userName [userName]` | ||
| : The UPN of the user to remove from the specified Viva Engage role. Specify either `userId` or `userName`, but not both. | ||
|
|
||
| `--force` | ||
| : Do not prompt for confirmation | ||
| ``` | ||
|
|
||
| <Global /> | ||
|
|
||
| ## Permissions | ||
|
|
||
| <Tabs> | ||
| <TabItem value="Delegated"> | ||
|
|
||
| | Resource | Permissions | | ||
| |-----------------|------------------------------| | ||
| | Microsoft Graph | EngagementRole.ReadWrite.All | | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Application"> | ||
|
|
||
| | Resource | Permissions | | ||
| |-----------------|------------------------------| | ||
| | Microsoft Graph | EngagementRole.ReadWrite.All | | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
|
|
||
| ## Examples | ||
|
|
||
| Revoke a user specified by id from a Viva Engage role specified by a name | ||
|
|
||
| ```sh | ||
| m365 viva engage role member remove --userId 7a2ca997-9461-402e-9882-58088a370889 --roleName 'Verified Admin' | ||
| ``` | ||
|
|
||
| Revoke a user specified by UPN from a Viva Engage role specified by id and do not prompt for confirmation | ||
|
|
||
| ```sh | ||
| m365 viva engage role member remove --userName [email protected] --roleId 77aa47ad-96fe-4ecc-8024-fd1ac5e28f17 --force | ||
| ``` | ||
|
|
||
| ## Response | ||
|
|
||
| The command won't return a response on success. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
253 changes: 253 additions & 0 deletions
253
src/m365/viva/commands/engage/engage-role-member-remove.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| import assert from 'assert'; | ||
| import sinon from 'sinon'; | ||
| import auth from '../../../../Auth.js'; | ||
| import { Logger } from '../../../../cli/Logger.js'; | ||
| import { CommandError } from '../../../../Command.js'; | ||
| import request from '../../../../request.js'; | ||
| import { telemetry } from '../../../../telemetry.js'; | ||
| import { pid } from '../../../../utils/pid.js'; | ||
| import { session } from '../../../../utils/session.js'; | ||
| import { sinonUtil } from '../../../../utils/sinonUtil.js'; | ||
| import commands from '../../commands.js'; | ||
| import command, { options } from './engage-role-member-remove.js'; | ||
| import { CommandInfo } from '../../../../cli/CommandInfo.js'; | ||
| import { cli } from '../../../../cli/cli.js'; | ||
| import { vivaEngage } from '../../../../utils/vivaEngage.js'; | ||
| import { entraUser } from '../../../../utils/entraUser.js'; | ||
|
|
||
| describe(commands.ENGAGE_ROLE_MEMBER_REMOVE, () => { | ||
| const roleId = 'ec759127-089f-4f91-8dfc-03a30b51cb38'; | ||
| const roleName = 'Network Admin'; | ||
| const userId = 'a1b2c3d4-e5f6-4789-9012-3456789abcde'; | ||
| const userName = '[email protected]'; | ||
|
|
||
| let log: string[]; | ||
| let logger: Logger; | ||
| let commandInfo: CommandInfo; | ||
| let commandOptionsSchema: typeof options; | ||
| let promptIssued: boolean; | ||
|
|
||
| before(() => { | ||
| sinon.stub(auth, 'restoreAuth').resolves(); | ||
| sinon.stub(telemetry, 'trackEvent').resolves(); | ||
| sinon.stub(pid, 'getProcessName').returns(''); | ||
| sinon.stub(session, 'getId').returns(''); | ||
| auth.connection.active = true; | ||
| commandInfo = cli.getCommandInfo(command); | ||
| commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| log = []; | ||
| logger = { | ||
| log: async (msg: string) => { | ||
| log.push(msg); | ||
| }, | ||
| logRaw: async (msg: string) => { | ||
| log.push(msg); | ||
| }, | ||
| logToStderr: async (msg: string) => { | ||
| log.push(msg); | ||
| } | ||
| }; | ||
| sinon.stub(cli, 'promptForConfirmation').callsFake(() => { | ||
| promptIssued = true; | ||
| return Promise.resolve(false); | ||
| }); | ||
|
|
||
| promptIssued = false; | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| sinonUtil.restore([ | ||
| request.delete, | ||
| entraUser.getUserIdByUpn, | ||
| vivaEngage.getRoleIdByName, | ||
| cli.promptForConfirmation | ||
| ]); | ||
| }); | ||
|
|
||
| after(() => { | ||
| sinon.restore(); | ||
| auth.connection.active = false; | ||
| }); | ||
|
|
||
| it('has correct name', () => { | ||
| assert.strictEqual(command.name, commands.ENGAGE_ROLE_MEMBER_REMOVE); | ||
| }); | ||
|
|
||
| it('has a description', () => { | ||
| assert.notStrictEqual(command.description, null); | ||
| }); | ||
|
|
||
| it('fails validation if roleId is not a valid GUID', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: 'invalid', | ||
| userId: userId | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('passes validation if roleId is a valid GUID', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userId: userId | ||
| }); | ||
| assert.strictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('passes validation if roleName is specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleName: roleName, | ||
| userId: userId | ||
| }); | ||
| assert.strictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if both roleId and roleName are specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| roleName: roleName, | ||
| userId: userId | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if neither roleId nor roleName is specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| userId: userId | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if userId is not a valid GUID', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userId: 'invalid' | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('passes validation if userId is a valid GUID', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userId: userId | ||
| }); | ||
| assert.strictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if userName is not a valid UPN', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userName: 'invalid' | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('passes validation if userName is specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userName: userName | ||
| }); | ||
| assert.strictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if both userId and userName are specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId, | ||
| userId: userId, | ||
| userName: userName | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('fails validation if neither userId nor userName is specified', () => { | ||
| const actual = commandOptionsSchema.safeParse({ | ||
| roleId: roleId | ||
| }); | ||
| assert.notStrictEqual(actual.success, true); | ||
| }); | ||
|
|
||
| it('removes the user specified by id from Viva Engage role specified by id without prompting for confirmation', async () => { | ||
| const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => { | ||
| if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/roles/${roleId}/members/${userId}`) { | ||
Jwaegebaert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| throw 'Invalid request'; | ||
| }); | ||
|
|
||
| await command.action(logger, { options: commandOptionsSchema.parse({ roleId: roleId, userId: userId, force: true }) }); | ||
| assert(deleteRequestStub.called); | ||
| }); | ||
|
|
||
| it('removes the user specified by name from Viva Engage role specified by id while prompting for confirmation', async () => { | ||
| sinon.stub(entraUser, 'getUserIdByUpn').withArgs(userName).resolves(userId); | ||
|
|
||
| const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => { | ||
| if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/roles/${roleId}/members/${userId}`) { | ||
Jwaegebaert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| throw 'Invalid request'; | ||
| }); | ||
|
|
||
| sinonUtil.restore(cli.promptForConfirmation); | ||
| sinon.stub(cli, 'promptForConfirmation').resolves(true); | ||
|
|
||
| await command.action(logger, { options: commandOptionsSchema.parse({ roleId: roleId, userName: userName }) }); | ||
| assert(deleteRequestStub.called); | ||
| }); | ||
|
|
||
| it('removes the user specified by id from Viva Engage role specified by name while prompting for confirmation', async () => { | ||
| sinon.stub(vivaEngage, 'getRoleIdByName').withArgs(roleName).resolves(roleId); | ||
|
|
||
| const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => { | ||
| if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/roles/${roleId}/members/${userId}`) { | ||
Jwaegebaert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| throw 'Invalid request'; | ||
| }); | ||
|
|
||
| sinonUtil.restore(cli.promptForConfirmation); | ||
| sinon.stub(cli, 'promptForConfirmation').resolves(true); | ||
|
|
||
| await command.action(logger, { options: commandOptionsSchema.parse({ roleName: roleName, userId: userId }) }); | ||
| assert(deleteRequestStub.called); | ||
| }); | ||
|
|
||
| it('removes the user specified by name from Viva Engage role specified by name while prompting for confirmation', async () => { | ||
| sinon.stub(entraUser, 'getUserIdByUpn').withArgs(userName).resolves(userId); | ||
| sinon.stub(vivaEngage, 'getRoleIdByName').withArgs(roleName).resolves(roleId); | ||
|
|
||
| const deleteRequestStub = sinon.stub(request, 'delete').callsFake(async (opts) => { | ||
| if (opts.url === `https://graph.microsoft.com/v1.0/employeeExperience/roles/${roleId}/members/${userId}`) { | ||
Jwaegebaert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| throw 'Invalid request'; | ||
| }); | ||
|
|
||
| sinonUtil.restore(cli.promptForConfirmation); | ||
| sinon.stub(cli, 'promptForConfirmation').resolves(true); | ||
|
|
||
| await command.action(logger, { options: commandOptionsSchema.parse({ roleName: roleName, userName: userName, verbose: true }) }); | ||
| assert(deleteRequestStub.called); | ||
| }); | ||
|
|
||
| it('handles error when removing a user from a Viva Engage role failed', async () => { | ||
| sinon.stub(request, 'delete').rejects({ error: { message: 'An error has occurred' } }); | ||
|
|
||
| await assert.rejects( | ||
| command.action(logger, { options: commandOptionsSchema.parse({ roleId: roleId, userId: userId, force: true }) }), | ||
| new CommandError('An error has occurred') | ||
| ); | ||
| }); | ||
|
|
||
| it('prompts before removing a member from a Viva Engage role when confirm option not passed', async () => { | ||
| await command.action(logger, { options: { roleId: roleId, userId: userId } }); | ||
|
|
||
| assert(promptIssued); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.