Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions docs/docs/cmd/viva/engage/engage-role-member-remove.mdx
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.
5 changes: 5 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4938,6 +4938,11 @@ const sidebars: SidebarsConfig = {
label: 'engage role member list',
id: 'cmd/viva/engage/engage-role-member-list'
},
{
type: 'doc',
label: 'engage role member remove',
id: 'cmd/viva/engage/engage-role-member-remove'
},
{
type: 'doc',
label: 'engage user get',
Expand Down
1 change: 1 addition & 0 deletions src/m365/viva/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default {
ENGAGE_REPORT_GROUPSACTIVITYGROUPCOUNTS: `${prefix} engage report groupsactivitygroupcounts`,
ENGAGE_ROLE_LIST: `${prefix} engage role list`,
ENGAGE_ROLE_MEMBER_LIST: `${prefix} engage role member list`,
ENGAGE_ROLE_MEMBER_REMOVE: `${prefix} engage role member remove`,
ENGAGE_SEARCH: `${prefix} engage search`,
ENGAGE_USER_GET: `${prefix} engage user get`,
ENGAGE_USER_LIST: `${prefix} engage user list`
Expand Down
253 changes: 253 additions & 0 deletions src/m365/viva/commands/engage/engage-role-member-remove.spec.ts
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}`) {
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}`) {
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}`) {
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}`) {
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);
});
});
Loading