diff --git a/src/m365/util/commands/accesstoken/accesstoken-get.spec.ts b/src/m365/util/commands/accesstoken/accesstoken-get.spec.ts index 1615b45aa6b..5ceff24be7c 100644 --- a/src/m365/util/commands/accesstoken/accesstoken-get.spec.ts +++ b/src/m365/util/commands/accesstoken/accesstoken-get.spec.ts @@ -1,6 +1,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 { Logger } from '../../../../cli/Logger.js'; import { CommandError } from '../../../../Command.js'; import { telemetry } from '../../../../telemetry.js'; @@ -8,13 +10,15 @@ 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 './accesstoken-get.js'; +import command, { options } from './accesstoken-get.js'; describe(commands.ACCESSTOKEN_GET, () => { let log: any[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; let loggerLogRawSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -22,6 +26,8 @@ describe(commands.ACCESSTOKEN_GET, () => { sinon.stub(pid, 'getProcessName').returns(''); sinon.stub(session, 'getId').returns(''); auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -69,7 +75,7 @@ describe(commands.ACCESSTOKEN_GET, () => { accessToken: 'ABC' }; - await command.action(logger, { options: { resource: 'https://graph.microsoft.com' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ resource: 'https://graph.microsoft.com' }) }); assert(loggerLogSpy.calledWith('ABC')); }); @@ -82,14 +88,14 @@ describe(commands.ACCESSTOKEN_GET, () => { accessToken: 'ABC' }; - await command.action(logger, { options: { resource: 'sharepoint' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ resource: 'sharepoint' }) }); assert(loggerLogSpy.calledWith('ABC')); }); it('correctly handles error when retrieving access token', async () => { sinon.stub(auth, 'ensureAccessToken').rejects(new Error('An error has occurred')); - await assert.rejects(command.action(logger, { options: { resource: 'https://graph.microsoft.com' } } as any), new CommandError('An error has occurred')); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ resource: 'https://graph.microsoft.com' }) }), new CommandError('An error has occurred')); }); it('returns error when sharepoint specified as resource and SPO URL not available', async () => { @@ -100,7 +106,7 @@ describe(commands.ACCESSTOKEN_GET, () => { accessToken: 'ABC' }; - await assert.rejects(command.action(logger, { options: { resource: 'sharepoint' } } as any), new CommandError(`SharePoint URL undefined. Use the 'm365 spo set --url https://contoso.sharepoint.com' command to set the URL`)); + await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ resource: 'sharepoint' }) }), new CommandError(`SharePoint URL undefined. Use the 'm365 spo set --url https://contoso.sharepoint.com' command to set the URL`)); }); it('retrieves access token for graph.microsoft.com when graph specified as the resource', async () => { @@ -111,7 +117,7 @@ describe(commands.ACCESSTOKEN_GET, () => { accessToken: 'ABC' }; - await command.action(logger, { options: { resource: 'graph' } }); + await command.action(logger, { options: commandOptionsSchema.parse({ resource: 'graph' }) }); assert(loggerLogSpy.calledWith('ABC')); }); @@ -123,7 +129,7 @@ describe(commands.ACCESSTOKEN_GET, () => { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' }; - await command.action(logger, { options: { resource: 'graph', decoded: true } }); + await command.action(logger, { options: commandOptionsSchema.parse({ resource: 'graph', decoded: true }) }); assert(loggerLogRawSpy.calledWith(`{ "alg": "HS256", "typ": "JWT" @@ -133,4 +139,32 @@ describe(commands.ACCESSTOKEN_GET, () => { "iat": 1516239022 }.[signature]`)); }); + + it('fails validation if resource is missing', () => { + const actual = commandOptionsSchema.safeParse({}); + assert.strictEqual(actual.success, false); + }); + + it('fails validation with unknown options', () => { + const actual = commandOptionsSchema.safeParse({ + resource: 'https://graph.microsoft.com', + unknownOption: 'value' + }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation with all required options', () => { + const actual = commandOptionsSchema.safeParse({ resource: 'https://graph.microsoft.com' }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation with optional new flag', () => { + const actual = commandOptionsSchema.safeParse({ resource: 'https://graph.microsoft.com', new: true }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation with optional decoded flag', () => { + const actual = commandOptionsSchema.safeParse({ resource: 'https://graph.microsoft.com', decoded: true }); + assert.strictEqual(actual.success, true); + }); }); diff --git a/src/m365/util/commands/accesstoken/accesstoken-get.ts b/src/m365/util/commands/accesstoken/accesstoken-get.ts index 16fd4efa850..0443973bbfa 100644 --- a/src/m365/util/commands/accesstoken/accesstoken-get.ts +++ b/src/m365/util/commands/accesstoken/accesstoken-get.ts @@ -1,20 +1,23 @@ +import { z } from 'zod'; import auth, { Auth } from '../../../../Auth.js'; import { Logger } from '../../../../cli/Logger.js'; -import Command from '../../../../Command.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; +import Command, { globalOptionsZod } from '../../../../Command.js'; import commands from '../../commands.js'; import { accessToken } from '../../../../utils/accessToken.js'; +export const options = z.strictObject({ + ...globalOptionsZod.shape, + resource: z.string().alias('r'), + new: z.boolean().optional(), + decoded: z.boolean().optional() +}); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - new?: boolean; - resource: string; - decoded?: boolean; -} - class UtilAccessTokenGetCommand extends Command { public get name(): string { return commands.ACCESSTOKEN_GET; @@ -24,34 +27,8 @@ class UtilAccessTokenGetCommand extends Command { return 'Gets access token for the specified resource'; } - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - new: args.options.new, - decoded: args.options.decoded - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-r, --resource ' - }, - { - option: '--new' - }, - { - option: '--decoded' - } - ); + public get schema(): z.ZodType | undefined { + return options; } public async commandAction(logger: Logger, args: CommandArgs): Promise {