Skip to content

Commit a4425ad

Browse files
committed
chore: add gen2 migration deployment status validation and unit tests
1 parent bd1fc47 commit a4425ad

2 files changed

Lines changed: 148 additions & 5 deletions

File tree

packages/amplify-cli/src/__tests__/commands/gen2-migration/_validations.test.ts

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AmplifyGen2MigrationValidations } from '../../../commands/gen2-migration/_validations';
2-
import { $TSContext } from '@aws-amplify/amplify-cli-core';
3-
import { DescribeChangeSetOutput } from '@aws-sdk/client-cloudformation';
2+
import { $TSContext, stateManager } from '@aws-amplify/amplify-cli-core';
3+
import { DescribeChangeSetOutput, CloudFormationClient } from '@aws-sdk/client-cloudformation';
44

55
describe('AmplifyGen2MigrationValidations', () => {
66
let mockContext: $TSContext;
@@ -317,4 +317,119 @@ describe('AmplifyGen2MigrationValidations', () => {
317317
});
318318
});
319319
});
320+
321+
describe('validateDeploymentStatus', () => {
322+
beforeEach(() => {
323+
jest.clearAllMocks();
324+
});
325+
326+
it('should throw StackNotFoundError when stackName is missing', async () => {
327+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
328+
providers: {
329+
awscloudformation: {},
330+
},
331+
});
332+
333+
await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
334+
name: 'StackNotFoundError',
335+
message: 'Root stack not found',
336+
resolution: 'Ensure the project is initialized and deployed.',
337+
});
338+
});
339+
340+
it('should throw StackNotFoundError when stack not found in CloudFormation', async () => {
341+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
342+
providers: {
343+
awscloudformation: {
344+
StackName: 'test-stack',
345+
},
346+
},
347+
});
348+
349+
const mockSend = jest.fn().mockResolvedValue({ Stacks: [] });
350+
jest.spyOn(CloudFormationClient.prototype, 'send').mockImplementation(mockSend);
351+
352+
await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
353+
name: 'StackNotFoundError',
354+
message: 'Stack test-stack not found in CloudFormation',
355+
resolution: 'Ensure the project is deployed.',
356+
});
357+
});
358+
359+
it('should pass when stack status is UPDATE_COMPLETE', async () => {
360+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
361+
providers: {
362+
awscloudformation: {
363+
StackName: 'test-stack',
364+
},
365+
},
366+
});
367+
368+
const mockSend = jest.fn().mockResolvedValue({
369+
Stacks: [{ StackStatus: 'UPDATE_COMPLETE' }],
370+
});
371+
jest.spyOn(CloudFormationClient.prototype, 'send').mockImplementation(mockSend);
372+
373+
await expect(validations.validateDeploymentStatus()).resolves.not.toThrow();
374+
});
375+
376+
it('should pass when stack status is CREATE_COMPLETE', async () => {
377+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
378+
providers: {
379+
awscloudformation: {
380+
StackName: 'test-stack',
381+
},
382+
},
383+
});
384+
385+
const mockSend = jest.fn().mockResolvedValue({
386+
Stacks: [{ StackStatus: 'CREATE_COMPLETE' }],
387+
});
388+
jest.spyOn(CloudFormationClient.prototype, 'send').mockImplementation(mockSend);
389+
390+
await expect(validations.validateDeploymentStatus()).resolves.not.toThrow();
391+
});
392+
393+
it('should throw StackStateError when status is UPDATE_IN_PROGRESS', async () => {
394+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
395+
providers: {
396+
awscloudformation: {
397+
StackName: 'test-stack',
398+
},
399+
},
400+
});
401+
402+
const mockSend = jest.fn().mockResolvedValue({
403+
Stacks: [{ StackStatus: 'UPDATE_IN_PROGRESS' }],
404+
});
405+
jest.spyOn(CloudFormationClient.prototype, 'send').mockImplementation(mockSend);
406+
407+
await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
408+
name: 'StackStateError',
409+
message: 'Root stack status is UPDATE_IN_PROGRESS, expected UPDATE_COMPLETE or CREATE_COMPLETE',
410+
resolution: 'Complete the deployment before proceeding.',
411+
});
412+
});
413+
414+
it('should throw StackStateError when status is ROLLBACK_COMPLETE', async () => {
415+
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
416+
providers: {
417+
awscloudformation: {
418+
StackName: 'test-stack',
419+
},
420+
},
421+
});
422+
423+
const mockSend = jest.fn().mockResolvedValue({
424+
Stacks: [{ StackStatus: 'ROLLBACK_COMPLETE' }],
425+
});
426+
jest.spyOn(CloudFormationClient.prototype, 'send').mockImplementation(mockSend);
427+
428+
await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
429+
name: 'StackStateError',
430+
message: 'Root stack status is ROLLBACK_COMPLETE, expected UPDATE_COMPLETE or CREATE_COMPLETE',
431+
resolution: 'Complete the deployment before proceeding.',
432+
});
433+
});
434+
});
320435
});

packages/amplify-cli/src/commands/gen2-migration/_validations.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AmplifyDriftDetector } from '../drift';
2-
import { $TSContext, AmplifyError } from '@aws-amplify/amplify-cli-core';
2+
import { $TSContext, AmplifyError, stateManager } from '@aws-amplify/amplify-cli-core';
33
import { printer } from '@aws-amplify/amplify-prompts';
4-
import { DescribeChangeSetOutput } from '@aws-sdk/client-cloudformation';
4+
import { DescribeChangeSetOutput, CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation';
55
import { STATEFUL_RESOURCES } from './stateful-resources';
66

77
export class AmplifyGen2MigrationValidations {
@@ -16,7 +16,35 @@ export class AmplifyGen2MigrationValidations {
1616
}
1717

1818
public async validateDeploymentStatus(): Promise<void> {
19-
printer.warn('Not implemented');
19+
const amplifyMeta = stateManager.getMeta();
20+
const stackName = amplifyMeta?.providers?.awscloudformation?.StackName;
21+
22+
if (!stackName) {
23+
throw new AmplifyError('StackNotFoundError', {
24+
message: 'Root stack not found',
25+
resolution: 'Ensure the project is initialized and deployed.',
26+
});
27+
}
28+
29+
const cfnClient = new CloudFormationClient({});
30+
const response = await cfnClient.send(new DescribeStacksCommand({ StackName: stackName }));
31+
32+
if (!response.Stacks || response.Stacks.length === 0) {
33+
throw new AmplifyError('StackNotFoundError', {
34+
message: `Stack ${stackName} not found in CloudFormation`,
35+
resolution: 'Ensure the project is deployed.',
36+
});
37+
}
38+
39+
const stackStatus = response.Stacks[0].StackStatus;
40+
const validStatuses = ['UPDATE_COMPLETE', 'CREATE_COMPLETE'];
41+
42+
if (!validStatuses.includes(stackStatus)) {
43+
throw new AmplifyError('StackStateError', {
44+
message: `Root stack status is ${stackStatus}, expected ${validStatuses.join(' or ')}`,
45+
resolution: 'Complete the deployment before proceeding.',
46+
});
47+
}
2048
}
2149

2250
public async validateDeploymentVersion(): Promise<void> {

0 commit comments

Comments
 (0)