feat: Cloudformation + RDS
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import {describe, expect, test} from 'bun:test'
|
||||
import {
|
||||
CreateStackCommand,
|
||||
CreateChangeSetCommand,
|
||||
DescribeChangeSetCommand,
|
||||
DeleteStackCommand,
|
||||
DescribeStackEventsCommand,
|
||||
DescribeStacksCommand,
|
||||
GetTemplateCommand,
|
||||
ListStackResourcesCommand,
|
||||
ListStacksCommand,
|
||||
UpdateStackCommand,
|
||||
type CloudFormationClient,
|
||||
} from '@aws-sdk/client-cloudformation'
|
||||
import {AwsCloudFormationAdapter} from './AwsCloudFormationAdapter'
|
||||
@@ -81,6 +86,8 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient((command) => {
|
||||
if (command instanceof DescribeStacksCommand) return {Stacks: [ordersStack]}
|
||||
if (command instanceof ListStackResourcesCommand) return {StackResourceSummaries: [bucketResource]}
|
||||
if (command instanceof DescribeStackEventsCommand) return {StackEvents: [{LogicalResourceId: 'OrdersBucket', ResourceStatus: 'CREATE_COMPLETE', Timestamp: new Date('2026-01-02T03:04:07.000Z')}]}
|
||||
if (command instanceof GetTemplateCommand) return {TemplateBody: '{"Resources":{}}'}
|
||||
throw new Error('Unexpected command')
|
||||
}))
|
||||
|
||||
@@ -96,6 +103,7 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
resourceType: 'AWS::S3::Bucket',
|
||||
status: 'CREATE_COMPLETE',
|
||||
}])
|
||||
expect(result?.metadata.templateBody).toBe('{"Resources":{}}')
|
||||
})
|
||||
|
||||
test('paginates ListStackResources and combines resources from every page', async () => {
|
||||
@@ -107,6 +115,8 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
}
|
||||
return {StackResourceSummaries: [{...bucketResource, LogicalResourceId: 'OrdersQueue', PhysicalResourceId: 'orders-queue', ResourceType: 'AWS::SQS::Queue'}]}
|
||||
}
|
||||
if (command instanceof DescribeStackEventsCommand) return {StackEvents: []}
|
||||
if (command instanceof GetTemplateCommand) return {TemplateBody: '{}'}
|
||||
throw new Error('Unexpected command')
|
||||
}))
|
||||
|
||||
@@ -153,6 +163,43 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
expect(result.metadata.stackId).toBe(ordersStack.StackId)
|
||||
})
|
||||
|
||||
test('passes parameters and capabilities to CloudFormation', async () => {
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient((command) => {
|
||||
if (command instanceof CreateStackCommand) {
|
||||
expect(command.input).toMatchObject({
|
||||
Parameters: [{ParameterKey: 'Environment', ParameterValue: 'test'}],
|
||||
Capabilities: ['CAPABILITY_NAMED_IAM'],
|
||||
})
|
||||
return {StackId: ordersStack.StackId}
|
||||
}
|
||||
if (command instanceof CreateChangeSetCommand) {
|
||||
expect(command.input).toMatchObject({
|
||||
Parameters: [{ParameterKey: 'Environment', ParameterValue: 'test'}],
|
||||
Capabilities: ['CAPABILITY_NAMED_IAM'],
|
||||
})
|
||||
return {Id: 'change-set-id'}
|
||||
}
|
||||
if (command instanceof DescribeChangeSetCommand) return {ChangeSetName: 'planned', Status: 'CREATE_COMPLETE', ExecutionStatus: 'AVAILABLE'}
|
||||
throw new Error('Unexpected command')
|
||||
}))
|
||||
|
||||
await adapter.create({values: {stackName: 'orders-stack', templateBody: '{}', parameters: '{"Environment":"test"}', capabilities: 'CAPABILITY_NAMED_IAM'}})
|
||||
await adapter.createCloudFormationChangeSet('orders-stack', {name: 'planned', templateBody: '{}', parameters: {Environment: 'test'}, capabilities: ['CAPABILITY_NAMED_IAM']})
|
||||
})
|
||||
|
||||
test('creates a stack from an S3 template URL', async () => {
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient((command) => {
|
||||
expect(command).toBeInstanceOf(CreateStackCommand)
|
||||
expect((command as CreateStackCommand).input).toEqual({
|
||||
StackName: 'orders-stack',
|
||||
TemplateURL: 'https://templates.s3.amazonaws.com/orders.yaml',
|
||||
})
|
||||
return {StackId: ordersStack.StackId}
|
||||
}))
|
||||
|
||||
await expect(adapter.create({values: {stackName: 'orders-stack', templateUrl: 'https://templates.s3.amazonaws.com/orders.yaml'}})).resolves.toMatchObject({id: 'orders-stack'})
|
||||
})
|
||||
|
||||
test('rejects a missing stack name before calling CloudFormation', async () => {
|
||||
let called = false
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient(() => {
|
||||
@@ -164,14 +211,14 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
expect(called).toBeFalse()
|
||||
})
|
||||
|
||||
test('rejects a missing template body before calling CloudFormation', async () => {
|
||||
test('rejects missing template input before calling CloudFormation', async () => {
|
||||
let called = false
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient(() => {
|
||||
called = true
|
||||
return {}
|
||||
}))
|
||||
|
||||
await expect(adapter.create({values: {stackName: 'orders-stack'}})).rejects.toThrow('templateBody is required')
|
||||
await expect(adapter.create({values: {stackName: 'orders-stack'}})).rejects.toThrow('templateBody or templateUrl is required')
|
||||
expect(called).toBeFalse()
|
||||
})
|
||||
|
||||
@@ -185,6 +232,22 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
await adapter.delete('orders-stack')
|
||||
})
|
||||
|
||||
test('updates a stack template then reads back the stack workspace', async () => {
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient((command) => {
|
||||
if (command instanceof UpdateStackCommand) {
|
||||
expect(command.input).toEqual({StackName: 'orders-stack', TemplateBody: '{"Resources":{"Queue":{"Type":"AWS::SQS::Queue"}}}'})
|
||||
return {StackId: ordersStack.StackId}
|
||||
}
|
||||
if (command instanceof DescribeStacksCommand) return {Stacks: [ordersStack]}
|
||||
if (command instanceof ListStackResourcesCommand) return {StackResourceSummaries: []}
|
||||
if (command instanceof DescribeStackEventsCommand) return {StackEvents: []}
|
||||
if (command instanceof GetTemplateCommand) return {TemplateBody: '{}'}
|
||||
throw new Error('Unexpected command')
|
||||
}))
|
||||
|
||||
await expect(adapter.update('orders-stack', {values: {templateBody: '{"Resources":{"Queue":{"Type":"AWS::SQS::Queue"}}}'}})).resolves.toMatchObject({id: 'orders-stack'})
|
||||
})
|
||||
|
||||
test('returns the AWS CloudFormation schema', () => {
|
||||
const adapter = new AwsCloudFormationAdapter(fakeClient(() => ({})))
|
||||
|
||||
@@ -192,7 +255,7 @@ describe('AwsCloudFormationAdapter', () => {
|
||||
cloud: 'aws',
|
||||
service: 'iac',
|
||||
displayName: 'CloudFormation',
|
||||
actions: ['list', 'create', 'delete', 'inspect'],
|
||||
actions: ['list', 'create', 'update', 'delete', 'inspect'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user