@@ -11,5 +11,218 @@ Update the command file to use ZOD.
1111
1212Use the below git commits as reference implementations for a command and its tests using ZOD. Do not continue with the task if you do not have access to these commits.
1313
14- - 824c1ebd2dfb0a1eabb623ed6a22da50f4edf61d
15- - 3410857272d39e16fe498823a1df9f320b1021dd
14+ ``` diff
15+ commit 824c1ebd2dfb0a1eabb623ed6a22da50f4edf61d
16+ Author: waldekmastykarz <
[email protected] >
17+ Date: Sun Apr 20 09:02:32 2025 +0200
18+
19+ diff --git a/src/m365/booking/commands/business/business-get.spec.ts b/src/m365/booking/commands/business/business-get.spec.ts
20+ index 40b3c3d2a..9c4f21496 100644
21+ --- a/src/m365/booking/commands/business/business-get.spec.ts
22+ +++ b/src/m365/booking/commands/business/business-get.spec.ts
23+ @@ -1,10 +1,13 @@
24+ import assert from 'assert';
25+ import sinon from 'sinon';
26+ + import { z } from 'zod';
27+ import auth from '../../../../Auth.js';
28+ import { cli } from '../../../../cli/cli.js';
29+ + import { CommandInfo } from '../../../../cli/CommandInfo.js';
30+ import { Logger } from '../../../../cli/Logger.js';
31+ import { CommandError } from '../../../../Command.js';
32+ import request from '../../../../request.js';
33+ + import { settingsNames } from '../../../../settingsNames.js';
34+ import { telemetry } from '../../../../telemetry.js';
35+ import { formatting } from '../../../../utils/formatting.js';
36+ import { pid } from '../../../../utils/pid.js';
37+ @@ -12,7 +15,6 @@ import { session } from '../../../../utils/session.js';
38+ import { sinonUtil } from '../../../../utils/sinonUtil.js';
39+ import commands from '../../commands.js';
40+ import command from './business-get.js';
41+ - import { settingsNames } from '../../../../settingsNames.js';
42+
43+ describe(commands.BUSINESS_GET, () => {
44+ const validId = '
[email protected] ';
45+ @@ -31,6 +33,8 @@ describe(commands.BUSINESS_GET, () => {
46+ let log: string[];
47+ let logger: Logger;
48+ let loggerLogSpy: sinon.SinonSpy;
49+ + let commandInfo: CommandInfo;
50+ + let commandOptionsSchema: z.ZodTypeAny;
51+
52+ before(() => {
53+ sinon.stub(auth, 'restoreAuth').resolves();
54+ @@ -39,6 +43,8 @@ describe(commands.BUSINESS_GET, () => {
55+ sinon.stub(session, 'getId').returns('');
56+
57+ auth.connection.active = true;
58+ + commandInfo = cli.getCommandInfo(command);
59+ + commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
60+ });
61+
62+ beforeEach(() => {
63+ @@ -80,6 +86,25 @@ describe(commands.BUSINESS_GET, () => {
64+ assert.notStrictEqual(command.description, null);
65+ });
66+
67+ + it('fails validation when id or name are not specified', () => {
68+ + const actual = commandOptionsSchema.safeParse({});
69+ + assert.strictEqual(actual.success, false);
70+ + });
71+ +
72+ + it('passes validation when id is specified', () => {
73+ + const actual = commandOptionsSchema.safeParse({
74+ + id: validId
75+ + });
76+ + assert.strictEqual(actual.success, true);
77+ + });
78+ +
79+ + it('passes validation when name is specified', () => {
80+ + const actual = commandOptionsSchema.safeParse({
81+ + name: validName
82+ + });
83+ + assert.strictEqual(actual.success, true);
84+ + });
85+ +
86+ it('gets business by id', async () => {
87+ sinon.stub(request, 'get').callsFake(async (opts) => {
88+ if (opts.url === `https://graph.microsoft.com/v1.0/solutions/bookingBusinesses/${formatting.encodeQueryParameter(validId)}`) {
89+ @@ -89,7 +114,7 @@ describe(commands.BUSINESS_GET, () => {
90+ throw 'Invalid request';
91+ });
92+
93+ - await command.action(logger, { options: { id: validId } });
94+ + await command.action(logger, { options: commandOptionsSchema.parse({ id: validId }) });
95+ assert(loggerLogSpy.calledWith(businessResponse));
96+ });
97+
98+ @@ -106,7 +131,7 @@ describe(commands.BUSINESS_GET, () => {
99+ throw 'Invalid request';
100+ });
101+
102+ - await command.action(logger, { options: { name: validName } });
103+ + await command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) });
104+ assert(loggerLogSpy.calledWith(businessResponse));
105+ });
106+
107+ @@ -127,7 +152,7 @@ describe(commands.BUSINESS_GET, () => {
108+ throw 'Invalid request';
109+ });
110+
111+ - await assert.rejects(command.action(logger, { options: { name: validName } } as any), new CommandError("Multiple businesses with name 'Valid Business' found. Found: [email protected] ."));112+ + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) }), new CommandError("Multiple businesses with name 'Valid Business' found. Found: [email protected] ."));113+ });
114+
115+ it('handles selecting single result when multiple businesses with the specified name found and cli is set to prompt', async () => {
116+ @@ -153,7 +178,7 @@ describe(commands.BUSINESS_GET, () => {
117+
118+ sinon.stub(cli, 'handleMultipleResultsFound').resolves(businessResponse);
119+
120+ - await command.action(logger, { options: { name: validName } });
121+ + await command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) });
122+ assert(loggerLogSpy.calledWith(businessResponse));
123+ });
124+
125+ @@ -166,7 +191,7 @@ describe(commands.BUSINESS_GET, () => {
126+ throw 'Invalid request';
127+ });
128+
129+ - await assert.rejects(command.action(logger, { options: { name: validName } } as any), new CommandError(`The specified business with name ${validName} does not exist.`));
130+ + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) } as any), new CommandError(`The specified business with name ${validName} does not exist.`));
131+ });
132+
133+ it('fails when no business found with name because of an empty displayName', async () => {
134+ @@ -178,13 +203,13 @@ describe(commands.BUSINESS_GET, () => {
135+ throw 'Invalid request';
136+ });
137+
138+ - await assert.rejects(command.action(logger, { options: { name: validName } } as any), new CommandError(`The specified business with name ${validName} does not exist.`));
139+ + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) } as any), new CommandError(`The specified business with name ${validName} does not exist.`));
140+ });
141+
142+ it('correctly handles random API error', async () => {
143+ sinonUtil.restore(request.get);
144+ sinon.stub(request, 'get').rejects(new Error('An error has occurred'));
145+ - await assert.rejects(command.action(logger, { options: {} } as any), new CommandError('An error has occurred'));
146+ + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ name: validName }) } as any), new CommandError('An error has occurred'));
147+ });
148+ });
149+
150+ diff --git a/src/m365/booking/commands/business/business-get.ts b/src/m365/booking/commands/business/business-get.ts
151+ index 5a066ea56..bace8b52c 100644
152+ --- a/src/m365/booking/commands/business/business-get.ts
153+ +++ b/src/m365/booking/commands/business/business-get.ts
154+ @@ -1,21 +1,27 @@
155+ import { BookingBusiness } from '@microsoft/microsoft-graph-types';
156+ + import { z } from 'zod';
157+ + import { cli } from '../../../../cli/cli.js';
158+ import { Logger } from '../../../../cli/Logger.js';
159+ - import GlobalOptions from '../../../../GlobalOptions.js';
160+ + import { globalOptionsZod } from '../../../../Command.js';
161+ import request, { CliRequestOptions } from '../../../../request.js';
162+ import { formatting } from '../../../../utils/formatting.js';
163+ + import { zod } from '../../../../utils/zod.js';
164+ import GraphCommand from '../../../base/GraphCommand.js';
165+ import commands from '../../commands.js';
166+ - import { cli } from '../../../../cli/cli.js';
167+ +
168+ + const options = globalOptionsZod
169+ + .extend({
170+ + id: zod.alias('i', z.string().uuid().optional()),
171+ + name: zod.alias('n', z.string().optional())
172+ + })
173+ + .strict();
174+ +
175+ + declare type Options = z.infer<typeof options>;
176+
177+ interface CommandArgs {
178+ options: Options;
179+ }
180+
181+ - interface Options extends GlobalOptions {
182+ - id?: string;
183+ - name?: string;
184+ - }
185+ -
186+ class BookingBusinessGetCommand extends GraphCommand {
187+ public get name(): string {
188+ return commands.BUSINESS_GET;
189+ @@ -25,32 +31,15 @@ class BookingBusinessGetCommand extends GraphCommand {
190+ return 'Retrieve the specified Microsoft Bookings business.';
191+ }
192+
193+ - constructor() {
194+ - super();
195+ -
196+ - this.#initTelemetry();
197+ - this.#initOptions();
198+ - this.#initOptionSets();
199+ + public get schema(): z.ZodTypeAny | undefined {
200+ + return options;
201+ }
202+
203+ - #initTelemetry(): void {
204+ - this.telemetry.push((args: CommandArgs) => {
205+ - Object.assign(this.telemetryProperties, {
206+ - id: typeof args.options.id !== 'undefined',
207+ - name: typeof args.options.name !== 'undefined'
208+ + public getRefinedSchema(schema: typeof options): z.ZodEffects<any> | undefined {
209+ + return schema
210+ + .refine(options => [options.id, options.name].filter(Boolean).length === 1, {
211+ + message: 'Specify either id or name'
212+ });
213+ - });
214+ - }
215+ -
216+ - #initOptions(): void {
217+ - this.options.unshift(
218+ - { option: '-i, --id [id]' },
219+ - { option: '-n, --name [name]' }
220+ - );
221+ - }
222+ -
223+ - #initOptionSets(): void {
224+ - this.optionSets.push({ options: ['id', 'name'] });
225+ }
226+
227+ public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
228+ ```
0 commit comments