1- import { Capability , StackSummary , StackStatus , StackResourceSummary } from '@aws-sdk/client-cloudformation' ;
1+ import {
2+ Capability ,
3+ StackSummary ,
4+ StackStatus ,
5+ StackResourceSummary ,
6+ ChangeSetStatus ,
7+ } from '@aws-sdk/client-cloudformation' ;
28import { StubbedInstance } from 'ts-sinon' ;
39import { describe , it , expect , vi , beforeEach } from 'vitest' ;
410import { CancellationToken , ResponseError , ErrorCodes } from 'vscode-languageserver' ;
@@ -22,8 +28,10 @@ import {
2228 deleteChangeSetHandler ,
2329 describeChangeSetDeletionStatusHandler ,
2430 getChangeSetDeletionStatusHandler ,
31+ describeChangeSetHandler ,
2532} from '../../../src/handlers/StackHandler' ;
2633import { analyzeCapabilities } from '../../../src/stacks/actions/CapabilityAnalyzer' ;
34+ import { mapChangesToStackChanges } from '../../../src/stacks/actions/StackActionOperations' ;
2735import {
2836 TemplateUri ,
2937 GetCapabilitiesResult ,
@@ -32,7 +40,13 @@ import {
3240 StackActionPhase ,
3341 StackActionState ,
3442} from '../../../src/stacks/actions/StackActionRequestType' ;
35- import { ListStacksParams , ListStacksResult , ListStackResourcesResult } from '../../../src/stacks/StackRequestType' ;
43+ import {
44+ ListStacksParams ,
45+ ListStacksResult ,
46+ ListStackResourcesResult ,
47+ DescribeChangeSetParams ,
48+ DescribeChangeSetResult ,
49+ } from '../../../src/stacks/StackRequestType' ;
3650import {
3751 createMockComponents ,
3852 createMockSyntaxTreeManager ,
@@ -55,6 +69,7 @@ vi.mock('../../../src/stacks/actions/StackActionParser', () => ({
5569 parseCreateDeploymentParams : vi . fn ( ( input ) => input ) ,
5670 parseDeleteChangeSetParams : vi . fn ( ( input ) => input ) ,
5771 parseListStackResourcesParams : vi . fn ( ( input ) => input ) ,
72+ parseDescribeChangeSetParams : vi . fn ( ( input ) => input ) ,
5873} ) ) ;
5974
6075vi . mock ( '../../../src/utils/ZodErrorWrapper' , ( ) => ( {
@@ -65,6 +80,10 @@ vi.mock('../../../src/stacks/actions/CapabilityAnalyzer', () => ({
6580 analyzeCapabilities : vi . fn ( ) ,
6681} ) ) ;
6782
83+ vi . mock ( '../../../src/stacks/actions/StackActionOperations' , ( ) => ( {
84+ mapChangesToStackChanges : vi . fn ( ) ,
85+ } ) ) ;
86+
6887describe ( 'StackActionHandler' , ( ) => {
6988 let mockComponents : MockedServerComponents ;
7089 let syntaxTreeManager : StubbedInstance < SyntaxTreeManager > ;
@@ -690,4 +709,107 @@ describe('StackActionHandler', () => {
690709 expect ( result . resources [ 0 ] . logicalId ) . toBe ( 'MyBucket' ) ;
691710 } ) ;
692711 } ) ;
712+
713+ describe ( 'describeChangeSetHandler' , ( ) => {
714+ it ( 'should return changeset details on success' , async ( ) => {
715+ const mockChangeSetResponse = {
716+ Status : ChangeSetStatus . CREATE_COMPLETE ,
717+ CreationTime : new Date ( '2023-01-01T00:00:00Z' ) ,
718+ Description : 'Test changeset' ,
719+ Changes : [
720+ {
721+ Action : 'Add' ,
722+ ResourceChange : {
723+ LogicalResourceId : 'MyBucket' ,
724+ ResourceType : 'AWS::S3::Bucket' ,
725+ } ,
726+ } ,
727+ ] ,
728+ $metadata : { } ,
729+ } ;
730+
731+ const mockMappedChanges = [
732+ {
733+ type : 'Resource' ,
734+ resourceChange : {
735+ action : 'Add' ,
736+ logicalResourceId : 'MyBucket' ,
737+ resourceType : 'AWS::S3::Bucket' ,
738+ } ,
739+ } ,
740+ ] ;
741+
742+ mockComponents . cfnService . describeChangeSet . resolves ( mockChangeSetResponse ) ;
743+ vi . mocked ( mapChangesToStackChanges ) . mockReturnValue ( mockMappedChanges ) ;
744+
745+ const handler = describeChangeSetHandler ( mockComponents ) ;
746+ const params : DescribeChangeSetParams = {
747+ changeSetName : 'test-changeset' ,
748+ stackName : 'test-stack' ,
749+ } ;
750+
751+ const result = ( await handler ( params , { } as any ) ) as DescribeChangeSetResult ;
752+
753+ expect ( result ) . toEqual ( {
754+ changeSetName : 'test-changeset' ,
755+ stackName : 'test-stack' ,
756+ status : ChangeSetStatus . CREATE_COMPLETE ,
757+ creationTime : '2023-01-01T00:00:00.000Z' ,
758+ description : 'Test changeset' ,
759+ changes : mockMappedChanges ,
760+ } ) ;
761+
762+ expect (
763+ mockComponents . cfnService . describeChangeSet . calledWith ( {
764+ ChangeSetName : 'test-changeset' ,
765+ IncludePropertyValues : true ,
766+ StackName : 'test-stack' ,
767+ } ) ,
768+ ) . toBe ( true ) ;
769+ expect ( mapChangesToStackChanges ) . toHaveBeenCalledWith ( mockChangeSetResponse . Changes ) ;
770+ } ) ;
771+
772+ it ( 'should handle undefined optional fields' , async ( ) => {
773+ const mockChangeSetResponse = {
774+ Status : undefined ,
775+ CreationTime : undefined ,
776+ Description : undefined ,
777+ Changes : undefined ,
778+ $metadata : { } ,
779+ } ;
780+
781+ mockComponents . cfnService . describeChangeSet . resolves ( mockChangeSetResponse ) ;
782+ vi . mocked ( mapChangesToStackChanges ) . mockReturnValue ( [ ] ) ;
783+
784+ const handler = describeChangeSetHandler ( mockComponents ) ;
785+ const params : DescribeChangeSetParams = {
786+ changeSetName : 'test-changeset' ,
787+ stackName : 'test-stack' ,
788+ } ;
789+
790+ const result = ( await handler ( params , { } as any ) ) as DescribeChangeSetResult ;
791+
792+ expect ( result ) . toEqual ( {
793+ changeSetName : 'test-changeset' ,
794+ stackName : 'test-stack' ,
795+ status : '' ,
796+ creationTime : undefined ,
797+ description : undefined ,
798+ changes : [ ] ,
799+ } ) ;
800+ } ) ;
801+
802+ it ( 'should propagate errors from cfnService' , async ( ) => {
803+ const error = new Error ( 'ChangeSet not found' ) ;
804+ mockComponents . cfnService . describeChangeSet . rejects ( error ) ;
805+
806+ const handler = describeChangeSetHandler ( mockComponents ) ;
807+ const params : DescribeChangeSetParams = {
808+ changeSetName : 'non-existent-changeset' ,
809+ stackName : 'test-stack' ,
810+ } ;
811+
812+ await expect ( handler ( params , { } as any ) ) . rejects . toThrow ( 'ChangeSet not found' ) ;
813+ } ) ;
814+ } ) ;
693815} ) ;
0 commit comments