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
14 changes: 9 additions & 5 deletions src/m365/entra/commands/multitenant/multitenant-get.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import request from '../../../../request.js';
import { cli } from '../../../../cli/cli.js';
import { Logger } from '../../../../cli/Logger.js';
import commands from '../../commands.js';
import command, { options } from './multitenant-get.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
import { Logger } from '../../../../cli/Logger.js';
import request from '../../../../request.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import command from './multitenant-get.js';
import { CommandError } from '../../../../Command.js';

describe(commands.MULTITENANT_GET, () => {
let log: string[];
let logger: Logger;
let loggerLogSpy: sinon.SinonSpy;
let commandOptionsSchema: typeof options;
const response = {
"id": "ab217953-e37f-4691-97b8-dbb8a0a3bcaf",
"createdDateTime": "2024-05-05T05:05:05",
Expand All @@ -29,6 +31,8 @@ describe(commands.MULTITENANT_GET, () => {
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
const commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -75,7 +79,7 @@ describe(commands.MULTITENANT_GET, () => {
throw 'Invalid request';
});

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

Expand All @@ -93,6 +97,6 @@ describe(commands.MULTITENANT_GET, () => {
};
sinon.stub(request, 'get').rejects(error);

await assert.rejects(command.action(logger, { options: {} }), new CommandError(error.error.message));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({}) }), new CommandError(error.error.message));
});
});
17 changes: 16 additions & 1 deletion src/m365/entra/commands/multitenant/multitenant-get.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { z } from 'zod';
import { globalOptionsZod } from '../../../../Command.js';
import { Logger } from '../../../../cli/Logger.js';
import request, { CliRequestOptions } from '../../../../request.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';
import { MultitenantOrganization } from './MultitenantOrganization.js';

export const options = globalOptionsZod
.extend({})
.strict();
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

class EntraMultitenantGetCommand extends GraphCommand {
public get name(): string {
return commands.MULTITENANT_GET;
Expand All @@ -13,7 +24,11 @@ class EntraMultitenantGetCommand extends GraphCommand {
return 'Gets properties of the multitenant organization';
}

public async commandAction(logger: Logger): Promise<void> {
public get schema(): z.ZodTypeAny | undefined {
return options;
}

public async commandAction(logger: Logger, _args: CommandArgs): Promise<void> {

const requestOptions: CliRequestOptions = {
url: `${this.resource}/v1.0/tenantRelationships/multiTenantOrganization`,
Expand Down
15 changes: 9 additions & 6 deletions src/m365/entra/commands/multitenant/multitenant-remove.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import commands from '../../commands.js';
import { cli } from '../../../../cli/cli.js';
import request from '../../../../request.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import command from './multitenant-remove.js';
import command, { options } from './multitenant-remove.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
Expand All @@ -28,13 +28,16 @@ describe(commands.MULTITENANT_REMOVE, () => {
let log: string[];
let logger: Logger;
let promptIssued: boolean;
let commandOptionsSchema: typeof options;

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;
const commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -121,7 +124,7 @@ describe(commands.MULTITENANT_REMOVE, () => {
return {} as any;
});

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

Expand Down Expand Up @@ -167,20 +170,20 @@ describe(commands.MULTITENANT_REMOVE, () => {
sinonUtil.restore(cli.promptForConfirmation);
sinon.stub(cli, 'promptForConfirmation').resolves(true);

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(deleteRequestStub.calledTwice);
});

it('prompts before removing the multitenant organization when prompt option not passed', async () => {
await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });

assert(promptIssued);
});

it('aborts removing the multitenant organization when prompt not confirmed', async () => {
const deleteSpy = sinon.stub(request, 'delete').resolves();

await command.action(logger, { options: {} });
await command.action(logger, { options: commandOptionsSchema.parse({}) });
assert(deleteSpy.notCalled);
});

Expand Down Expand Up @@ -223,7 +226,7 @@ describe(commands.MULTITENANT_REMOVE, () => {
throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { force: true } }),
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ force: true }) }),
new CommandError(error.error.message));
});
});
36 changes: 10 additions & 26 deletions src/m365/entra/commands/multitenant/multitenant-remove.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import GlobalOptions from '../../../../GlobalOptions.js';
import { z } from 'zod';
import { globalOptionsZod } from '../../../../Command.js';
import { Organization } from '@microsoft/microsoft-graph-types';
import { Logger } from '../../../../cli/Logger.js';
import request, { CliRequestOptions } from '../../../../request.js';
Expand All @@ -11,14 +12,16 @@ interface MultitenantOrganizationMember {
tenantId?: string;
}

export const options = globalOptionsZod
.extend({
force: z.boolean().optional().alias('f')
}).strict();
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
force?: boolean;
}

