import {describe, expect, test} from 'bun:test' import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, ListTopicsCommand, PublishCommand, SubscribeCommand, type SNSClient, UnsubscribeCommand, } from '@aws-sdk/client-sns' import {AwsSnsAdapter} from './AwsSnsAdapter' import {ValidationError} from '../cloud-spi/errors' const ORDERS_ARN = 'arn:aws:sns:us-east-1:000000000000:orders' const ALERTS_ARN = 'arn:aws:sns:us-east-1:000000000000:alerts' function stubSns() { const sent: object[] = [] const client = { async send(command: object) { sent.push(command) if (command instanceof ListTopicsCommand) return {Topics: [{TopicArn: ORDERS_ARN}, {TopicArn: ALERTS_ARN}]} if (command instanceof GetTopicAttributesCommand) { return {Attributes: {DisplayName: 'Order notifications', FifoTopic: 'false'}} } if (command instanceof CreateTopicCommand) { return {TopicArn: `arn:aws:sns:us-east-1:000000000000:${command.input.Name}`} } if (command instanceof PublishCommand) return {MessageId: 'e5a98c5b-cd94-4c38-a9ac-7260ec3e575f'} if (command instanceof ListSubscriptionsByTopicCommand) return {Subscriptions: [{ SubscriptionArn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-1', Protocol: 'sqs', Endpoint: 'arn:aws:sqs:us-east-1:000000000000:orders', }]} if (command instanceof GetSubscriptionAttributesCommand) return {Attributes: {FilterPolicy: '{"eventType":["created"]}'}} if (command instanceof SubscribeCommand) return {SubscriptionArn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-2'} return {} }, } as unknown as SNSClient return {client, sent} } describe('AwsSnsAdapter', () => { test('lists SNS topic ARNs as topic resources', async () => { const {client} = stubSns() const [topic] = await new AwsSnsAdapter(client).list() expect(topic).toMatchObject({id: ORDERS_ARN, name: 'orders', service: 'notifications', type: 'topic'}) expect(topic?.metadata.arn).toBe(ORDERS_ARN) }) test('gets a topic with its SNS attributes', async () => { const {client, sent} = stubSns() const topic = await new AwsSnsAdapter(client).get(ORDERS_ARN) expect(sent[0]).toBeInstanceOf(GetTopicAttributesCommand) expect(topic?.metadata.displayName).toBe('Order notifications') }) test('creates and reads back an SNS topic', async () => { const {client, sent} = stubSns() const topic = await new AwsSnsAdapter(client).create({values: {name: 'orders'}}) expect(sent[0]).toBeInstanceOf(CreateTopicCommand) expect((sent[0] as CreateTopicCommand).input.Name).toBe('orders') expect(sent[1]).toBeInstanceOf(GetTopicAttributesCommand) expect(topic.id).toBe(ORDERS_ARN) }) test('deletes a topic by ARN', async () => { const {client, sent} = stubSns() await new AwsSnsAdapter(client).delete(ORDERS_ARN) expect((sent[0] as DeleteTopicCommand).input.TopicArn).toBe(ORDERS_ARN) }) test('publishes a message with optional subject and string message attributes', async () => { const {client, sent} = stubSns() const result = await new AwsSnsAdapter(client).publish(ORDERS_ARN, { message: 'Order placed', subject: 'Order update', messageAttributes: {orderId: '1234'}, }) expect(result.messageId).toBe('e5a98c5b-cd94-4c38-a9ac-7260ec3e575f') expect((sent[0] as PublishCommand).input).toEqual({ TopicArn: ORDERS_ARN, Message: 'Order placed', Subject: 'Order update', MessageAttributes: {orderId: {DataType: 'String', StringValue: '1234'}}, }) }) test('manages SNS subscriptions with direct SDK commands and exposes their attributes', async () => { const {client, sent} = stubSns() const adapter = new AwsSnsAdapter(client) const created = await adapter.createSnsSubscription(ORDERS_ARN, { protocol: 'sqs', endpoint: 'arn:aws:sqs:us-east-1:000000000000:orders', }) const subscriptions = await adapter.listSnsSubscriptions(ORDERS_ARN) await adapter.deleteSnsSubscription('arn:aws:sns:us-east-1:000000000000:orders:sub-1') expect((sent[0] as SubscribeCommand).input).toEqual({ TopicArn: ORDERS_ARN, Protocol: 'sqs', Endpoint: 'arn:aws:sqs:us-east-1:000000000000:orders', }) expect((sent[1] as ListSubscriptionsByTopicCommand).input).toEqual({TopicArn: ORDERS_ARN}) expect((sent[2] as GetSubscriptionAttributesCommand).input).toEqual({ SubscriptionArn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-1', }) expect((sent[3] as UnsubscribeCommand).input).toEqual({ SubscriptionArn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-1', }) expect(created).toMatchObject({arn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-2', protocol: 'sqs', status: 'Confirmed'}) expect(subscriptions).toEqual([{ arn: 'arn:aws:sns:us-east-1:000000000000:orders:sub-1', protocol: 'sqs', endpoint: 'arn:aws:sqs:us-east-1:000000000000:orders', status: 'Confirmed', filterPolicy: '{"eventType":["created"]}', }]) }) test('rejects invalid topic names and blank messages before calling SNS', async () => { const {client, sent} = stubSns() const adapter = new AwsSnsAdapter(client) await expect(adapter.create({values: {name: 'has space'}})).rejects.toBeInstanceOf(ValidationError) await expect(adapter.publish(ORDERS_ARN, {message: ''})).rejects.toBeInstanceOf(ValidationError) expect(sent).toEqual([]) }) })