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
35 changes: 10 additions & 25 deletions src/m365/connection/commands/connection-remove.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ 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 './connection-remove.js';
import command, { options } from './connection-remove.js';
import { CommandInfo } from '../../../cli/CommandInfo.js';
import { settingsNames } from '../../../settingsNames.js';
import { sinonUtil } from '../../../utils/sinonUtil.js';
import { CommandError } from '../../../Command.js';
import { cli } from '../../../cli/cli.js';
Expand All @@ -17,6 +16,7 @@ describe(commands.REMOVE, () => {
let log: string[];
let logger: Logger;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

before(() => {
sinon.stub(auth, 'clearConnectionInfo').resolves();
Expand All @@ -25,7 +25,7 @@ describe(commands.REMOVE, () => {
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
commandInfo = cli.getCommandInfo(command);

commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;

auth.connection.active = true;
auth.connection.authType = AuthType.DeviceCode;
Expand Down Expand Up @@ -97,7 +97,6 @@ describe(commands.REMOVE, () => {
sinonUtil.restore([
auth.ensureAccessToken,
auth.removeConnectionInfo,
cli.getSettingWithDefaultValue,
cli.promptForConfirmation,
cli.handleMultipleResultsFound
]);
Expand All @@ -116,30 +115,17 @@ describe(commands.REMOVE, () => {
assert.notStrictEqual(command.description, null);
});

it('fails validation if name is not specified', async () => {
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => {
if (settingName === settingsNames.prompt) {
return false;
}

return defaultValue;
});

const actual = await command.validate({ options: {} }, commandInfo);
assert.notStrictEqual(actual, true);
});

it(`fails with error if the connection cannot be found`, async () => {
sinon.stub(cli, 'promptForConfirmation').resolves(true);
await assert.rejects(command.action(logger, { options: { name: 'Non-existent connection' } }), new CommandError(`The connection 'Non-existent connection' cannot be found.`));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: 'Non-existent connection' }) }), new CommandError(`The connection 'Non-existent connection' cannot be found.`));
});

it('fails with error when restoring auth information leads to error', async () => {
sinonUtil.restore(auth.restoreAuth);
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.reject('An error has occurred'));

try {
await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('An error has occurred'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: 'test' }) }), new CommandError('An error has occurred'));
}
finally {
sinonUtil.restore(auth.restoreAuth);
Expand All @@ -149,25 +135,24 @@ describe(commands.REMOVE, () => {
it(`removes the 'Contoso Application' connection when prompt is confirmed`, async () => {
sinon.stub(cli, 'promptForConfirmation').resolves(true);
const removeStub = sinon.stub(auth, 'removeConnectionInfo').resolves();
await command.action(logger, { options: { name: 'acd6df42-10a9-4315-8928-53334f1c9d01' } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: 'acd6df42-10a9-4315-8928-53334f1c9d01' }) });
assert(removeStub.calledOnce);
});

it(`removes the 'Contoso Application' connection and not prompting for confirmation`, async () => {
const removeStub = sinon.stub(auth, 'removeConnectionInfo').resolves();
await command.action(logger, { options: { name: 'acd6df42-10a9-4315-8928-53334f1c9d01', force: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: 'acd6df42-10a9-4315-8928-53334f1c9d01', force: true }) });
assert(removeStub.calledOnce);
});


it('aborts removing the connection when prompt not confirmed', async () => {
sinon.stub(cli, 'promptForConfirmation').resolves(false);
const removeStub = sinon.stub(auth, 'removeConnectionInfo').resolves();

await command.action(logger, {
options: {
options: commandOptionsSchema.parse({
name: 'acd6df42-10a9-4315-8928-53334f1c9d01'
}
})
});
assert(removeStub.notCalled);
});
Expand All @@ -176,7 +161,7 @@ describe(commands.REMOVE, () => {
sinon.stub(cli, 'promptForConfirmation').resolves(true);
const removeStub = sinon.stub(auth, 'removeConnectionInfo').resolves();

await command.action(logger, { options: { name: '028de82d-7fd9-476e-a9fd-be9714280ff3', debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: '028de82d-7fd9-476e-a9fd-be9714280ff3', debug: true }) });
assert(removeStub.calledOnce);
});
});
50 changes: 12 additions & 38 deletions src/m365/connection/commands/connection-remove.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { z } from 'zod';
import { Logger } from '../../../cli/Logger.js';
import auth from '../../../Auth.js';
import commands from '../commands.js';
import Command, { CommandError } from '../../../Command.js';
import GlobalOptions from '../../../GlobalOptions.js';
import Command, { CommandError, globalOptionsZod } from '../../../Command.js';
import { cli } from '../../../cli/cli.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
name: z.string().alias('n'),
force: z.boolean().optional().alias('f')
});

declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
name: string;
force?: boolean;
}