class EntraMultitenantRemoveCommand extends GraphCommand {
public get name(): string {
return commands.MULTITENANT_REMOVE;
Expand All @@ -28,27 +31,8 @@ class EntraMultitenantRemoveCommand extends GraphCommand {
return 'Removes a multitenant organization';
}

constructor() {
super();

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

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

#initOptions(): void {
this.options.unshift(
{
option: '-f, --force'
}
);
public get schema(): z.ZodTypeAny | undefined {
return options;
}

public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
Expand Down
32 changes: 16 additions & 16 deletions src/m365/entra/commands/multitenant/multitenant-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { cli } from '../../../../cli/cli.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import commands from '../../commands.js';
import command from './multitenant-set.js';
import command, { options } from './multitenant-set.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
Expand All @@ -16,15 +15,16 @@ import { CommandError } from '../../../../Command.js';
describe(commands.MULTITENANT_SET, () => {
let log: string[];
let logger: Logger;
let commandInfo: CommandInfo;
let commandOptionsSchema: typeof options;

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);
const commandInfo = cli.getCommandInfo(command);
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
});

beforeEach(() => {
Expand Down Expand Up @@ -63,23 +63,23 @@ describe(commands.MULTITENANT_SET, () => {
});

it('passes validation when only displayName is specified', async () => {
const actual = await command.validate({ options: { displayName: 'Contoso organization' } }, commandInfo);
assert.strictEqual(actual, true);
const parseResult = commandOptionsSchema.safeParse({ displayName: 'Contoso organization' });
assert.strictEqual(parseResult.success, true);
});

it('passes validation when only description is specified', async () => {
const actual = await command.validate({ options: { description: 'Contoso and partners' } }, commandInfo);
assert.strictEqual(actual, true);
const parseResult = commandOptionsSchema.safeParse({ description: 'Contoso and partners' });
assert.strictEqual(parseResult.success, true);
});

it('passes validation when the displayName and description are specified', async () => {
const actual = await command.validate({ options: { displayName: 'Contoso organization', description: 'Contoso and partners' } }, commandInfo);
assert.strictEqual(actual, true);
const parseResult = commandOptionsSchema.safeParse({ displayName: 'Contoso organization', description: 'Contoso and partners' });
assert.strictEqual(parseResult.success, true);
});

it('fails validation when no option is specified', async () => {
const actual = await command.validate({ options: {} }, commandInfo);
assert.notStrictEqual(actual, true);
const parseResult = commandOptionsSchema.safeParse({});
assert.strictEqual(parseResult.success, false);
});

it('updates a displayName of a multitenant organization', async () => {
Expand All @@ -91,7 +91,7 @@ describe(commands.MULTITENANT_SET, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { displayName: 'Contoso organization' } });
await command.action(logger, { options: commandOptionsSchema.parse({ displayName: 'Contoso organization' }) });
assert(patchRequestStub.called);
});

Expand All @@ -104,7 +104,7 @@ describe(commands.MULTITENANT_SET, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { description: 'Contoso and partners', verbose: true } });
await command.action(logger, { options: commandOptionsSchema.parse({ description: 'Contoso and partners', verbose: true }) });
assert(patchRequestStub.called);
});

Expand All @@ -117,7 +117,7 @@ describe(commands.MULTITENANT_SET, () => {
throw 'Invalid request';
});

await command.action(logger, { options: { displayName: 'Contoso organization', description: 'Contoso and partners' } });
await command.action(logger, { options: commandOptionsSchema.parse({ displayName: 'Contoso organization', description: 'Contoso and partners' }) });
assert(patchRequestStub.called);
});

Expand All @@ -133,6 +133,6 @@ describe(commands.MULTITENANT_SET, () => {
}
});

await assert.rejects(command.action(logger, { options: {} }), new CommandError('Invalid request'));
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ displayName: 'test' }) }), new CommandError('Invalid request'));
});
});
61 changes: 15 additions & 46 deletions src/m365/entra/commands/multitenant/multitenant-set.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import GlobalOptions from '../../../../GlobalOptions.js';
import { z } from 'zod';
import { globalOptionsZod } from '../../../../Command.js';
import { Logger } from '../../../../cli/Logger.js';
import request, { CliRequestOptions } from '../../../../request.js';
import GraphCommand from '../../../base/GraphCommand.js';
import commands from '../../commands.js';
import { MultitenantOrganization } from './MultitenantOrganization.js';

export const options = globalOptionsZod
.extend({
displayName: z.string().optional().alias('n'),
description: z.string().optional().alias('d')
}).strict();
declare type Options = z.infer<typeof options>;

interface CommandArgs {
options: Options;
}

interface Options extends GlobalOptions {
displayName?: string;
description?: string;
}

class EntraMultitenantSetCommand extends GraphCommand {
public get name(): string {
return commands.MULTITENANT_SET;
Expand All @@ -23,49 +26,15 @@ class EntraMultitenantSetCommand extends GraphCommand {
return 'Updates the properties of a multitenant organization';
}

constructor() {
super();

this.#initTelemetry();
this.#initOptions();
this.#initValidators();
this.#initTypes();
public get schema(): z.ZodTypeAny | undefined {
return options;
}

#initTelemetry(): void {
this.telemetry.push((args: CommandArgs) => {
Object.assign(this.telemetryProperties, {
displayName: typeof args.options.displayName !== 'undefined',
description: typeof args.options.description !== 'undefined'
public getRefinedSchema(schema: typeof options): z.ZodObject<any> | undefined {
return schema
.refine(options => options.displayName || options.description, {
error: 'Specify either displayName or description or both'
});
});
}

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

#initValidators(): void {
this.validators.push(
async (args: CommandArgs) => {
if (!args.options.displayName && !args.options.description) {
return 'Specify either displayName or description or both.';
}

return true;
}
);
}

#initTypes(): void {
this.types.string.push('displayName', 'description');
}

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