Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { AmplifyGen2MigrationValidations } from '../../../commands/gen2-migration/_validations';
import { $TSContext } from '@aws-amplify/amplify-cli-core';
import { $TSContext, stateManager } from '@aws-amplify/amplify-cli-core';
import { CloudFormationClient, DescribeChangeSetOutput } from '@aws-sdk/client-cloudformation';

jest.mock('@aws-sdk/client-cloudformation');
jest.mock('@aws-amplify/amplify-cli-core', () => ({
...jest.requireActual('@aws-amplify/amplify-cli-core'),
stateManager: {
getMeta: jest.fn(),
},
}));

describe('AmplifyGen2MigrationValidations', () => {
let mockContext: $TSContext;
Expand Down Expand Up @@ -491,4 +497,123 @@ describe('AmplifyGen2MigrationValidations', () => {
});
});
});

describe('validateDeploymentStatus', () => {
let mockSend: jest.Mock;

beforeEach(() => {
mockSend = jest.fn();
(CloudFormationClient as jest.Mock).mockImplementation(() => ({
send: mockSend,
}));
});

afterEach(() => {
jest.clearAllMocks();
});

it('should throw StackNotFoundError when stackName is missing', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {},
},
});

await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
name: 'StackNotFoundError',
message: 'Root stack not found',
resolution: 'Ensure the project is initialized and deployed.',
});
});

it('should throw StackNotFoundError when stack not found in CloudFormation', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {
StackName: 'test-stack',
},
},
});

mockSend.mockResolvedValue({ Stacks: [] });

await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
name: 'StackNotFoundError',
message: 'Stack test-stack not found in CloudFormation',
resolution: 'Ensure the project is deployed.',
});
});

it('should pass when stack status is UPDATE_COMPLETE', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {
StackName: 'test-stack',
},
},
});

mockSend.mockResolvedValue({
Stacks: [{ StackStatus: 'UPDATE_COMPLETE' }],
});

await expect(validations.validateDeploymentStatus()).resolves.not.toThrow();
});

it('should pass when stack status is CREATE_COMPLETE', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {
StackName: 'test-stack',
},
},
});

mockSend.mockResolvedValue({
Stacks: [{ StackStatus: 'CREATE_COMPLETE' }],
});

await expect(validations.validateDeploymentStatus()).resolves.not.toThrow();
});

it('should throw StackStateError when status is UPDATE_IN_PROGRESS', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {
StackName: 'test-stack',
},
},
});

mockSend.mockResolvedValue({
Stacks: [{ StackStatus: 'UPDATE_IN_PROGRESS' }],
});

await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
name: 'StackStateError',
message: 'Root stack status is UPDATE_IN_PROGRESS, expected UPDATE_COMPLETE or CREATE_COMPLETE',
resolution: 'Complete the deployment before proceeding.',
});
});

it('should throw StackStateError when status is ROLLBACK_COMPLETE', async () => {
jest.spyOn(stateManager, 'getMeta').mockReturnValue({
providers: {
awscloudformation: {
StackName: 'test-stack',
},
},
});

mockSend.mockResolvedValue({
Stacks: [{ StackStatus: 'ROLLBACK_COMPLETE' }],
});

await expect(validations.validateDeploymentStatus()).rejects.toMatchObject({
name: 'StackStateError',
message: 'Root stack status is ROLLBACK_COMPLETE, expected UPDATE_COMPLETE or CREATE_COMPLETE',
resolution: 'Complete the deployment before proceeding.',
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { AmplifyDriftDetector } from '../drift';
import { $TSContext, AmplifyError } from '@aws-amplify/amplify-cli-core';
import { $TSContext, AmplifyError, stateManager } from '@aws-amplify/amplify-cli-core';
import { printer } from '@aws-amplify/amplify-prompts';
import { CloudFormationClient, DescribeChangeSetOutput, DescribeStackResourcesCommand } from '@aws-sdk/client-cloudformation';
import {
DescribeChangeSetOutput,
CloudFormationClient,
DescribeStacksCommand,
DescribeStackResourcesCommand,
} from '@aws-sdk/client-cloudformation';
import { STATEFUL_RESOURCES } from './stateful-resources';

export class AmplifyGen2MigrationValidations {
Expand All @@ -16,7 +21,35 @@ export class AmplifyGen2MigrationValidations {
}

public async validateDeploymentStatus(): Promise<void> {
printer.warn('Not implemented');
const amplifyMeta = stateManager.getMeta();
const stackName = amplifyMeta?.providers?.awscloudformation?.StackName;

if (!stackName) {
throw new AmplifyError('StackNotFoundError', {
message: 'Root stack not found',
resolution: 'Ensure the project is initialized and deployed.',
});
}

const cfnClient = new CloudFormationClient({});
const response = await cfnClient.send(new DescribeStacksCommand({ StackName: stackName }));

if (!response.Stacks || response.Stacks.length === 0) {
throw new AmplifyError('StackNotFoundError', {
message: `Stack ${stackName} not found in CloudFormation`,
resolution: 'Ensure the project is deployed.',
});
}

const stackStatus = response.Stacks[0].StackStatus;
const validStatuses = ['UPDATE_COMPLETE', 'CREATE_COMPLETE'];

if (!validStatuses.includes(stackStatus)) {
throw new AmplifyError('StackStateError', {
message: `Root stack status is ${stackStatus}, expected ${validStatuses.join(' or ')}`,
resolution: 'Complete the deployment before proceeding.',
});
}
}

public async validateDeploymentVersion(): Promise<void> {
Expand Down
Loading