Skip to content

Commit 2ef5579

Browse files
Migrates some 'm365group' commands to zod
1 parent 071c745 commit 2ef5579

File tree

4 files changed

+106
-158
lines changed

4 files changed

+106
-158
lines changed

src/m365/entra/commands/m365group/m365group-get.spec.ts

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import assert from 'assert';
22
import sinon from 'sinon';
3+
import { z } from 'zod';
34
import auth from '../../../../Auth.js';
45
import { CommandError } from '../../../../Command.js';
56
import { cli } from '../../../../cli/cli.js';
@@ -19,6 +20,7 @@ describe(commands.M365GROUP_GET, () => {
1920
let logger: Logger;
2021
let loggerLogSpy: sinon.SinonSpy;
2122
let commandInfo: CommandInfo;
23+
let commandOptionsSchema: z.ZodTypeAny;
2224

2325
before(() => {
2426
sinon.stub(auth, 'restoreAuth').resolves();
@@ -28,6 +30,7 @@ describe(commands.M365GROUP_GET, () => {
2830
sinon.stub(entraGroup, 'isUnifiedGroup').resolves(true);
2931
auth.connection.active = true;
3032
commandInfo = cli.getCommandInfo(command);
33+
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
3134
});
3235

3336
beforeEach(() => {
@@ -109,7 +112,7 @@ describe(commands.M365GROUP_GET, () => {
109112
throw 'Invalid request';
110113
});
111114

112-
await command.action(logger, { options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' } });
115+
await command.action(logger, { options: commandOptionsSchema.parse({ id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' }) });
113116
assert(loggerLogSpy.calledWith({
114117
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
115118
"deletedDateTime": null,
@@ -189,7 +192,7 @@ describe(commands.M365GROUP_GET, () => {
189192
throw 'Invalid request';
190193
});
191194

192-
await command.action(logger, { options: { displayName: 'Finance' } });
195+
await command.action(logger, { options: commandOptionsSchema.parse({ displayName: 'Finance' }) });
193196
assert(loggerLogSpy.calledWith({
194197
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
195198
"deletedDateTime": null,
@@ -265,7 +268,7 @@ describe(commands.M365GROUP_GET, () => {
265268
throw 'Invalid request';
266269
});
267270

268-
await command.action(logger, { options: { debug: true, id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' } });
271+
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' }) });
269272
assert(loggerLogSpy.calledWith({
270273
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
271274
"deletedDateTime": null,
@@ -346,7 +349,7 @@ describe(commands.M365GROUP_GET, () => {
346349
throw 'Invalid request';
347350
});
348351

349-
await command.action(logger, { options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', includeSiteUrl: true } });
352+
await command.action(logger, { options: commandOptionsSchema.parse({ id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', includeSiteUrl: true }) });
350353
assert(loggerErrSpy.calledWith(chalk.yellow(`Parameter 'includeSiteUrl' is deprecated. Please use 'withSiteUrl' instead`)));
351354

352355
sinonUtil.restore(loggerErrSpy);
@@ -397,7 +400,7 @@ describe(commands.M365GROUP_GET, () => {
397400
throw 'Invalid request';
398401
});
399402

400-
await command.action(logger, { options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true } });
403+
await command.action(logger, { options: commandOptionsSchema.parse({ id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true }) });
401404
assert(loggerLogSpy.calledWith({
402405
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
403406
"deletedDateTime": null,
@@ -478,7 +481,7 @@ describe(commands.M365GROUP_GET, () => {
478481
throw 'Invalid request';
479482
});
480483

481-
await command.action(logger, { options: { debug: true, id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true } });
484+
await command.action(logger, { options: commandOptionsSchema.parse({ debug: true, id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true }) });
482485
assert(loggerLogSpy.calledWith({
483486
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
484487
"deletedDateTime": null,
@@ -559,7 +562,7 @@ describe(commands.M365GROUP_GET, () => {
559562
throw 'Invalid request';
560563
});
561564

562-
await command.action(logger, { options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true } });
565+
await command.action(logger, { options: commandOptionsSchema.parse({ id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844', withSiteUrl: true }) });
563566
assert(loggerLogSpy.calledWith({
564567
"id": "1caf7dcd-7e83-4c3a-94f7-932a1299c844",
565568
"deletedDateTime": null,
@@ -597,28 +600,39 @@ describe(commands.M365GROUP_GET, () => {
597600
const errorMessage = 'Something went wrong';
598601
sinon.stub(request, 'get').rejects(new Error(errorMessage));
599602

600-
await assert.rejects(command.action(logger, { options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' } }), new CommandError(errorMessage));
603+
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' }) }), new CommandError(errorMessage));
601604
});
602605

603-
it('fails validation if the id is not a valid GUID', async () => {
604-
const actual = await command.validate({ options: { id: '123' } }, commandInfo);
605-
assert.notStrictEqual(actual, true);
606+
it('fails validation when id and displayName are not specified', () => {
607+
const actual = commandOptionsSchema.safeParse({});
608+
assert.strictEqual(actual.success, false);
606609
});
607610

608-
it('passes validation if the id is a valid GUID', async () => {
609-
const actual = await command.validate({ options: { id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844' } }, commandInfo);
610-
assert.strictEqual(actual, true);
611+
it('fails validation when both id and displayName are specified', () => {
612+
const actual = commandOptionsSchema.safeParse({
613+
id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844',
614+
displayName: 'Finance'
615+
});
616+
assert.strictEqual(actual.success, false);
611617
});
612618

613-
it('supports specifying id', () => {
614-
const options = command.options;
615-
let containsOption = false;
616-
options.forEach(o => {
617-
if (o.option.indexOf('--id') > -1) {
618-
containsOption = true;
619-
}
619+
it('fails validation if the id is not a valid GUID', () => {
620+
const actual = commandOptionsSchema.safeParse({ id: '123' });
621+
assert.strictEqual(actual.success, false);
622+
});
623+
624+
it('passes validation if the id is a valid GUID', () => {
625+
const actual = commandOptionsSchema.safeParse({
626+
id: '1caf7dcd-7e83-4c3a-94f7-932a1299c844'
627+
});
628+
assert.strictEqual(actual.success, true);
629+
});
630+
631+
it('passes validation when displayName is specified', () => {
632+
const actual = commandOptionsSchema.safeParse({
633+
displayName: 'Finance'
620634
});
621-
assert(containsOption);
635+
assert.strictEqual(actual.success, true);
622636
});
623637

624638
it('shows error when the group is not a unified group', async () => {
@@ -650,7 +664,7 @@ describe(commands.M365GROUP_GET, () => {
650664
sinonUtil.restore(entraGroup.isUnifiedGroup);
651665
sinon.stub(entraGroup, 'isUnifiedGroup').resolves(false);
652666

653-
await assert.rejects(command.action(logger, { options: { id: groupId } }),
667+
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ id: groupId }) }),
654668
new CommandError(`Specified group with id '${groupId}' is not a Microsoft 365 group.`));
655669
});
656670
});

src/m365/entra/commands/m365group/m365group-get.ts

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
1+
import { z } from 'zod';
12
import { Logger } from '../../../../cli/Logger.js';
2-
import GlobalOptions from '../../../../GlobalOptions.js';
3+
import { globalOptionsZod } from '../../../../Command.js';
34
import request, { CliRequestOptions } from '../../../../request.js';
45
import { entraGroup } from '../../../../utils/entraGroup.js';
5-
import { validation } from '../../../../utils/validation.js';
6+
import { zod } from '../../../../utils/zod.js';
67
import GraphCommand from '../../../base/GraphCommand.js';
78
import commands from '../../commands.js';
89
import { GroupExtended } from './GroupExtended.js';
910

11+
const options = globalOptionsZod
12+
.extend({
13+
id: zod.alias('i', z.string().uuid().optional()),
14+
displayName: zod.alias('n', z.string().optional()),
15+
includeSiteUrl: z.boolean().optional(),
16+
withSiteUrl: z.boolean().optional()
17+
})
18+
.strict();
19+
20+
declare type Options = z.infer<typeof options>;
21+
1022
interface CommandArgs {
1123
options: Options;
1224
}
1325

14-
interface Options extends GlobalOptions {
15-
id?: string;
16-
displayName?: string;
17-
includeSiteUrl?: boolean;
18-
withSiteUrl?: boolean;
19-
}
20-
2126
class EntraM365GroupGetCommand extends GraphCommand {
2227
public get name(): string {
2328
return commands.M365GROUP_GET;
@@ -27,60 +32,15 @@ class EntraM365GroupGetCommand extends GraphCommand {
2732
return 'Gets information about the specified Microsoft 365 Group or Microsoft Teams team';
2833
}
2934

30-
constructor() {
31-
super();
32-
33-
this.#initTelemetry();
34-
this.#initOptions();
35-
this.#initValidators();
36-
this.#initOptionSets();
37-
this.#initTypes();
35+
public get schema(): z.ZodTypeAny | undefined {
36+
return options;
3837
}
3938

40-
#initTelemetry(): void {
41-
this.telemetry.push((args: CommandArgs) => {
42-
Object.assign(this.telemetryProperties, {
43-
includeSiteUrl: !!args.options.includeSiteUrl,
44-
withSiteUrl: !!args.options.withSiteUrl
39+
public getRefinedSchema(schema: typeof options): z.ZodEffects<any> | undefined {
40+
return schema
41+
.refine(options => [options.id, options.displayName].filter(Boolean).length === 1, {
42+
message: 'Specify either id or displayName'
4543
});
46-
});
47-
}
48-
49-
#initOptions(): void {
50-
this.options.unshift(
51-
{
52-
option: '-i, --id [id]'
53-
},
54-
{
55-
option: '-n, --displayName [displayName]'
56-
},
57-
{
58-
option: '--includeSiteUrl'
59-
},
60-
{
61-
option: '--withSiteUrl'
62-
}
63-
);
64-
}
65-
66-
#initValidators(): void {
67-
this.validators.push(
68-
async (args: CommandArgs) => {
69-
if (args.options.id && !validation.isValidGuid(args.options.id)) {
70-
return `${args.options.id} is not a valid GUID`;
71-
}
72-
73-
return true;
74-
}
75-
);
76-
}
77-
78-
#initOptionSets(): void {
79-
this.optionSets.push({ options: ['id', 'displayName'] });
80-
}
81-
82-
#initTypes(): void {
83-
this.types.string.push('id', 'displayName');
8444
}
8545

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

0 commit comments

Comments
 (0)