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
7 changes: 7 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 25 additions & 9 deletions src/m365/context/commands/context-init.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import assert from 'assert';
import fs from 'fs';
import sinon from 'sinon';
import { cli } from '../../../cli/cli.js';
import { CommandInfo } from '../../../cli/CommandInfo.js';
import { Logger } from '../../../cli/Logger.js';
import { telemetry } from '../../../telemetry.js';
import { CommandError } from '../../../Command.js';
import { sinonUtil } from '../../../utils/sinonUtil.js';
import commands from '../commands.js';
import command from './context-init.js';
import command, { options } from './context-init.js';

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

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

beforeEach(() => {
Expand Down Expand Up @@ -51,6 +57,16 @@ describe(commands.INIT, () => {
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({ option: "value" });
assert.strictEqual(actual.success, false);
});

it('logs an error if an error occurred while reading the .m365rc.json', async () => {
const originalFsExistsSync = fs.existsSync;
const originalFsReadFileSync = fs.readFileSync;
Expand All @@ -72,7 +88,7 @@ describe(commands.INIT, () => {
}
});

await assert.rejects(command.action(logger, { options: { verbose: true } }), new CommandError('Error reading .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }), new CommandError('Error reading .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
});

it(`logs an error if the .m365rc.json file contents couldn't be parsed`, async () => {
Expand Down Expand Up @@ -104,7 +120,7 @@ describe(commands.INIT, () => {
errorMessage = err;
}

await assert.rejects(command.action(logger, { options: { verbose: true } }), new CommandError(`Error reading .m365rc.json: ${errorMessage}. Please add context info to .m365rc.json manually.`));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }), new CommandError(`Error reading .m365rc.json: ${errorMessage}. Please add context info to .m365rc.json manually.`));
});

it(`logs an error if the content can't be written to the .m365rc.json file`, async () => {
Expand All @@ -131,7 +147,7 @@ describe(commands.INIT, () => {
});
sinon.stub(fs, 'writeFileSync').callsFake(_ => { throw new Error('An error has occurred'); });

await assert.rejects(() => command.action(logger, { options: { verbose: true } }), new CommandError('Error writing .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
await assert.rejects(() => command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }), new CommandError('Error writing .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
});

it(`creates the .m365rc.json file if it doesn't exist and saves context info`, async () => {
Expand All @@ -147,7 +163,7 @@ describe(commands.INIT, () => {
});
const fsWriteFileSyncStub = sinon.stub(fs, 'writeFileSync').callsFake(() => { });

await command.action(logger, { options: { verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) });

assert(fsWriteFileSyncStub.calledWith('.m365rc.json', JSON.stringify({
context: {}
Expand Down Expand Up @@ -176,7 +192,7 @@ describe(commands.INIT, () => {
});
const fsWriteFileSyncStub = sinon.stub(fs, 'writeFileSync').callsFake(() => { });

await command.action(logger, { options: { verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) });

assert(fsWriteFileSyncStub.calledWith('.m365rc.json', JSON.stringify({
context: {}
Expand Down Expand Up @@ -207,7 +223,7 @@ describe(commands.INIT, () => {
});
const fsWriteFileSyncSpy = sinon.spy(fs, 'writeFileSync');

await command.action(logger, { options: { verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) });

assert(fsWriteFileSyncSpy.notCalled);
});
Expand Down Expand Up @@ -235,7 +251,7 @@ describe(commands.INIT, () => {
});
const fsWriteFileSyncSpy = sinon.spy(fs, 'writeFileSync');

await assert.rejects(command.action(logger, { options: { verbose: true } }), new CommandError('Error reading .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }), new CommandError('Error reading .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
assert(fsWriteFileSyncSpy.notCalled);
});

Expand All @@ -251,6 +267,6 @@ describe(commands.INIT, () => {
});
sinon.stub(fs, 'writeFileSync').callsFake(_ => { throw new Error('An error has occurred'); });

await assert.rejects(command.action(logger, { options: { verbose: true } }), new CommandError('Error writing .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ verbose: true }) }), new CommandError('Error writing .m365rc.json: Error: An error has occurred. Please add context info to .m365rc.json manually.'));
});
});
8 changes: 8 additions & 0 deletions src/m365/context/commands/context-init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { z } from 'zod';
import { globalOptionsZod } from '../../../Command.js';
import ContextCommand from '../../base/ContextCommand.js';
import commands from '../commands.js';

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

class ContextInitCommand extends ContextCommand {
public get name(): string {
return commands.INIT;
Expand All @@ -10,6 +14,10 @@ class ContextInitCommand extends ContextCommand {
return 'Initiates CLI for Microsoft 365 context in the current working folder';
}

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

public async commandAction(): Promise<void> {
await this.saveContextInfo({});
}
Expand Down
49 changes: 43 additions & 6 deletions src/m365/context/commands/option/option-set.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import assert from 'assert';
import fs from 'fs';
import sinon from 'sinon';
import { cli } from '../../../../cli/cli.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { Logger } from '../../../../cli/Logger.js';
import { CommandError } from '../../../../Command.js';
import { telemetry } from '../../../../telemetry.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
import command from './option-set.js';
import command, { options } from './option-set.js';

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

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

beforeEach(() => {
Expand Down Expand Up @@ -51,11 +57,42 @@ describe(commands.OPTION_SET, () => {
assert.notStrictEqual(command.description, null);
});

it('passes validation when name and value are specified', () => {
const actual = commandOptionsSchema.safeParse({
name: 'listName',
value: 'testList'
});
assert.strictEqual(actual.success, true);
});

it('fails validation when name is not specified', () => {
const actual = commandOptionsSchema.safeParse({
value: 'testList'
});
assert.strictEqual(actual.success, false);
});

it('fails validation when value is not specified', () => {
const actual = commandOptionsSchema.safeParse({
name: 'listName'
});
assert.strictEqual(actual.success, false);
});

it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({
name: 'listName',
value: 'testList',
unknown: 'option'
});
assert.strictEqual(actual.success, false);
});

it('handles an error when reading file contents fails', async () => {
sinon.stub(fs, 'existsSync').callsFake(_ => true);
sinon.stub(fs, 'readFileSync').callsFake(_ => { throw new Error('An error has occurred'); });

await assert.rejects(command.action(logger, { options: { debug: true, name: 'listName', value: 'testList' } }), new CommandError(`Error reading .m365rc.json: Error: An error has occurred. Please add listName to .m365rc.json manually.`));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, name: 'listName', value: 'testList' }) }), new CommandError(`Error reading .m365rc.json: Error: An error has occurred. Please add listName to .m365rc.json manually.`));
});

it('handles an error when writing file contents fails', async () => {
Expand All @@ -71,7 +108,7 @@ describe(commands.OPTION_SET, () => {
}));
sinon.stub(fs, 'writeFileSync').callsFake(_ => { throw new Error('An error has occurred'); });

await assert.rejects(command.action(logger, { options: { debug: true, name: 'listName', value: 'testList' } }), new CommandError(`Error writing .m365rc.json: Error: An error has occurred. Please add listName to .m365rc.json manually.`));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, name: 'listName', value: 'testList' }) }), new CommandError(`Error writing .m365rc.json: Error: An error has occurred. Please add listName to .m365rc.json manually.`));
});

