Skip to content
Open
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
28 changes: 20 additions & 8 deletions src/m365/cli/commands/cli-consent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ import { telemetry } from '../../../telemetry.js';
import { pid } from '../../../utils/pid.js';
import { session } from '../../../utils/session.js';
import commands from '../commands.js';
import command from './cli-consent.js';
import command, { options } from './cli-consent.js';
import { sinonUtil } from '../../../utils/sinonUtil.js';

describe(commands.CONSENT, () => {
let log: any[];
let logger: Logger;
let loggerLogSpy: any;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

before(() => {
sinon.stub(telemetry, 'trackEvent').resolves();
sinon.stub(pid, 'getProcessName').callsFake(() => '');
sinon.stub(session, 'getId').callsFake(() => '');
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -62,17 +64,27 @@ describe(commands.CONSENT, () => {
it('shows consent URL for VivaEngage permissions for a custom single-tenant app', async () => {
sinon.stub(cli, 'getTenant').returns('fb5cb38f-ecdb-4c6a-a93b-b8cfd56b4a89');
sinon.stub(cli, 'getClientId').returns('2587b55d-a41e-436d-bb1d-6223eb185dd4');
await command.action(logger, { options: { service: 'VivaEngage' } });
await command.action(logger, { options: commandOptionsSchema.parse({ service: 'VivaEngage' }) });
assert(loggerLogSpy.calledWith(`To consent permissions for executing VivaEngage commands, navigate in your web browser to https://login.microsoftonline.com/fb5cb38f-ecdb-4c6a-a93b-b8cfd56b4a89/oauth2/v2.0/authorize?client_id=2587b55d-a41e-436d-bb1d-6223eb185dd4&response_type=code&scope=https%3A%2F%2Fapi.yammer.com%2Fuser_impersonation`));
});

it('fails validation if specified service is invalid ', async () => {
const actual = await command.validate({ options: { service: 'invalid' } }, commandInfo);
assert.notStrictEqual(actual, true);
it('fails validation with no options', () => {
const actual = commandOptionsSchema.safeParse({});
assert.notStrictEqual(actual.success, true);
});

it('passes validation if service is set to VivaEngage ', async () => {
const actual = await command.validate({ options: { service: 'VivaEngage' } }, commandInfo);
assert.strictEqual(actual, true);
it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({ service: 'VivaEngage', unknownOption: 'value' });
assert.notStrictEqual(actual.success, true);
});

it('fails validation if specified service is invalid ', () => {
const actual = commandOptionsSchema.safeParse({ service: 'invalid' });
assert.notStrictEqual(actual.success, true);
});

it('passes validation if service is set to VivaEngage ', () => {
const actual = commandOptionsSchema.safeParse({ service: 'VivaEngage' });
assert.strictEqual(actual.success, true);
});
});
50 changes: 10 additions & 40 deletions src/m365/cli/commands/cli-consent.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { z } from 'zod';
import { cli } from '../../../cli/cli.js';
import { Logger } from '../../../cli/Logger.js';
import GlobalOptions from '../../../GlobalOptions.js';
import { globalOptionsZod } from '../../../Command.js';
import AnonymousCommand from '../../base/AnonymousCommand.js';
import commands from '../commands.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
service: z.enum(['VivaEngage']).alias('s')
});
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
service: string;
}

class CliConsentCommand extends AnonymousCommand {
public get name(): string {
return commands.CONSENT;
Expand All @@ -21,41 +24,8 @@ class CliConsentCommand extends AnonymousCommand {
return 'Consent additional permissions for the Microsoft Entra application used by the CLI for Microsoft 365';
}

constructor() {
super();

this.#initTelemetry();
this.#initOptions();
this.#initValidators();
}

#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
service: args.options.service
});
});
}

#initOptions(): void {
this.options.unshift(
{
option: '-s, --service <service>',
autocomplete: ['VivaEngage']
}
);
}

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (args.options.service !== 'VivaEngage') {
return `${args.options.service} is not a valid value for the service option. Allowed values: VivaEngage`;
}

return true;
}
);
public get schema(): z.ZodType | undefined {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
41 changes: 27 additions & 14 deletions src/m365/cli/commands/cli-doctor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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 from './cli-doctor.js';
import command, { options } from './cli-doctor.js';

const require = createRequire(import.meta.url);
const packageJSON = require('../../../../package.json');
Expand All @@ -19,6 +19,7 @@ describe(commands.DOCTOR, () => {
let log: any[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandOptionsSchema: typeof options;

before(() => {
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
Expand All @@ -27,6 +28,8 @@ describe(commands.DOCTOR, () => {
sinon.stub(session, 'getId').callsFake(() => '');
auth.connection.active = true;
sinon.stub(cli.getConfig(), 'all').value({});
const commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -67,6 +70,16 @@ describe(commands.DOCTOR, () => {
assert.notStrictEqual(command.description, null);
});

it('passes validation with no options', () => {
const actual = commandOptionsSchema.safeParse({});
assert.strictEqual(actual.success, true);
});

it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({ unknownOption: 'value' });
assert.notStrictEqual(actual.success, true);
});

it('retrieves scopes in the diagnostic information about the current environment', async () => {
const jwt = JSON.stringify({
aud: 'https://graph.microsoft.com',
Expand All @@ -90,7 +103,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -163,7 +176,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -210,7 +223,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -254,7 +267,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -291,7 +304,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -335,7 +348,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.DeviceCode);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -371,7 +384,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -407,7 +420,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: { debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -443,7 +456,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: { debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -479,7 +492,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': 'docker' });

await command.action(logger, { options: { debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -508,7 +521,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: { debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -538,7 +551,7 @@ describe(commands.DOCTOR, () => {
sinon.stub(auth.connection, 'authType').value(AuthType.Certificate);
sinon.stub(process, 'env').value({ 'CLIMICROSOFT365_ENV': '' });

await command.action(logger, { options: { debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true }) });
assert(loggerLogSpy.calledWith({
authMode: 'certificate',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down Expand Up @@ -576,7 +589,7 @@ describe(commands.DOCTOR, () => {
sinonUtil.restore(cli.getConfig().all);
sinon.stub(cli.getConfig(), 'all').value({ "showHelpOnFailure": false });

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledWith({
authMode: 'deviceCode',
cliEntraAppId: '31359c7f-bd7e-475c-86db-fdb8c937548e',
Expand Down
9 changes: 8 additions & 1 deletion src/m365/cli/commands/cli-doctor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import os from 'os';
import { z } from 'zod';
import auth from '../../../Auth.js';
import { cli } from '../../../cli/cli.js';
import { Logger } from '../../../cli/Logger.js';
import Command from '../../../Command.js';
import Command, { globalOptionsZod } from '../../../Command.js';
import { app } from '../../../utils/app.js';
import { validation } from '../../../utils/validation.js';
import commands from '../commands.js';

export const options = z.strictObject({ ...globalOptionsZod.shape });

interface CliDiagnosticInfo {
os: {
platform: string;
Expand All @@ -33,6 +36,10 @@ class CliDoctorCommand extends Command {
return 'Retrieves diagnostic information about the current environment';
}

public get schema(): z.ZodType | undefined {
return options;
}

public async commandAction(logger: Logger): Promise<void> {
const roles: string[] = [];
const scopes: Map<string, string[]> = new Map<string, string[]>();
Expand Down
Loading
Loading