import {describe, expect, test} from 'bun:test' import {CreateClusterCommand, CreateServiceCommand, DeleteServiceCommand, DescribeClustersCommand, DescribeServicesCommand, DescribeTaskDefinitionCommand, DescribeTasksCommand, ListClustersCommand, ListServicesCommand, ListTaskDefinitionsCommand, ListTasksCommand, RegisterTaskDefinitionCommand, type ECSClient} from '@aws-sdk/client-ecs' import {DescribeLoadBalancersCommand, DescribeTargetGroupsCommand, DescribeTargetHealthCommand, type ElasticLoadBalancingV2Client} from '@aws-sdk/client-elastic-load-balancing-v2' import {AwsEcsAdapter} from './AwsEcsAdapter' import {AwsFargateAdapter} from './AwsFargateAdapter' const cluster = 'orders-cluster' describe('AwsEcsAdapter', () => { test('lists EC2 and Fargate services with task, IP target, and ALB health', async () => { const ecs = {async send(command: object) { if (command instanceof ListClustersCommand) return {clusterArns: [cluster]} if (command instanceof ListServicesCommand) return {serviceArns: ['orders', 'fargate']} if (command instanceof DescribeServicesCommand) return {services: [ {serviceName: 'orders', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/orders', launchType: 'EC2', status: 'ACTIVE', desiredCount: 2, runningCount: 1, pendingCount: 1, taskDefinition: 'orders:4'}, {serviceName: 'fargate', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/fargate', launchType: 'FARGATE', status: 'ACTIVE', desiredCount: 1, runningCount: 1, taskDefinition: 'fargate:1', networkConfiguration: {awsvpcConfiguration: {subnets: ['subnet-1'], securityGroups: ['sg-1'], assignPublicIp: 'ENABLED'}}, loadBalancers: [{targetGroupArn: 'tg-1', containerName: 'app', containerPort: 8080}]}, ]} if (command instanceof ListTasksCommand) return {taskArns: ['task-1']} if (command instanceof DescribeTasksCommand) return {tasks: [{taskArn: 'task-1', lastStatus: 'RUNNING', healthStatus: 'HEALTHY', connectivity: 'CONNECTED'}]} return {} }} as unknown as ECSClient const elb = {async send(command: object) { if (command instanceof DescribeTargetGroupsCommand) return {TargetGroups: [{TargetType: 'ip', LoadBalancerArns: ['alb-1']}]} if (command instanceof DescribeTargetHealthCommand) return {TargetHealthDescriptions: [{Target: {Id: '10.0.1.5', Port: 8080}, TargetHealth: {State: 'healthy'}}]} if (command instanceof DescribeLoadBalancersCommand) return {LoadBalancers: [{DNSName: 'orders.example.test', Scheme: 'internet-facing', State: {Code: 'active'}}]} return {} }} as unknown as ElasticLoadBalancingV2Client const services = await new AwsEcsAdapter(ecs, elb).list() expect(services).toHaveLength(2) expect(services[0]).toMatchObject({metadata: {launchType: 'EC2', tasks: [{healthStatus: 'HEALTHY'}]}}) expect(services[1]).toMatchObject({metadata: {launchType: 'FARGATE', networkConfiguration: {subnets: ['subnet-1']}, loadBalancers: [{targetType: 'ip', targets: [{state: 'healthy'}], endpoints: [{dnsName: 'orders.example.test', state: 'active'}]}]}}) }) test('creates an awsvpc Fargate service and force deletes it', async () => { const sent: object[] = [] const ecs = {async send(command: object) { sent.push(command) if (command instanceof CreateServiceCommand) return {service: {serviceName: 'orders', serviceArn: 'arn:aws:ecs:us-east-1:000000000000:service/orders', launchType: 'FARGATE', status: 'ACTIVE', desiredCount: 1, runningCount: 0, taskDefinition: 'orders:1'}} if (command instanceof ListTasksCommand) return {taskArns: []} return {} }} as unknown as ECSClient const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client const adapter = new AwsEcsAdapter(ecs, elb) await adapter.create({values: {cluster, serviceName: 'orders', taskDefinition: 'orders:1', launchType: 'FARGATE', deploymentController: 'CODE_DEPLOY', subnets: 'subnet-1, subnet-2', securityGroups: 'sg-1', assignPublicIp: 'ENABLED', targetGroupArn: 'tg-1', containerName: 'app', containerPort: '8080'}}) await adapter.delete(`${cluster}|orders`) expect((sent[0] as CreateServiceCommand).input).toMatchObject({cluster, serviceName: 'orders', launchType: 'FARGATE', deploymentController: {type: 'CODE_DEPLOY'}, networkConfiguration: {awsvpcConfiguration: {subnets: ['subnet-1', 'subnet-2'], securityGroups: ['sg-1'], assignPublicIp: 'ENABLED'}}, loadBalancers: [{targetGroupArn: 'tg-1', containerName: 'app', containerPort: 8080}]}) expect((sent.at(-1) as DeleteServiceCommand).input).toEqual({cluster, service: 'orders', force: true}) }) test('creates and lists clusters through ECS APIs', async () => { const sent: object[] = [] const clusterArn = 'arn:aws:ecs:us-east-1:000000000000:cluster/orders' const ecs = {async send(command: object) { sent.push(command) if (command instanceof CreateClusterCommand) return {cluster: {clusterName: 'orders', clusterArn, status: 'ACTIVE'}} if (command instanceof ListClustersCommand) return {clusterArns: [clusterArn]} if (command instanceof DescribeClustersCommand) return {clusters: [{clusterName: 'orders', clusterArn, status: 'ACTIVE', runningTasksCount: 2}]} if (command instanceof ListServicesCommand || command instanceof ListTaskDefinitionsCommand) return {} return {} }} as unknown as ECSClient const adapter = new AwsEcsAdapter(ecs, {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client) const created = await adapter.create({values: {resourceKind: 'cluster', clusterName: 'orders'}}) const resources = await adapter.list() expect(sent[0]).toBeInstanceOf(CreateClusterCommand) expect((sent[0] as CreateClusterCommand).input).toEqual({clusterName: 'orders'}) expect(created).toMatchObject({id: `cluster:${clusterArn}`, type: 'ecs-cluster', name: 'orders'}) expect(resources).toContainEqual(expect.objectContaining({type: 'ecs-cluster', name: 'orders', metadata: expect.objectContaining({runningTasksCount: 2})})) }) test('registers and lists only awsvpc Fargate task definitions', async () => { const sent: object[] = [] const fargateArn = 'arn:aws:ecs:us-east-1:000000000000:task-definition/orders:1' const ec2Arn = 'arn:aws:ecs:us-east-1:000000000000:task-definition/worker:1' const fargate = {taskDefinitionArn: fargateArn, family: 'orders', revision: 1, status: 'ACTIVE', cpu: '256', memory: '512', networkMode: 'awsvpc', requiresCompatibilities: ['FARGATE'], containerDefinitions: [{name: 'web', image: 'orders:latest', portMappings: [{containerPort: 8080}]}]} const ecs = {async send(command: object) { sent.push(command) if (command instanceof RegisterTaskDefinitionCommand) return {taskDefinition: fargate} if (command instanceof ListClustersCommand) return {} if (command instanceof ListTaskDefinitionsCommand) return {taskDefinitionArns: [fargateArn, ec2Arn]} if (command instanceof DescribeTaskDefinitionCommand) return {taskDefinition: command.input.taskDefinition === fargateArn ? fargate : {taskDefinitionArn: ec2Arn, family: 'worker', networkMode: 'bridge', requiresCompatibilities: ['EC2']}} return {} }} as unknown as ECSClient const adapter = new AwsEcsAdapter(ecs, {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client) const created = await adapter.create({values: {resourceKind: 'taskDefinition', family: 'orders', image: 'orders:latest', containerName: 'web', containerPort: '8080', cpu: '256', memory: '512'}}) const resources = await adapter.list() expect((sent[0] as RegisterTaskDefinitionCommand).input).toEqual({family: 'orders', cpu: '256', memory: '512', networkMode: 'awsvpc', requiresCompatibilities: ['FARGATE'], containerDefinitions: [{name: 'web', image: 'orders:latest', essential: true, portMappings: [{containerPort: 8080, protocol: 'tcp'}]}]}) expect(created).toMatchObject({type: 'ecs-task-definition', name: 'orders:1', metadata: {image: 'orders:latest', containerPort: 8080}}) expect(resources).toHaveLength(1) expect(resources[0]).toMatchObject({id: `task-definition:${fargateArn}`, type: 'ecs-task-definition'}) }) test('does not present stale inactive counts as running', async () => { const ecs = {async send(command: object) { if (command instanceof DescribeServicesCommand) return {services: [{serviceName: 'orders', status: 'INACTIVE', desiredCount: 2, runningCount: 2, pendingCount: 1, deployments: [{desiredCount: 2, runningCount: 2}]}]} return {} }} as unknown as ECSClient const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client const service = await new AwsEcsAdapter(ecs, elb).get(`${cluster}|orders`) expect(service).toMatchObject({metadata: {inactive: true, desiredCount: 0, runningCount: 0, pendingCount: 0, deployments: [{desiredCount: 0, runningCount: 0}]}}) }) test('Fargate adapter lists and creates only Fargate services', async () => { const sent: object[] = [] const ecs = {async send(command: object) { sent.push(command) if (command instanceof ListClustersCommand) return {clusterArns: [cluster]} if (command instanceof ListServicesCommand) return {serviceArns: ['ec2', 'fargate']} if (command instanceof DescribeServicesCommand) return {services: [ {serviceName: 'ec2', launchType: 'EC2', status: 'ACTIVE'}, {serviceName: 'fargate', launchType: 'FARGATE', status: 'ACTIVE'}, ]} if (command instanceof CreateServiceCommand) return {service: {serviceName: 'created', launchType: 'FARGATE', status: 'ACTIVE'}} if (command instanceof ListTasksCommand) return {taskArns: []} return {} }} as unknown as ECSClient const elb = {async send() { return {} }} as unknown as ElasticLoadBalancingV2Client const adapter = new AwsFargateAdapter(new AwsEcsAdapter(ecs, elb)) const services = await adapter.list() const created = await adapter.create({values: {cluster, serviceName: 'created', taskDefinition: 'created:1', launchType: 'EC2', subnets: 'subnet-1'}}) expect(services).toHaveLength(1) expect(services[0]).toMatchObject({service: 'fargate', name: 'fargate', metadata: {launchType: 'FARGATE'}}) expect(created).toMatchObject({service: 'fargate', metadata: {launchType: 'FARGATE'}}) expect(sent.find((command) => command instanceof CreateServiceCommand)).toMatchObject({input: {launchType: 'FARGATE'}}) expect(adapter.schema().fields.find((field) => field.name === 'launchType')).toBeUndefined() expect(adapter.schema().fields.find((field) => field.name === 'subnets')).toMatchObject({required: true}) }) })