From 12e771117169f3f9bc314f53350be998bdc4cad8 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 01:09:24 -0500 Subject: [PATCH 1/6] fix(cloudformation): refresh stacks after changeset is deleted --- .../cloudformation/commands/cfnCommands.ts | 27 ++----------------- .../actions/changeSetDeletionWorkflow.ts | 5 +++- ...-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json | 4 +++ 3 files changed, 10 insertions(+), 26 deletions(-) create mode 100644 packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json diff --git a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts b/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts index cbc067cb85d..ee7c829cf1c 100644 --- a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts +++ b/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts @@ -31,7 +31,6 @@ import { shouldImportResources, getResourcesToImport, getEnvironmentName, - getChangeSetName, chooseOptionalFlagSuggestion as chooseOptionalFlagMode, getTags, getOnStackFailure, @@ -111,14 +110,8 @@ export function executeChangeSetCommand(client: LanguageClient, coordinator: Sta } export function deleteChangeSetCommand(client: LanguageClient) { - return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params?: ChangeSetReference) => { + return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params: ChangeSetReference) => { try { - params = params ?? (await promptForChangeSetReference()) - - if (!params) { - return - } - const changeSetDeletion = new ChangeSetDeletion(params.stackName, params.changeSetName, client) await changeSetDeletion.delete() @@ -129,14 +122,8 @@ export function deleteChangeSetCommand(client: LanguageClient) { } export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffWebviewProvider) { - return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params?: ChangeSetReference) => { + return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params: ChangeSetReference) => { try { - params = params ?? (await promptForChangeSetReference()) - - if (!params) { - return - } - const describeChangeSetResult = await describeChangeSet(client, { changeSetName: params.changeSetName, stackName: params.stackName, @@ -157,16 +144,6 @@ export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffW }) } -async function promptForChangeSetReference(): Promise { - const stackName = await getStackName() - const changeSetName = await getChangeSetName() - if (!stackName || !changeSetName) { - return undefined - } - - return { stackName: stackName, changeSetName: changeSetName } -} - export function deployTemplateCommand( client: LanguageClient, diffProvider: DiffWebviewProvider, diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts index 43e23a64eea..eaa064e7f58 100644 --- a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts +++ b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts @@ -15,7 +15,8 @@ import { import { deleteChangeSet, describeChangeSetDeletionStatus, getChangeSetDeletionStatus } from './stackActionApi' import { createChangeSetDeletionParams } from './stackActionUtil' import { getLogger } from '../../../../shared/logger/logger' -import { extractErrorMessage } from '../../utils' +import { commandKey, extractErrorMessage } from '../../utils' +import { commands } from 'vscode' export class ChangeSetDeletion { private readonly id: string @@ -66,6 +67,7 @@ export class ChangeSetDeletion { describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' ) } + void commands.executeCommand(commandKey('stacks.refresh')) clearInterval(interval) break case StackActionPhase.DELETION_FAILED: { @@ -77,6 +79,7 @@ export class ChangeSetDeletion { this.stackName, describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' ) + void commands.executeCommand(commandKey('stacks.refresh')) clearInterval(interval) break } diff --git a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json new file mode 100644 index 00000000000..c6f22fab3b8 --- /dev/null +++ b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Refresh stacks after change set deletion" +} From fd904984731ea6df10b5f6d2d48c58b029c1df4a Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 10:39:30 -0500 Subject: [PATCH 2/6] add unit test and use globals setInterval --- .../actions/changeSetDeletionWorkflow.ts | 22 ++--- .../actions/changeSetDeletionWorkflow.test.ts | 85 +++++++++++++++++++ 2 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts index eaa064e7f58..a1fa6bdd65c 100644 --- a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts +++ b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts @@ -17,19 +17,18 @@ import { createChangeSetDeletionParams } from './stackActionUtil' import { getLogger } from '../../../../shared/logger/logger' import { commandKey, extractErrorMessage } from '../../utils' import { commands } from 'vscode' +import globals from '../../../../shared/extensionGlobals' export class ChangeSetDeletion { private readonly id: string - private readonly stackName: string - private readonly changeSetName: string - private readonly client: LanguageClient private status: StackActionPhase | undefined - constructor(stackName: string, changeSetName: string, client: LanguageClient) { + constructor( + private readonly stackName: string, + private readonly changeSetName: string, + private readonly client: LanguageClient + ) { this.id = uuidv4() - this.stackName = stackName - this.changeSetName = changeSetName - this.client = client } async delete() { @@ -39,7 +38,7 @@ export class ChangeSetDeletion { } private pollForProgress() { - const interval = setInterval(() => { + const interval = globals.clock.setInterval(() => { getChangeSetDeletionStatus(this.client, { id: this.id }) .then(async (deletionResult) => { if (deletionResult.phase === this.status) { @@ -68,7 +67,7 @@ export class ChangeSetDeletion { ) } void commands.executeCommand(commandKey('stacks.refresh')) - clearInterval(interval) + globals.clock.clearInterval(interval) break case StackActionPhase.DELETION_FAILED: { const describeDeplomentStatusResult = await describeChangeSetDeletionStatus(this.client, { @@ -80,7 +79,7 @@ export class ChangeSetDeletion { describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' ) void commands.executeCommand(commandKey('stacks.refresh')) - clearInterval(interval) + globals.clock.clearInterval(interval) break } } @@ -88,7 +87,8 @@ export class ChangeSetDeletion { .catch(async (error) => { getLogger().error(`Error polling for deletion status: ${error}`) showErrorMessage(`Error polling for deletion status: ${extractErrorMessage(error)}`) - clearInterval(interval) + void commands.executeCommand(commandKey('stacks.refresh')) + globals.clock.clearInterval(interval) }) }, 1000) } diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts new file mode 100644 index 00000000000..1dab45b112d --- /dev/null +++ b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts @@ -0,0 +1,85 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import { SinonSandbox, SinonStub, createSandbox } from 'sinon' +import { commands } from 'vscode' +import { ChangeSetDeletion } from '../../../../../awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow' +import { + StackActionPhase, + StackActionState, +} from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' +import { commandKey } from '../../../../../awsService/cloudformation/utils' +import { globals } from '../../../../../shared' + +describe('ChangeSetDeletion', function () { + let sandbox: SinonSandbox + + beforeEach(function () { + sandbox = createSandbox() + }) + + afterEach(function () { + sandbox.restore() + }) + + describe('delete', function () { + let mockClient: any + let executeCommandStub: SinonStub + let getChangeSetDeletionStatusStub: SinonStub + let describeChangeSetDeletionStatusStub: SinonStub + let setIntervalStub: SinonStub + + beforeEach(function () { + mockClient = { sendRequest: sandbox.stub().resolves({}) } + executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves() + + const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi') + getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus') + describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus') + sandbox.stub(stackActionApi, 'deleteChangeSet').resolves() + + setIntervalStub = sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => { + setImmediate(() => callback()) + return 1 as any + }) + sandbox.stub(globals.clock, 'clearInterval') + }) + + it('should call refresh command after successful deletion', async function () { + getChangeSetDeletionStatusStub.resolves({ + phase: StackActionPhase.DELETION_COMPLETE, + state: StackActionState.SUCCESSFUL, + }) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + + it('should call refresh command after failed deletion', async function () { + getChangeSetDeletionStatusStub.resolves({ phase: StackActionPhase.DELETION_FAILED }) + describeChangeSetDeletionStatusStub.resolves({ FailureReason: 'Test failure' }) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + + it('should not call refresh command when polling encounters error', async function () { + getChangeSetDeletionStatusStub.rejects(new Error('Polling error')) + + const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) + await deletion.delete() + await new Promise((resolve) => setImmediate(resolve)) + + assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) + }) + }) +}) From 72f4b7ef20d907ec23c4d725d65abbf61bdedfa7 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 10:55:13 -0500 Subject: [PATCH 3/6] remove unused stub --- .../stacks/actions/changeSetDeletionWorkflow.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts index 1dab45b112d..18679760d52 100644 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts +++ b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts @@ -30,7 +30,6 @@ describe('ChangeSetDeletion', function () { let executeCommandStub: SinonStub let getChangeSetDeletionStatusStub: SinonStub let describeChangeSetDeletionStatusStub: SinonStub - let setIntervalStub: SinonStub beforeEach(function () { mockClient = { sendRequest: sandbox.stub().resolves({}) } @@ -40,11 +39,6 @@ describe('ChangeSetDeletion', function () { getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus') describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus') sandbox.stub(stackActionApi, 'deleteChangeSet').resolves() - - setIntervalStub = sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => { - setImmediate(() => callback()) - return 1 as any - }) sandbox.stub(globals.clock, 'clearInterval') }) From ceadbba04ae7bd03ed09503b59ee13358a053964 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 11:21:47 -0500 Subject: [PATCH 4/6] stub globals setInterval --- .../stacks/actions/changeSetDeletionWorkflow.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts index 18679760d52..9dec3c1e3d3 100644 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts +++ b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts @@ -39,6 +39,11 @@ describe('ChangeSetDeletion', function () { getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus') describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus') sandbox.stub(stackActionApi, 'deleteChangeSet').resolves() + + sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => { + setImmediate(() => callback()) + return 1 as any + }) sandbox.stub(globals.clock, 'clearInterval') }) From 5e559bb868e338256a4c9afc59f62f2add715e71 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 11:48:13 -0500 Subject: [PATCH 5/6] add cfn prefix in changelog --- .../Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json index c6f22fab3b8..aa93c10ad5f 100644 --- a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json +++ b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json @@ -1,4 +1,4 @@ { "type": "Bug Fix", - "description": "Refresh stacks after change set deletion" + "description": "Cloudformation: refresh stacks after change set deletion" } From d90fa906fa2be9a5b67d6401ee3d48d589abb462 Mon Sep 17 00:00:00 2001 From: Zeeshan Ahmed Date: Fri, 21 Nov 2025 11:51:38 -0500 Subject: [PATCH 6/6] typo in changelog --- .../Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json index aa93c10ad5f..c04dbc69061 100644 --- a/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json +++ b/packages/toolkit/.changes/next-release/Bug Fix-20f1c834-8e8b-47a2-ab09-4a24df3e959e.json @@ -1,4 +1,4 @@ { "type": "Bug Fix", - "description": "Cloudformation: refresh stacks after change set deletion" + "description": "CloudFormation: refresh stacks after change set deletion" }