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
133 changes: 50 additions & 83 deletions src/m365/external/commands/connection/connection-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ 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 './connection-add.js';
import command, { options } from './connection-add.js';

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

const externalConnectionAddResponse: ExternalConnectors.ExternalConnection = {
configuration: {
Expand Down Expand Up @@ -48,6 +49,7 @@ describe(commands.CONNECTION_ADD, () => {
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -158,102 +160,67 @@ describe(commands.CONNECTION_ADD, () => {
new CommandError(`An error has occurred`));
});

it('fails validation if id is less than 3 characters', async () => {
const actual = await command.validate({
options: {
id: 'T',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.notStrictEqual(actual, false);
});

it('fails validation if id is more than 32 characters', async () => {
const actual = await command.validate({
options: {
id: 'TestConnectionForCLIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.notStrictEqual(actual, false);
});

it('fails validation if id is not alphanumeric', async () => {
const actual = await command.validate({
options: {
id: 'Test_Connection!',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.notStrictEqual(actual, false);
it('fails validation if id is less than 3 characters', () => {
const actual = commandOptionsSchema.safeParse({
id: 'T',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert.strictEqual(actual.success, false);
});

it('fails validation if id starts with Microsoft', async () => {
const actual = await command.validate({
options: {
id: 'MicrosoftTestConnectionForCLI',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.notStrictEqual(actual, false);
it('fails validation if id is more than 32 characters', () => {
const actual = commandOptionsSchema.safeParse({
id: 'TestConnectionForCLIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert.strictEqual(actual.success, false);
});

it('fails validation if id is SharePoint', async () => {
const actual = await command.validate({
options: {
id: 'SharePoint',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.notStrictEqual(actual, false);
it('fails validation if id is not alphanumeric', () => {
const actual = commandOptionsSchema.safeParse({
id: 'Test_Connection!',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert.strictEqual(actual.success, false);
});

it('passes validation for a valid id', async () => {
const actual = await command.validate({
options: {
id: 'myapp',
name: 'Test Connection for CLI',
description: 'Test connection'
}
}, commandInfo);
assert.strictEqual(actual, true);
it('fails validation if id starts with Microsoft', () => {
const actual = commandOptionsSchema.safeParse({
id: 'MicrosoftTestConnectionForCLI',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert.strictEqual(actual.success, false);
});

it('supports specifying id', () => {
const options = command.options;
let containsOption = false;
options.forEach(o => {
if (o.option.indexOf('--id') > -1) {
containsOption = true;
}
it('fails validation if id is SharePoint', () => {
const actual = commandOptionsSchema.safeParse({
id: 'SharePoint',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert(containsOption);
assert.strictEqual(actual.success, false);
});

it('supports specifying name', () => {
const options = command.options;
let containsOption = false;
options.forEach(o => {
if (o.option.indexOf('--name') > -1) {
containsOption = true;
}
it('passes validation for a valid id', () => {
const actual = commandOptionsSchema.safeParse({
id: 'myapp',
name: 'Test Connection for CLI',
description: 'Test connection'
});
assert(containsOption);
assert.strictEqual(actual.success, true);
});

it('supports specifying description', () => {
const options = command.options;
let containsOption = false;
options.forEach(o => {
if (o.option.indexOf('--description') > -1) {
containsOption = true;
}
it('fails validation with unknown options', () => {
const actual = commandOptionsSchema.safeParse({
id: 'myapp',
name: 'Test Connection for CLI',
description: 'Test connection',
unknownOption: 'value'
});
assert(containsOption);
assert.strictEqual(actual.success, false);
});
});
129 changes: 44 additions & 85 deletions src/m365/external/commands/connection/connection-add.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,55 @@
import { ExternalConnectors } from '@microsoft/microsoft-graph-types/microsoft-graph';
import { z } from 'zod';
import { globalOptionsZod } from '../../../../Command.js';
import { Logger } from '../../../../cli/Logger.js';
import GlobalOptions from '../../../../GlobalOptions.js';
import request from '../../../../request.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';

const invalidIds: string[] = ['None',
'Directory',
'Exchange',
'ExchangeArchive',
'LinkedIn',
'Mailbox',
'OneDriveBusiness',
'SharePoint',
'Teams',
'Yammer',
'Connectors',
'TaskFabric',
'PowerBI',
'Assistant',
'TopicEngine',
'MSFT_All_Connectors'
];

export const options = z.strictObject({
...globalOptionsZod.shape,
id: z.string()
.min(3, 'ID must be between 3 and 32 characters in length.')
.max(32, 'ID must be between 3 and 32 characters in length.')
.refine(id => !/[^\w]|_/g.test(id), {
message: 'ID must only contain alphanumeric characters.'
})
.refine(id => !(id.length > 9 && id.startsWith('Microsoft')), {
message: 'ID cannot begin with Microsoft'
})
.refine(id => !invalidIds.includes(id), {
error: () => `ID cannot be one of the following values: ${invalidIds.join(', ')}.`
})
.alias('i'),
name: z.string().alias('n'),
description: z.string().alias('d'),
authorizedAppIds: z.string().optional()
});

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

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
id: string;
name: string;
description: string;
authorizedAppIds?: string;
}

class ExternalConnectionAddCommand extends GraphCommand {
public get name(): string {
return commands.CONNECTION_ADD;
Expand All @@ -29,83 +63,8 @@ class ExternalConnectionAddCommand extends GraphCommand {
return [commands.EXTERNALCONNECTION_ADD];
}

constructor() {
super();

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

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

#initOptions(): void {
this.options.unshift(
{
option: '-i, --id <id>'
},
{
option: '-n, --name <name>'
},
{
option: '-d, --description <description>'
},
{
option: '--authorizedAppIds [authorizedAppIds]'
}
);
}

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
const id = args.options.id;
if (id.length < 3 || id.length > 32) {
return 'ID must be between 3 and 32 characters in length.';
}

const alphaNumericRegEx = /[^\w]|_/g;

if (alphaNumericRegEx.test(id)) {
return 'ID must only contain alphanumeric characters.';
}

if (id.length > 9 &&
id.startsWith('Microsoft')) {
return 'ID cannot begin with Microsoft';
}

const invalidIds: string[] = ['None',
'Directory',
'Exchange',
'ExchangeArchive',
'LinkedIn',
'Mailbox',
'OneDriveBusiness',
'SharePoint',
'Teams',
'Yammer',
'Connectors',
'TaskFabric',
'PowerBI',
'Assistant',
'TopicEngine',
'MSFT_All_Connectors'
];

if (invalidIds.indexOf(id) > -1) {
return `ID cannot be one of the following values: ${invalidIds.join(', ')}.`;
}

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

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