From 96172e9cc977772d696bbdf72a1a79350358e06a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:32:45 +0000 Subject: [PATCH 1/2] Address PR review feedback for Entra Zod migration --- .../approleassignment-add.spec.ts | 3 +- .../approleassignment-add.ts | 4 +-- .../approleassignment-list.spec.ts | 3 +- .../approleassignment-remove.spec.ts | 3 +- .../approleassignment-remove.ts | 4 +-- .../enterpriseapp/enterpriseapp-get.spec.ts | 7 ++++- .../enterpriseapp/enterpriseapp-list.spec.ts | 28 ++++++++++++++++++- .../enterpriseapp-remove.spec.ts | 2 +- 8 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/m365/entra/commands/approleassignment/approleassignment-add.spec.ts b/src/m365/entra/commands/approleassignment/approleassignment-add.spec.ts index ea9c2bbf512..edb075f199f 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-add.spec.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-add.spec.ts @@ -49,7 +49,7 @@ describe(commands.APPROLEASSIGNMENT_ADD, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - commandOptionsSchema = commandInfo.command.getSchemaToParse()! as typeof options; + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -311,4 +311,3 @@ describe(commands.APPROLEASSIGNMENT_ADD, () => { assert.strictEqual(actual.success, true); }); }); - diff --git a/src/m365/entra/commands/approleassignment/approleassignment-add.ts b/src/m365/entra/commands/approleassignment/approleassignment-add.ts index d2140e6b80f..b77314d889a 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-add.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-add.ts @@ -22,7 +22,7 @@ export const options = z.strictObject({ appObjectId: z.uuid().optional(), appDisplayName: z.string().optional(), resource: z.string().alias('r'), - scopes: z.string().alias('s') + scopes: z.string().transform((value) => value.split(',').map(String)).alias('s') }); declare type Options = z.infer; @@ -144,7 +144,7 @@ class EntraAppRoleAssignmentAddCommand extends GraphCommand { } // search for match between the found app roles and the specified scopes option value - for (const scope of args.options.scopes.split(',')) { + for (const scope of args.options.scopes) { const existingRoles = appRolesFound.filter((role: AppRole) => { return role.value.toLocaleLowerCase() === scope.toLocaleLowerCase().trim(); }); diff --git a/src/m365/entra/commands/approleassignment/approleassignment-list.spec.ts b/src/m365/entra/commands/approleassignment/approleassignment-list.spec.ts index 81227021834..c90df9cd258 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-list.spec.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-list.spec.ts @@ -443,7 +443,7 @@ describe(commands.APPROLEASSIGNMENT_LIST, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - commandOptionsSchema = commandInfo.command.getSchemaToParse()! as typeof options; + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -577,4 +577,3 @@ describe(commands.APPROLEASSIGNMENT_LIST, () => { assert.strictEqual(actual.success, true); }); }); - diff --git a/src/m365/entra/commands/approleassignment/approleassignment-remove.spec.ts b/src/m365/entra/commands/approleassignment/approleassignment-remove.spec.ts index 88642feab0d..a737e924589 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-remove.spec.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-remove.spec.ts @@ -29,7 +29,7 @@ describe(commands.APPROLEASSIGNMENT_REMOVE, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - commandOptionsSchema = commandInfo.command.getSchemaToParse()! as typeof options; + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -279,4 +279,3 @@ describe(commands.APPROLEASSIGNMENT_REMOVE, () => { assert.strictEqual(actual.success, true); }); }); - diff --git a/src/m365/entra/commands/approleassignment/approleassignment-remove.ts b/src/m365/entra/commands/approleassignment/approleassignment-remove.ts index d0e4ab86e2a..67eb5350a59 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-remove.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-remove.ts @@ -16,7 +16,7 @@ export const options = z.strictObject({ appObjectId: z.uuid().optional(), appDisplayName: z.string().optional(), resource: z.string().alias('r'), - scopes: z.string().alias('s'), + scopes: z.string().transform((value) => value.split(',').map(String)).alias('s'), force: z.boolean().optional().alias('f') }); @@ -115,7 +115,7 @@ class EntraAppRoleAssignmentRemoveCommand extends GraphCommand { throw `The resource '${args.options.resource}' does not have any application permissions available.`; } - for (const scope of args.options.scopes.split(',')) { + for (const scope of args.options.scopes) { const existingRoles = appRolesFound.filter((role: AppRole) => { return role.value!.toLocaleLowerCase() === scope.toLocaleLowerCase().trim(); }); diff --git a/src/m365/entra/commands/enterpriseapp/enterpriseapp-get.spec.ts b/src/m365/entra/commands/enterpriseapp/enterpriseapp-get.spec.ts index 2b9dc315f7b..d9e34d44ba9 100644 --- a/src/m365/entra/commands/enterpriseapp/enterpriseapp-get.spec.ts +++ b/src/m365/entra/commands/enterpriseapp/enterpriseapp-get.spec.ts @@ -44,7 +44,7 @@ describe(commands.ENTERPRISEAPP_GET, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - commandOptionsSchema = commandInfo.command.getSchemaToParse()! as typeof options; + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -307,6 +307,11 @@ describe(commands.ENTERPRISEAPP_GET, () => { assert.strictEqual(actual.success, false); }); + it('fails validation if both id and objectId are specified', () => { + const actual = commandOptionsSchema.safeParse({ id: '6a7b1395-d313-4682-8ed4-65a6265a6320', objectId: 'dbf32ef1-ea3e-4e3c-968f-79fbe57ea4d8' }); + assert.strictEqual(actual.success, false); + }); + it('fails validation if objectId and displayName are specified', () => { const actual = commandOptionsSchema.safeParse({ displayName: 'abc', objectId: '6a7b1395-d313-4682-8ed4-65a6265a6320' }); assert.strictEqual(actual.success, false); diff --git a/src/m365/entra/commands/enterpriseapp/enterpriseapp-list.spec.ts b/src/m365/entra/commands/enterpriseapp/enterpriseapp-list.spec.ts index ae54ca405ec..a57ca0ea8dc 100644 --- a/src/m365/entra/commands/enterpriseapp/enterpriseapp-list.spec.ts +++ b/src/m365/entra/commands/enterpriseapp/enterpriseapp-list.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 request from '../../../../request.js'; import { telemetry } from '../../../../telemetry.js'; @@ -8,12 +10,14 @@ 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 './enterpriseapp-list.js'; +import command, { options } from './enterpriseapp-list.js'; describe(commands.ENTERPRISEAPP_LIST, () => { let log: string[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; const displayName = "My custom enterprise application"; const tag = "WindowsAzureActiveDirectoryIntegratedApp"; @@ -97,6 +101,8 @@ describe(commands.ENTERPRISEAPP_LIST, () => { sinon.stub(pid, 'getProcessName').callsFake(() => ''); sinon.stub(session, 'getId').callsFake(() => ''); auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; }); beforeEach(() => { @@ -197,4 +203,24 @@ describe(commands.ENTERPRISEAPP_LIST, () => { await assert.rejects(command.action(logger, { options: {} } as any), error['odata.error'].message.value); }); + + it('passes validation when displayName is specified', () => { + const actual = commandOptionsSchema.safeParse({ displayName: 'My custom enterprise application' }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation when tag is specified', () => { + const actual = commandOptionsSchema.safeParse({ tag: 'WindowsAzureActiveDirectoryIntegratedApp' }); + assert.strictEqual(actual.success, true); + }); + + it('fails validation if displayName is not a string', () => { + const actual = commandOptionsSchema.safeParse({ displayName: 1 }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if tag is not a string', () => { + const actual = commandOptionsSchema.safeParse({ tag: 1 }); + assert.strictEqual(actual.success, false); + }); }); diff --git a/src/m365/entra/commands/enterpriseapp/enterpriseapp-remove.spec.ts b/src/m365/entra/commands/enterpriseapp/enterpriseapp-remove.spec.ts index 98d46977bcd..94829a7d128 100644 --- a/src/m365/entra/commands/enterpriseapp/enterpriseapp-remove.spec.ts +++ b/src/m365/entra/commands/enterpriseapp/enterpriseapp-remove.spec.ts @@ -56,7 +56,7 @@ describe(commands.ENTERPRISEAPP_REMOVE, () => { sinon.stub(session, 'getId').returns(''); auth.connection.active = true; commandInfo = cli.getCommandInfo(command); - commandOptionsSchema = commandInfo.command.getSchemaToParse()! as typeof options; + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => settingName === settingsNames.prompt ? false : defaultValue); }); From 6b7e1c839fcaf322d21025eaac52daa35cf2ea41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:39:54 +0000 Subject: [PATCH 2/2] Apply reviewer feedback for Entra command migration --- .../entra/commands/approleassignment/approleassignment-add.ts | 4 +++- .../commands/approleassignment/approleassignment-remove.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/m365/entra/commands/approleassignment/approleassignment-add.ts b/src/m365/entra/commands/approleassignment/approleassignment-add.ts index b77314d889a..e8b2dd606c2 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-add.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-add.ts @@ -143,8 +143,10 @@ class EntraAppRoleAssignmentAddCommand extends GraphCommand { throw `The resource '${args.options.resource}' does not have any application permissions available.`; } + const scopes = `${args.options.scopes}`.split(',').map(String); + // search for match between the found app roles and the specified scopes option value - for (const scope of args.options.scopes) { + for (const scope of scopes) { const existingRoles = appRolesFound.filter((role: AppRole) => { return role.value.toLocaleLowerCase() === scope.toLocaleLowerCase().trim(); }); diff --git a/src/m365/entra/commands/approleassignment/approleassignment-remove.ts b/src/m365/entra/commands/approleassignment/approleassignment-remove.ts index 67eb5350a59..1b716b584b7 100644 --- a/src/m365/entra/commands/approleassignment/approleassignment-remove.ts +++ b/src/m365/entra/commands/approleassignment/approleassignment-remove.ts @@ -115,7 +115,9 @@ class EntraAppRoleAssignmentRemoveCommand extends GraphCommand { throw `The resource '${args.options.resource}' does not have any application permissions available.`; } - for (const scope of args.options.scopes) { + const scopes = `${args.options.scopes}`.split(',').map(String); + + for (const scope of scopes) { const existingRoles = appRolesFound.filter((role: AppRole) => { return role.value!.toLocaleLowerCase() === scope.toLocaleLowerCase().trim(); });