it('adds a new key with value when context is present', async () => {
Expand All @@ -85,7 +122,7 @@ describe(commands.OPTION_SET, () => {
fileContents = contents as string;
});

await command.action(logger, { options: { verbose: true, name: 'listName', value: 'testList' } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, name: 'listName', value: 'testList' }) });
assert.strictEqual(filePath, '.m365rc.json');
assert.strictEqual(fileContents, JSON.stringify({
context: { listName: 'testList' }
Expand All @@ -101,7 +138,7 @@ describe(commands.OPTION_SET, () => {
filePath = _.toString();
fileContents = contents as string;
});
await assert.doesNotReject(command.action(logger, { options: { debug: true, name: 'listName', value: 'testList' } }));
await assert.doesNotReject(command.action(logger, { options: commandOptionsSchema.parse({ debug: true, name: 'listName', value: 'testList' }) }));
assert.strictEqual(filePath, '.m365rc.json');
assert.strictEqual(fileContents, JSON.stringify({
context: { listName: 'testList' }
Expand Down Expand Up @@ -129,7 +166,7 @@ describe(commands.OPTION_SET, () => {
fileContents = contents as string;
});

await command.action(logger, { options: { verbose: true, name: 'listName', value: 'testList' } });
await command.action(logger, { options: commandOptionsSchema.parse({ verbose: true, name: 'listName', value: 'testList' }) });
assert.strictEqual(filePath, '.m365rc.json');
assert.strictEqual(fileContents, JSON.stringify({
"apps": [
Expand Down
34 changes: 12 additions & 22 deletions src/m365/context/commands/option/option-set.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import fs from 'fs';
import { z } from 'zod';
import { Logger } from '../../../../cli/Logger.js';
import { CommandError } from '../../../../Command.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import { CommandError, globalOptionsZod } from '../../../../Command.js';
import ContextCommand from '../../../base/ContextCommand.js';
import { M365RcJson } from '../../../base/M365RcJson.js';
import commands from '../../commands.js';

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

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

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
name: string;
value: string;
}

class ContextOptionSetCommand extends ContextCommand {
public get name(): string {
return commands.OPTION_SET;
Expand All @@ -24,21 +27,8 @@ class ContextOptionSetCommand extends ContextCommand {
return 'Allows to add a new name for the option and value to the local context file.';
}

constructor() {
super();

this.#initOptions();
}

#initOptions(): void {
this.options.unshift(
{
option: '-n, --name <name>'
},
{
option: '-v, --value <value>'
}
);
public get schema(): z.ZodType | undefined {
return options;
}

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