class ConnectionRemoveCommand extends Command {
public get name(): string {
return commands.REMOVE;
Expand All @@ -23,37 +26,8 @@ class ConnectionRemoveCommand extends Command {
return 'Remove the specified connection';
}

constructor() {
super();

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


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

#initOptions(): void {
this.options.unshift(
{
option: '-n, --name <name>'
},
{
option: '-f, --force'
}
);
}

#initTypes(): void {
this.types.string.push('name');
this.types.boolean.push('force');
public get schema(): z.ZodType | undefined {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
24 changes: 14 additions & 10 deletions src/m365/connection/commands/connection-use.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ 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 './connection-use.js';
import { settingsNames } from '../../../settingsNames.js';
import command, { options } from './connection-use.js';
import { sinonUtil } from '../../../utils/sinonUtil.js';
import { CommandInfo } from '../../../cli/CommandInfo.js';
import { CommandError } from '../../../Command.js';
import { cli } from '../../../cli/cli.js';
import { ConnectionDetails } from '../../commands/ConnectionDetails.js';
Expand All @@ -17,6 +17,8 @@ describe(commands.USE, () => {
let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;
const mockContosoApplicationIdentityResponse = {
"connectedAs": "Contoso Application",
"connectionName": "acd6df42-10a9-4315-8928-53334f1c9d01",
Expand Down Expand Up @@ -80,7 +82,9 @@ describe(commands.USE, () => {
sinon.stub(telemetry, 'trackEvent').resolves();
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => settingName === settingsNames.prompt ? false : defaultValue);

commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;

auth.connection.active = true;
auth.connection.authType = AuthType.DeviceCode;
Expand Down Expand Up @@ -133,7 +137,7 @@ describe(commands.USE, () => {
});

it(`fails with error if the connection cannot be found`, async () => {
await assert.rejects(command.action(logger, { options: { name: 'Non-existent connection' } }),
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: 'Non-existent connection' }) }),
new CommandError(`The connection 'Non-existent connection' cannot be found.`));
});

Expand All @@ -142,40 +146,40 @@ describe(commands.USE, () => {
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.reject('An error has occurred'));

try {
await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('An error has occurred'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError('An error has occurred'));
}
finally {
sinonUtil.restore(auth.restoreAuth);
}
});

it(`switches to the 'Contoso Application' identity using the name option`, async () => {
await command.action(logger, { options: { name: 'acd6df42-10a9-4315-8928-53334f1c9d01' } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: 'acd6df42-10a9-4315-8928-53334f1c9d01' }) });
assert(loggerLogSpy.calledOnceWithExactly(mockContosoApplicationIdentityResponse));
});

it(`switches to the user identity using the name option`, async () => {
await command.action(logger, { options: { name: '028de82d-7fd9-476e-a9fd-be9714280ff3' } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: '028de82d-7fd9-476e-a9fd-be9714280ff3' }) });
assert(loggerLogSpy.calledOnceWithExactly(mockUserIdentityResponse));
});

it(`switches to the user identity using the name option (debug)`, async () => {
await command.action(logger, { options: { name: '028de82d-7fd9-476e-a9fd-be9714280ff3', debug: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ name: '028de82d-7fd9-476e-a9fd-be9714280ff3', debug: true }) });
const logged = loggerLogSpy.args[0][0] as unknown as ConnectionDetails;
assert.strictEqual(logged.connectedAs, mockUserIdentityResponse.connectedAs);
});

it('switches to the identity connection using prompting', async () => {
sinon.stub(cli, 'handleMultipleResultsFound').resolves(connections[1]);

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledOnceWithExactly(mockContosoApplicationIdentityResponse));
});

it(`switches to the user identity using prompting`, async () => {
sinon.stub(cli, 'handleMultipleResultsFound').resolves(connections[0]);

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(loggerLogSpy.calledOnceWithExactly(mockUserIdentityResponse));
});
});
43 changes: 11 additions & 32 deletions src/m365/connection/commands/connection-use.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { z } from 'zod';
import { Logger } from '../../../cli/Logger.js';
import auth, { Connection } from '../../../Auth.js';
import commands from '../commands.js';
import Command, { CommandError } from '../../../Command.js';
import GlobalOptions from '../../../GlobalOptions.js';
import Command, { CommandError, globalOptionsZod } from '../../../Command.js';
import { formatting } from '../../../utils/formatting.js';
import { cli } from '../../../cli/cli.js';

export const options = z.strictObject({
...globalOptionsZod.shape,
name: z.string().optional().alias('n')
});

declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
name?: string;
}

class ConnectionUseCommand extends Command {
public get name(): string {
return commands.USE;
Expand All @@ -23,32 +26,8 @@ class ConnectionUseCommand extends Command {
return 'Activate the specified Microsoft 365 tenant connection';
}

constructor() {
super();

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

#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
name: typeof args.options.name !== 'undefined'
});
});
}

#initOptions(): void {
this.options.unshift(
{
option: '-n, --name [name]'
}
);
}

#initTypes(): void {
this.types.string.push('name');
public get schema(): z.ZodType | undefined {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
Loading