Files
floci-ui/packages/api/src/services/ec2.test.ts
T
2026-09-26 16:14:24 -04:00

631 lines
28 KiB
TypeScript

import {describe, expect, test} from 'bun:test'
import type {EC2Client} from '@aws-sdk/client-ec2'
import {
AllocateAddressCommand,
AssociateAddressCommand,
AssociateRouteTableCommand,
AuthorizeSecurityGroupEgressCommand,
AuthorizeSecurityGroupIngressCommand,
CreateImageCommand,
CreateInternetGatewayCommand,
CreateKeyPairCommand,
CreateNatGatewayCommand,
CreateRouteCommand,
CreateRouteTableCommand,
CreateSecurityGroupCommand,
CreateSubnetCommand,
CreateTagsCommand,
CreateVpcCommand,
DeleteInternetGatewayCommand,
DeleteKeyPairCommand,
DeleteNatGatewayCommand,
DeleteRouteCommand,
DeleteSecurityGroupCommand,
DeleteSubnetCommand,
DeleteTagsCommand,
DeleteVpcCommand,
DeregisterImageCommand,
DescribeAddressesCommand,
DescribeAvailabilityZonesCommand,
DescribeImagesCommand,
DescribeInstanceTypesCommand,
DescribeInternetGatewaysCommand,
DescribeKeyPairsCommand,
DescribeNatGatewaysCommand,
DescribeRouteTablesCommand,
DescribeSecurityGroupsCommand,
DescribeSubnetsCommand,
DescribeVpcAttributeCommand,
DescribeVpcsCommand,
DetachInternetGatewayCommand,
DisassociateAddressCommand,
DisassociateRouteTableCommand,
GetConsoleOutputCommand,
RebootInstancesCommand,
ModifySubnetAttributeCommand,
ModifyVpcAttributeCommand,
ReleaseAddressCommand,
RevokeSecurityGroupEgressCommand,
RevokeSecurityGroupIngressCommand,
RunInstancesCommand,
StartInstancesCommand,
StopInstancesCommand,
TerminateInstancesCommand,
} from '@aws-sdk/client-ec2'
import {createEc2Service} from './ec2'
function fakeClient(responses: Record<string, unknown> = {}): EC2Client {
return {
send: async (cmd: {constructor: {name: string}}) => {
const name = cmd.constructor.name
if (name in responses) return responses[name]
return {}
},
} as unknown as EC2Client
}
describe('ec2Service.runInstance', () => {
test('sends RunInstancesCommand and returns mapped instance', async () => {
const sentCmds: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sentCmds.push(cmd)
return {
Instances: [{
InstanceId: 'i-new123',
Tags: [{Key: 'Name', Value: 'my-instance'}],
State: {Name: 'pending'},
}],
}
},
} as unknown as EC2Client
const service = createEc2Service(client)
const result = await service.runInstance({
name: 'my-instance',
imageId: 'ami-abc',
instanceType: 't3.micro',
})
expect(sentCmds[0]).toBeInstanceOf(RunInstancesCommand)
expect(result.instanceId).toBe('i-new123')
expect(result.name).toBe('my-instance')
expect(result.state).toBe('pending')
})
})
describe('ec2Service.describeInstance', () => {
test('maps attached network interfaces and their addresses', async () => {
const service = createEc2Service(fakeClient({
DescribeInstancesCommand: {
Reservations: [{Instances: [{
InstanceId: 'i-networked',
NetworkInterfaces: [{
NetworkInterfaceId: 'eni-123',
Attachment: {DeviceIndex: 0},
Status: 'in-use',
PrivateIpAddress: '10.0.1.10',
Association: {PublicIp: '198.51.100.10'},
Groups: [{GroupId: 'sg-123', GroupName: 'web'}],
}],
}]}],
},
}))
const instance = await service.describeInstance('i-networked')
expect(instance.networkInterfaces).toEqual([{
networkInterfaceId: 'eni-123',
deviceIndex: 0,
status: 'in-use',
privateIpAddress: '10.0.1.10',
publicIpAddress: '198.51.100.10',
privateDnsName: undefined,
subnetId: undefined,
vpcId: undefined,
securityGroups: [{id: 'sg-123', name: 'web'}],
}])
})
})
describe('ec2Service lifecycle', () => {
test('startInstance sends StartInstancesCommand with correct id', async () => {
const sentCmds: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).startInstance('i-abc')
expect(sentCmds[0]).toBeInstanceOf(StartInstancesCommand)
})
test('stopInstance sends StopInstancesCommand with correct id', async () => {
const sentCmds: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).stopInstance('i-abc')
expect(sentCmds[0]).toBeInstanceOf(StopInstancesCommand)
})
test('rebootInstance sends RebootInstancesCommand', async () => {
const sentCmds: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).rebootInstance('i-abc')
expect(sentCmds[0]).toBeInstanceOf(RebootInstancesCommand)
})
test('terminateInstance sends TerminateInstancesCommand', async () => {
const sentCmds: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sentCmds.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).terminateInstance('i-abc')
expect(sentCmds[0]).toBeInstanceOf(TerminateInstancesCommand)
})
})
describe('ec2Service.updateInstanceTags', () => {
test('calls CreateTags for additions and DeleteTags for removals', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
const service = createEc2Service(client)
await service.updateInstanceTags('i-abc', [{key: 'Env', value: 'prod'}], ['OldKey'])
expect(sent[0]).toBeInstanceOf(CreateTagsCommand)
expect(sent[1]).toBeInstanceOf(DeleteTagsCommand)
})
test('skips CreateTags when nothing to add', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).updateInstanceTags('i-abc', [], ['OldKey'])
expect(sent[0]).toBeInstanceOf(DeleteTagsCommand)
expect(sent).toHaveLength(1)
})
})
describe('ec2Service.createImage', () => {
test('sends CreateImageCommand and returns imageId', async () => {
const client = fakeClient({CreateImageCommand: {ImageId: 'ami-new'}})
const result = await createEc2Service(client).createImage({instanceId: 'i-abc', name: 'snap-1'})
expect(result.imageId).toBe('ami-new')
})
})
describe('ec2Service.deregisterImage', () => {
test('sends DeregisterImageCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deregisterImage('ami-old')
expect(sent[0]).toBeInstanceOf(DeregisterImageCommand)
})
})
describe('ec2Service.listAmis', () => {
test('requests the complete image catalog and maps launchable AMIs', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
return {
Images: [{
ImageId: 'ami-amazonlinux2023',
Name: 'Amazon Linux 2023',
}],
}
},
} as unknown as EC2Client
const result = await createEc2Service(client).listAmis()
expect(sent[0]).toBeInstanceOf(DescribeImagesCommand)
expect((sent[0] as DescribeImagesCommand).input).toEqual({})
expect(result).toEqual([
expect.objectContaining({
imageId: 'ami-amazonlinux2023',
name: 'Amazon Linux 2023',
}),
])
})
})
describe('ec2Service.getConsoleOutput', () => {
test('decodes base64 output', async () => {
const encoded = Buffer.from('hello console').toString('base64')
const client = fakeClient({GetConsoleOutputCommand: {Output: encoded}})
const result = await createEc2Service(client).getConsoleOutput('i-abc')
expect(result.output).toBe('hello console')
})
test('returns empty output when operation is unsupported', async () => {
const client: EC2Client = {
send: async () => { throw new Error('GetConsoleOutput is not supported') },
} as unknown as EC2Client
const result = await createEc2Service(client).getConsoleOutput('i-abc')
expect(result.output).toBe('')
})
})
describe('ec2Service key pairs', () => {
test('listKeyPairs returns empty array when unsupported', async () => {
const client: EC2Client = {
send: async () => { throw new Error('DescribeKeyPairs is not supported') },
} as unknown as EC2Client
const result = await createEc2Service(client).listKeyPairs()
expect(result).toEqual([])
})
test('createKeyPair sends CreateKeyPairCommand with the requested key type and returns material', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
return {KeyPairId: 'kp-1', KeyName: 'my-key', KeyMaterial: '-----BEGIN RSA-----'}
},
} as unknown as EC2Client
const result = await createEc2Service(client).createKeyPair('my-key', 'ed25519')
expect(sent[0]).toBeInstanceOf(CreateKeyPairCommand)
expect((sent[0] as CreateKeyPairCommand).input).toMatchObject({KeyName: 'my-key', KeyType: 'ed25519'})
expect(result.keyName).toBe('my-key')
expect(result.keyMaterial).toBe('-----BEGIN RSA-----')
})
test('createKeyPair defaults to rsa', async () => {
const sent: CreateKeyPairCommand[] = []
const client: EC2Client = {
send: async (cmd: CreateKeyPairCommand) => {
sent.push(cmd)
return {}
},
} as unknown as EC2Client
await createEc2Service(client).createKeyPair('my-key')
expect(sent[0].input.KeyType).toBe('rsa')
})
test('deleteKeyPair sends DeleteKeyPairCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deleteKeyPair('my-key')
expect(sent[0]).toBeInstanceOf(DeleteKeyPairCommand)
})
})
describe('ec2Service security groups', () => {
test('listSecurityGroups returns empty array when unsupported', async () => {
const client: EC2Client = {
send: async () => { throw new Error('DescribeSecurityGroups is not supported') },
} as unknown as EC2Client
expect(await createEc2Service(client).listSecurityGroups()).toEqual([])
})
test('createSecurityGroup sends CreateSecurityGroupCommand and returns groupId', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => { sent.push(cmd); return {GroupId: 'sg-new'} },
} as unknown as EC2Client
const result = await createEc2Service(client).createSecurityGroup('web', 'Web SG', 'vpc-1')
expect(sent[0]).toBeInstanceOf(CreateSecurityGroupCommand)
expect(result.groupId).toBe('sg-new')
})
test('deleteSecurityGroup sends DeleteSecurityGroupCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deleteSecurityGroup('sg-1')
expect(sent[0]).toBeInstanceOf(DeleteSecurityGroupCommand)
})
test('authorizeSecurityGroupIngress sends correct command', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).authorizeSecurityGroupIngress('sg-1', {protocol: 'tcp', fromPort: 80, toPort: 80, cidr: '0.0.0.0/0'})
expect(sent[0]).toBeInstanceOf(AuthorizeSecurityGroupIngressCommand)
})
test('revokeSecurityGroupIngress sends correct command', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).revokeSecurityGroupIngress('sg-1', {protocol: 'tcp', fromPort: 80, toPort: 80, cidr: '0.0.0.0/0'})
expect(sent[0]).toBeInstanceOf(RevokeSecurityGroupIngressCommand)
})
test('authorizeSecurityGroupEgress sends correct command', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).authorizeSecurityGroupEgress('sg-1', {protocol: 'tcp', fromPort: 443, toPort: 443, cidr: '0.0.0.0/0'})
expect(sent[0]).toBeInstanceOf(AuthorizeSecurityGroupEgressCommand)
})
test('revokeSecurityGroupEgress sends correct command', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).revokeSecurityGroupEgress('sg-1', {protocol: 'tcp', fromPort: 443, toPort: 443, cidr: '0.0.0.0/0'})
expect(sent[0]).toBeInstanceOf(RevokeSecurityGroupEgressCommand)
})
})
describe('ec2Service VPCs and subnets', () => {
test('listVpcs returns empty array when unsupported', async () => {
const client: EC2Client = {
send: async () => { throw new Error('DescribeVpcs is not supported') },
} as unknown as EC2Client
expect(await createEc2Service(client).listVpcs()).toEqual([])
})
test('createVpc sends CreateVpcCommand and returns mapped vpc', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => { sent.push(cmd); return {Vpc: {VpcId: 'vpc-new', CidrBlock: '10.0.0.0/16', IsDefault: false}} },
} as unknown as EC2Client
const result = await createEc2Service(client).createVpc('10.0.0.0/16')
expect(sent[0]).toBeInstanceOf(CreateVpcCommand)
expect(result.vpcId).toBe('vpc-new')
expect(result.cidrBlock).toBe('10.0.0.0/16')
})
test('deleteVpc sends DeleteVpcCommand as final command', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deleteVpc('vpc-1')
// deleteVpc first cleans up SGs, subnets, IGWs, RTBs then sends DeleteVpcCommand last
expect(sent[sent.length - 1]).toBeInstanceOf(DeleteVpcCommand)
})
test('listSubnets returns empty array when unsupported', async () => {
const client: EC2Client = {
send: async () => { throw new Error('DescribeSubnets is not supported') },
} as unknown as EC2Client
expect(await createEc2Service(client).listSubnets()).toEqual([])
})
test('createSubnet sends CreateSubnetCommand and returns mapped subnet', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
return {Subnet: {SubnetId: 'subnet-new', VpcId: 'vpc-1', CidrBlock: '10.0.1.0/24', AvailabilityZone: 'us-east-1a'}}
},
} as unknown as EC2Client
const result = await createEc2Service(client).createSubnet('vpc-1', '10.0.1.0/24', 'us-east-1a')
expect(sent[0]).toBeInstanceOf(CreateSubnetCommand)
expect(result.subnetId).toBe('subnet-new')
expect(result.availabilityZone).toBe('us-east-1a')
})
test('deleteSubnet sends DeleteSubnetCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deleteSubnet('subnet-1')
expect(sent[0]).toBeInstanceOf(DeleteSubnetCommand)
})
test('modifySubnetMapPublicIp sends ModifySubnetAttributeCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).modifySubnetMapPublicIp('subnet-1', true)
expect(sent[0]).toBeInstanceOf(ModifySubnetAttributeCommand)
})
test('getVpcAttributes returns dns settings from two parallel describe calls', async () => {
const client = fakeClient({
DescribeVpcAttributeCommand: {AttributeValue: {Value: true}},
})
const result = await createEc2Service(client).getVpcAttributes('vpc-1')
expect(result).toHaveProperty('enableDnsHostnames')
expect(result).toHaveProperty('enableDnsSupport')
})
test('modifyVpcAttribute sends ModifyVpcAttributeCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).modifyVpcAttribute('vpc-1', 'enableDnsHostnames', true)
expect(sent[0]).toBeInstanceOf(ModifyVpcAttributeCommand)
})
})
describe('ec2Service Internet Gateways', () => {
test('listInternetGateways returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeInternetGateways is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listInternetGateways()).toEqual([])
})
test('createInternetGateway sends CreateInternetGatewayCommand and returns mapped igw', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof CreateInternetGatewayCommand) return {InternetGateway: {InternetGatewayId: 'igw-0a1b2c3d', Attachments: [], Tags: []}}
return {}
},
} as unknown as EC2Client
const result = await createEc2Service(client).createInternetGateway()
expect(sent[0]).toBeInstanceOf(CreateInternetGatewayCommand)
expect(result.internetGatewayId).toBe('igw-0a1b2c3d')
})
test('deleteInternetGateway detaches all VPCs then deletes', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof DescribeInternetGatewaysCommand) {
return {InternetGateways: [{InternetGatewayId: 'igw-0a1b2c3d', Attachments: [{VpcId: 'vpc-0a1b2c3d', State: 'available'}]}]}
}
return {}
},
} as unknown as EC2Client
await createEc2Service(client).deleteInternetGateway('igw-0a1b2c3d')
expect(sent[0]).toBeInstanceOf(DescribeInternetGatewaysCommand)
expect(sent[1]).toBeInstanceOf(DetachInternetGatewayCommand)
expect(sent[2]).toBeInstanceOf(DeleteInternetGatewayCommand)
})
})
describe('ec2Service NAT Gateways', () => {
test('listNatGateways returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeNatGateways is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listNatGateways()).toEqual([])
})
test('createNatGateway auto-allocates EIP when allocationId is absent', async () => {
const sent: unknown[] = []
let attempt = 0
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof AllocateAddressCommand) return {AllocationId: 'eipalloc-0a1b2c3d', PublicIp: '1.2.3.4'}
if (cmd instanceof CreateNatGatewayCommand) return {NatGateway: {NatGatewayId: 'nat-0a1b2c3d', SubnetId: 'subnet-1', VpcId: 'vpc-1'}}
if (cmd instanceof DescribeNatGatewaysCommand) {
attempt++
return {NatGateways: [{NatGatewayId: 'nat-0a1b2c3d', SubnetId: 'subnet-1', VpcId: 'vpc-1', State: attempt >= 1 ? 'available' : 'pending'}]}
}
return {}
},
} as unknown as EC2Client
const result = await createEc2Service(client).createNatGateway({subnetId: 'subnet-1'})
expect(sent[0]).toBeInstanceOf(AllocateAddressCommand)
expect(sent[1]).toBeInstanceOf(CreateNatGatewayCommand)
expect(result.natGatewayId).toBe('nat-0a1b2c3d')
})
test('deleteNatGateway sends DeleteNatGatewayCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof DescribeNatGatewaysCommand) return {NatGateways: [{NatGatewayId: 'nat-0a1b2c3d', State: 'deleted'}]}
return {}
},
} as unknown as EC2Client
await createEc2Service(client).deleteNatGateway('nat-0a1b2c3d')
expect(sent[0]).toBeInstanceOf(DeleteNatGatewayCommand)
})
})
describe('ec2Service Route Tables', () => {
test('listRouteTables returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeRouteTables is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listRouteTables()).toEqual([])
})
test('createRoute sends CreateRouteCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).createRoute('rtb-0a1b2c3d', {destinationCidrBlock: '0.0.0.0/0', gatewayId: 'igw-0a1b2c3d'})
expect(sent[0]).toBeInstanceOf(CreateRouteCommand)
})
test('deleteRoute sends DeleteRouteCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).deleteRoute('rtb-0a1b2c3d', '0.0.0.0/0')
expect(sent[0]).toBeInstanceOf(DeleteRouteCommand)
})
test('associateRouteTable sends AssociateRouteTableCommand and returns associationId', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => { sent.push(cmd); return {AssociationId: 'rtbassoc-0a1b2c3d'} },
} as unknown as EC2Client
const id = await createEc2Service(client).associateRouteTable('rtb-0a1b2c3d', 'subnet-0a1b2c3d')
expect(sent[0]).toBeInstanceOf(AssociateRouteTableCommand)
expect(id).toBe('rtbassoc-0a1b2c3d')
})
test('disassociateRouteTable sends DisassociateRouteTableCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).disassociateRouteTable('rtbassoc-0a1b2c3d')
expect(sent[0]).toBeInstanceOf(DisassociateRouteTableCommand)
})
})
describe('ec2Service Elastic IPs', () => {
test('listElasticIps returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeAddresses is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listElasticIps()).toEqual([])
})
test('allocateElasticIp sends AllocateAddressCommand and returns eip', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof AllocateAddressCommand) return {AllocationId: 'eipalloc-0a1b2c3d', PublicIp: '54.0.0.1', Domain: 'vpc'}
return {}
},
} as unknown as EC2Client
const result = await createEc2Service(client).allocateElasticIp()
expect(sent[0]).toBeInstanceOf(AllocateAddressCommand)
expect(result.allocationId).toBe('eipalloc-0a1b2c3d')
expect(result.publicIp).toBe('54.0.0.1')
})
test('releaseElasticIp sends ReleaseAddressCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).releaseElasticIp('eipalloc-0a1b2c3d')
expect(sent[0]).toBeInstanceOf(ReleaseAddressCommand)
})
test('associateElasticIp sends AssociateAddressCommand and returns associationId', async () => {
const sent: unknown[] = []
const client: EC2Client = {
send: async (cmd: unknown) => {
sent.push(cmd)
if (cmd instanceof AssociateAddressCommand) return {AssociationId: 'eipassoc-test'}
return {}
},
} as unknown as EC2Client
const result = await createEc2Service(client).associateElasticIp('eipalloc-0a1b2c3d', 'i-test1')
expect(sent[0]).toBeInstanceOf(AssociateAddressCommand)
expect(result.associationId).toBe('eipassoc-test')
})
test('disassociateElasticIp sends DisassociateAddressCommand', async () => {
const sent: unknown[] = []
const client: EC2Client = {send: async (cmd: unknown) => { sent.push(cmd); return {} }} as unknown as EC2Client
await createEc2Service(client).disassociateElasticIp('eipassoc-test')
expect(sent[0]).toBeInstanceOf(DisassociateAddressCommand)
})
})
describe('ec2Service metadata', () => {
test('listAvailabilityZones returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeAvailabilityZones is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listAvailabilityZones()).toEqual([])
})
test('listAvailabilityZones returns mapped zones', async () => {
const client = fakeClient({
DescribeAvailabilityZonesCommand: {AvailabilityZones: [{ZoneName: 'us-east-1a', ZoneId: 'use1-az1', State: 'available'}]},
})
const result = await createEc2Service(client).listAvailabilityZones()
expect(result).toHaveLength(1)
expect(result[0].zoneName).toBe('us-east-1a')
expect(result[0].zoneId).toBe('use1-az1')
})
test('listInstanceTypes returns empty array when unsupported', async () => {
const client: EC2Client = {send: async () => { throw new Error('DescribeInstanceTypes is not supported') }} as unknown as EC2Client
expect(await createEc2Service(client).listInstanceTypes()).toEqual([])
})
test('listInstanceTypes returns mapped types', async () => {
const client = fakeClient({
DescribeInstanceTypesCommand: {InstanceTypes: [{InstanceType: 't3.micro', VCpuInfo: {DefaultVCpus: 2}, MemoryInfo: {SizeInMiB: 1024}}]},
})
const result = await createEc2Service(client).listInstanceTypes()
expect(result).toHaveLength(1)
expect(result[0].instanceType).toBe('t3.micro')
expect(result[0].vcpu).toBe(2)
expect(result[0].memoryMiB).toBe(1024)
})
})
// Suppress unused import warnings
void DescribeKeyPairsCommand
void DescribeSecurityGroupsCommand
void DescribeSubnetsCommand
void DescribeVpcAttributeCommand
void DescribeVpcsCommand
void DescribeInternetGatewaysCommand
void DescribeNatGatewaysCommand
void DescribeRouteTablesCommand
void DescribeAddressesCommand
void CreateRouteTableCommand
void ReleaseAddressCommand