feat: ElastiCache
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import {describe, expect, test} from 'bun:test'
|
||||
import {
|
||||
CreateCacheClusterCommand,
|
||||
CreateReplicationGroupCommand,
|
||||
DeleteCacheClusterCommand,
|
||||
DeleteReplicationGroupCommand,
|
||||
DescribeCacheClustersCommand,
|
||||
DescribeReplicationGroupsCommand,
|
||||
type ElastiCacheClient,
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import {ValidationError} from '../cloud-spi/errors'
|
||||
import {AwsElastiCacheAdapter} from './AwsElastiCacheAdapter'
|
||||
|
||||
type ElastiCacheCommand =
|
||||
| CreateCacheClusterCommand
|
||||
| CreateReplicationGroupCommand
|
||||
| DeleteCacheClusterCommand
|
||||
| DeleteReplicationGroupCommand
|
||||
| DescribeCacheClustersCommand
|
||||
| DescribeReplicationGroupsCommand
|
||||
|
||||
function recordingClient(responder: (command: ElastiCacheCommand) => unknown | Promise<unknown>) {
|
||||
const commands: ElastiCacheCommand[] = []
|
||||
const client = {
|
||||
async send(command: ElastiCacheCommand) {
|
||||
commands.push(command)
|
||||
return responder(command)
|
||||
},
|
||||
} as unknown as ElastiCacheClient
|
||||
return {client, commands}
|
||||
}
|
||||
|
||||
describe('AwsElastiCacheAdapter', () => {
|
||||
test('lists replication groups and cache clusters with native resource metadata', async () => {
|
||||
const {client} = recordingClient((command) => {
|
||||
if (command instanceof DescribeReplicationGroupsCommand) return {ReplicationGroups: [{
|
||||
ReplicationGroupId: 'orders', ARN: 'arn:aws:elasticache:us-east-1:000000000000:replicationgroup:orders',
|
||||
Status: 'available', Engine: 'valkey', CacheNodeType: 'cache.t3.micro', ClusterEnabled: true,
|
||||
ConfigurationEndpoint: {Address: 'orders.cfg.local', Port: 6379},
|
||||
NodeGroups: [{NodeGroupId: '0001', PrimaryEndpoint: {Address: 'orders-001.local', Port: 6379}}],
|
||||
}]}
|
||||
return {CacheClusters: [{
|
||||
CacheClusterId: 'sessions', ARN: 'arn:aws:elasticache:us-east-1:000000000000:cluster:sessions',
|
||||
CacheClusterStatus: 'available', Engine: 'memcached', EngineVersion: '1.6.22', CacheNodeType: 'cache.t3.micro',
|
||||
CacheClusterCreateTime: new Date('2026-10-05T10:00:00.000Z'), NumCacheNodes: 1,
|
||||
CacheNodes: [{CacheNodeId: '0001', Endpoint: {Address: 'sessions.local', Port: 11211}}],
|
||||
}]}
|
||||
})
|
||||
|
||||
const resources = await new AwsElastiCacheAdapter(client).list()
|
||||
|
||||
expect(resources).toEqual([
|
||||
expect.objectContaining({id: 'orders', type: 'replication-group', engine: 'valkey', status: 'available', region: 'us-east-1'}),
|
||||
expect.objectContaining({id: 'sessions', type: 'cache-cluster', engine: 'memcached', version: '1.6.22', createdAt: '2026-10-05T10:00:00.000Z'}),
|
||||
])
|
||||
expect(resources[0]?.metadata).toMatchObject({endpoint: 'orders.cfg.local', clusterEnabled: true, nodeGroups: [{NodeGroupId: '0001'}]})
|
||||
expect(resources[1]?.metadata).toMatchObject({endpoint: 'sessions.local', numCacheNodes: 1})
|
||||
})
|
||||
|
||||
test('creates a cluster-mode replication group with its AWS topology input', async () => {
|
||||
const {client, commands} = recordingClient((command) => {
|
||||
expect(command).toBeInstanceOf(CreateReplicationGroupCommand)
|
||||
return {ReplicationGroup: {
|
||||
ReplicationGroupId: 'orders', ReplicationGroupDescription: 'Order cache', Engine: 'redis',
|
||||
CacheNodeType: 'cache.t3.micro', Status: 'creating',
|
||||
}}
|
||||
})
|
||||
|
||||
const resource = await new AwsElastiCacheAdapter(client).create({values: {
|
||||
kind: 'replication-group', id: ' orders ', description: ' Order cache ', engine: 'redis',
|
||||
nodeType: 'cache.t3.micro', port: '6379', clusterMode: 'enabled', numNodeGroups: '2', replicasPerNodeGroup: '1',
|
||||
}})
|
||||
|
||||
expect((commands[0] as CreateReplicationGroupCommand).input).toEqual({
|
||||
ReplicationGroupId: 'orders', ReplicationGroupDescription: 'Order cache', Engine: 'redis',
|
||||
CacheNodeType: 'cache.t3.micro', Port: 6379, NumNodeGroups: 2, ReplicasPerNodeGroup: 1,
|
||||
})
|
||||
expect(resource).toMatchObject({id: 'orders', type: 'replication-group', status: 'creating'})
|
||||
})
|
||||
|
||||
test('creates a cache cluster with the cache-cluster AWS contract', async () => {
|
||||
const {client, commands} = recordingClient((command) => {
|
||||
expect(command).toBeInstanceOf(CreateCacheClusterCommand)
|
||||
return {CacheCluster: {CacheClusterId: 'sessions', Engine: 'memcached', CacheNodeType: 'cache.t3.micro', CacheClusterStatus: 'creating'}}
|
||||
})
|
||||
|
||||
await new AwsElastiCacheAdapter(client).create({values: {
|
||||
kind: 'cache-cluster', id: 'sessions', engine: 'memcached', nodeType: 'cache.t3.micro', numCacheNodes: '2',
|
||||
}})
|
||||
|
||||
expect((commands[0] as CreateCacheClusterCommand).input).toEqual({
|
||||
CacheClusterId: 'sessions', Engine: 'memcached', CacheNodeType: 'cache.t3.micro', NumCacheNodes: 2,
|
||||
})
|
||||
})
|
||||
|
||||
test('rejects invalid resource combinations before sending a command', async () => {
|
||||
const {client, commands} = recordingClient(() => ({}))
|
||||
const adapter = new AwsElastiCacheAdapter(client)
|
||||
|
||||
await expect(adapter.create({values: {
|
||||
kind: 'replication-group', id: 'orders', description: 'Orders', engine: 'memcached', nodeType: 'cache.t3.micro',
|
||||
}})).rejects.toBeInstanceOf(ValidationError)
|
||||
await expect(adapter.create({values: {
|
||||
kind: 'cache-cluster', id: 'orders--cache', engine: 'redis', nodeType: 'cache.t3.micro',
|
||||
}})).rejects.toBeInstanceOf(ValidationError)
|
||||
expect(commands).toHaveLength(0)
|
||||
})
|
||||
|
||||
test('deletes replication groups and cache clusters through their respective commands', async () => {
|
||||
const {client, commands} = recordingClient((command) => {
|
||||
if (command instanceof DescribeReplicationGroupsCommand && command.input.ReplicationGroupId === 'orders') {
|
||||
return {ReplicationGroups: [{ReplicationGroupId: 'orders'}]}
|
||||
}
|
||||
if (command instanceof DescribeReplicationGroupsCommand) {
|
||||
throw Object.assign(new Error('not found'), {name: 'ReplicationGroupNotFoundFault'})
|
||||
}
|
||||
if (command instanceof DescribeCacheClustersCommand) return {CacheClusters: [{CacheClusterId: 'sessions'}]}
|
||||
return {}
|
||||
})
|
||||
const adapter = new AwsElastiCacheAdapter(client)
|
||||
|
||||
await adapter.delete('orders')
|
||||
await adapter.delete('sessions')
|
||||
|
||||
expect(commands.filter((command) => command instanceof DeleteReplicationGroupCommand)[0]?.input).toEqual({
|
||||
ReplicationGroupId: 'orders', RetainPrimaryCluster: false,
|
||||
})
|
||||
expect(commands.filter((command) => command instanceof DeleteCacheClusterCommand)[0]?.input).toEqual({CacheClusterId: 'sessions'})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user