@geekmidas/events 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BasicConnection-C4_Y0nIj.d.mts → BasicConnection-BTRsXiuH.d.cts} +3 -2
- package/dist/BasicConnection-BTRsXiuH.d.cts.map +1 -0
- package/dist/BasicConnection-C-Cg2FJT.cjs.map +1 -1
- package/dist/BasicConnection-CpwybcC9.mjs.map +1 -1
- package/dist/{BasicConnection-CddnULuT.d.cts → BasicConnection-DIxtA7jK.d.mts} +3 -2
- package/dist/BasicConnection-DIxtA7jK.d.mts.map +1 -0
- package/dist/BasicPublisher-CXAVKxgq.cjs.map +1 -1
- package/dist/{BasicPublisher-DCBxaTwC.d.mts → BasicPublisher-CpJFK2oS.d.mts} +5 -3
- package/dist/BasicPublisher-CpJFK2oS.d.mts.map +1 -0
- package/dist/{BasicPublisher-V5hELHG9.d.cts → BasicPublisher-D1q0a2xp.d.cts} +5 -3
- package/dist/BasicPublisher-D1q0a2xp.d.cts.map +1 -0
- package/dist/BasicPublisher-DogbeHAH.mjs.map +1 -1
- package/dist/{BasicSubscriber-BB3aKeph.d.mts → BasicSubscriber-6w0Af4DD.d.mts} +5 -3
- package/dist/BasicSubscriber-6w0Af4DD.d.mts.map +1 -0
- package/dist/{BasicSubscriber-CCKfbRMo.mjs → BasicSubscriber-BJSZN22M.mjs} +2 -4
- package/dist/BasicSubscriber-BJSZN22M.mjs.map +1 -0
- package/dist/{BasicSubscriber-DOwgFw5j.d.cts → BasicSubscriber-DXxQewjc.d.cts} +5 -3
- package/dist/BasicSubscriber-DXxQewjc.d.cts.map +1 -0
- package/dist/{BasicSubscriber-BGqv3c8x.cjs → BasicSubscriber-mVJRMXhZ.cjs} +2 -4
- package/dist/BasicSubscriber-mVJRMXhZ.cjs.map +1 -0
- package/dist/{EventConnection-DZhn1Msl.d.cts → EventConnection-BaJRXdg7.d.cts} +2 -2
- package/dist/EventConnection-BaJRXdg7.d.cts.map +1 -0
- package/dist/EventConnection-ClLMoIUU.mjs.map +1 -1
- package/dist/EventConnection-F_N81SXu.cjs.map +1 -1
- package/dist/{EventConnection-DeXfLGom.d.mts → EventConnection-SOhtmxib.d.mts} +2 -2
- package/dist/EventConnection-SOhtmxib.d.mts.map +1 -0
- package/dist/EventConnection.d.cts +2 -2
- package/dist/EventConnection.d.mts +2 -2
- package/dist/{Publisher-C3m8JFXm.d.mts → Publisher-1CfcYRnm.d.mts} +4 -2
- package/dist/Publisher-1CfcYRnm.d.mts.map +1 -0
- package/dist/{Publisher-nXNVjL0e.d.cts → Publisher-BfNgvHwQ.d.cts} +4 -2
- package/dist/Publisher-BfNgvHwQ.d.cts.map +1 -0
- package/dist/Publisher-DS0aOh8r.mjs.map +1 -1
- package/dist/Publisher-ceu6OxCj.cjs.map +1 -1
- package/dist/Publisher.d.cts +2 -2
- package/dist/Publisher.d.mts +2 -2
- package/dist/{RabbitMQConnection-ungB0Zl6.cjs → RabbitMQConnection-Blj9Lo05.cjs} +2 -2
- package/dist/RabbitMQConnection-Blj9Lo05.cjs.map +1 -0
- package/dist/{RabbitMQConnection-7cWp_-_Q.d.cts → RabbitMQConnection-Bm8VCI2R.d.cts} +3 -2
- package/dist/RabbitMQConnection-Bm8VCI2R.d.cts.map +1 -0
- package/dist/{RabbitMQConnection-C2HC7J7K.d.mts → RabbitMQConnection-Ck-ktCp_.d.mts} +3 -2
- package/dist/RabbitMQConnection-Ck-ktCp_.d.mts.map +1 -0
- package/dist/{RabbitMQConnection-kU3MvN5P.mjs → RabbitMQConnection-Co7f5Xsx.mjs} +2 -2
- package/dist/RabbitMQConnection-Co7f5Xsx.mjs.map +1 -0
- package/dist/RabbitMQPublisher--DL6GM6g.cjs.map +1 -1
- package/dist/{RabbitMQPublisher-CpcbgrXj.d.cts → RabbitMQPublisher-COnxcWEZ.d.mts} +5 -3
- package/dist/RabbitMQPublisher-COnxcWEZ.d.mts.map +1 -0
- package/dist/RabbitMQPublisher-o_oVI3wr.mjs.map +1 -1
- package/dist/{RabbitMQPublisher-DDZi15Ro.d.mts → RabbitMQPublisher-qQT7-1-p.d.cts} +5 -3
- package/dist/RabbitMQPublisher-qQT7-1-p.d.cts.map +1 -0
- package/dist/{RabbitMQSubscriber-BplEJEk8.cjs → RabbitMQSubscriber-C8MYAihn.cjs} +2 -3
- package/dist/RabbitMQSubscriber-C8MYAihn.cjs.map +1 -0
- package/dist/{RabbitMQSubscriber-BJ9PTGny.d.cts → RabbitMQSubscriber-C_sZjDPU.d.mts} +5 -3
- package/dist/RabbitMQSubscriber-C_sZjDPU.d.mts.map +1 -0
- package/dist/{RabbitMQSubscriber-CIvIw8MG.d.mts → RabbitMQSubscriber-D12sssMb.d.cts} +5 -3
- package/dist/RabbitMQSubscriber-D12sssMb.d.cts.map +1 -0
- package/dist/{RabbitMQSubscriber-BaXqs5nO.mjs → RabbitMQSubscriber-DlGL0pC2.mjs} +2 -3
- package/dist/RabbitMQSubscriber-DlGL0pC2.mjs.map +1 -0
- package/dist/{SNSConnection-CAImaMM2.d.mts → SNSConnection-BDfQrVBO.d.cts} +3 -2
- package/dist/SNSConnection-BDfQrVBO.d.cts.map +1 -0
- package/dist/SNSConnection-D7wCf9Si.mjs.map +1 -1
- package/dist/SNSConnection-DVsC_pbB.cjs.map +1 -1
- package/dist/{SNSConnection-Cqbr7-Ho.d.cts → SNSConnection-mHb10nQ6.d.mts} +3 -2
- package/dist/SNSConnection-mHb10nQ6.d.mts.map +1 -0
- package/dist/{SNSPublisher-BZ6N3JI9.cjs → SNSPublisher-BoA8uLnw.cjs} +2 -3
- package/dist/SNSPublisher-BoA8uLnw.cjs.map +1 -0
- package/dist/{SNSPublisher-Dkhb4LU-.d.mts → SNSPublisher-CM7UAWtf.d.mts} +7 -6
- package/dist/SNSPublisher-CM7UAWtf.d.mts.map +1 -0
- package/dist/{SNSPublisher-BfWjzNiB.mjs → SNSPublisher-Drr4kHn6.mjs} +2 -3
- package/dist/SNSPublisher-Drr4kHn6.mjs.map +1 -0
- package/dist/{SNSPublisher-RVZFa7kL.d.cts → SNSPublisher-kpsSlGDn.d.cts} +7 -6
- package/dist/SNSPublisher-kpsSlGDn.d.cts.map +1 -0
- package/dist/{SNSSubscriber-C9YJWa7m.mjs → SNSSubscriber-CBIvQpqK.mjs} +4 -8
- package/dist/SNSSubscriber-CBIvQpqK.mjs.map +1 -0
- package/dist/{SNSSubscriber-UE0ia4XM.cjs → SNSSubscriber-CWiAJjpU.cjs} +4 -8
- package/dist/SNSSubscriber-CWiAJjpU.cjs.map +1 -0
- package/dist/{SNSSubscriber-B31JmkAE.d.mts → SNSSubscriber-DDB3--pm.d.cts} +4 -3
- package/dist/SNSSubscriber-DDB3--pm.d.cts.map +1 -0
- package/dist/{SNSSubscriber-k2UwKUxu.d.cts → SNSSubscriber-Dwo8391f.d.mts} +4 -3
- package/dist/SNSSubscriber-Dwo8391f.d.mts.map +1 -0
- package/dist/{SQSConnection-C5WFqZW7.d.mts → SQSConnection-B5Ep9Tma.d.cts} +3 -2
- package/dist/SQSConnection-B5Ep9Tma.d.cts.map +1 -0
- package/dist/{SQSConnection-geav2Rsn.d.cts → SQSConnection-BI_xOzyJ.d.mts} +3 -2
- package/dist/SQSConnection-BI_xOzyJ.d.mts.map +1 -0
- package/dist/SQSConnection-BwNjUPr4.cjs.map +1 -1
- package/dist/SQSConnection-Eleqqre3.mjs.map +1 -1
- package/dist/{SQSPublisher-CdsayOLV.d.mts → SQSPublisher-BN1ds6SN.d.mts} +5 -3
- package/dist/SQSPublisher-BN1ds6SN.d.mts.map +1 -0
- package/dist/{SQSPublisher-Utb_FTe5.d.cts → SQSPublisher-DFTqwzRW.d.cts} +5 -3
- package/dist/SQSPublisher-DFTqwzRW.d.cts.map +1 -0
- package/dist/SQSPublisher-DG9o4ME3.mjs.map +1 -1
- package/dist/SQSPublisher-Dq5353oZ.cjs.map +1 -1
- package/dist/{SQSSubscriber-DZDNgLE5.cjs → SQSSubscriber-B4sRXVl6.cjs} +5 -12
- package/dist/SQSSubscriber-B4sRXVl6.cjs.map +1 -0
- package/dist/{SQSSubscriber-B6iL1EnX.d.cts → SQSSubscriber-BO2me_gs.d.cts} +4 -3
- package/dist/SQSSubscriber-BO2me_gs.d.cts.map +1 -0
- package/dist/{SQSSubscriber-CD66P7hH.mjs → SQSSubscriber-CVGhZ8Nr.mjs} +5 -12
- package/dist/SQSSubscriber-CVGhZ8Nr.mjs.map +1 -0
- package/dist/{SQSSubscriber-DBeE4XAR.d.mts → SQSSubscriber-FyZFfr2-.d.mts} +4 -3
- package/dist/SQSSubscriber-FyZFfr2-.d.mts.map +1 -0
- package/dist/Subscriber-Bd9hb_jp.mjs.map +1 -1
- package/dist/{Subscriber-Cff9EIpv.d.mts → Subscriber-BxT-KzFJ.d.mts} +4 -2
- package/dist/Subscriber-BxT-KzFJ.d.mts.map +1 -0
- package/dist/{Subscriber-CpAwcUzb.d.cts → Subscriber-DDD_4Nlx.d.cts} +4 -2
- package/dist/Subscriber-DDD_4Nlx.d.cts.map +1 -0
- package/dist/Subscriber-saVOWuMq.cjs.map +1 -1
- package/dist/Subscriber.d.cts +2 -2
- package/dist/Subscriber.d.mts +2 -2
- package/dist/basic/BasicConnection.d.cts +2 -2
- package/dist/basic/BasicConnection.d.mts +2 -2
- package/dist/basic/BasicPublisher.d.cts +3 -3
- package/dist/basic/BasicPublisher.d.mts +3 -3
- package/dist/basic/BasicSubscriber.cjs +1 -1
- package/dist/basic/BasicSubscriber.d.cts +3 -3
- package/dist/basic/BasicSubscriber.d.mts +3 -3
- package/dist/basic/BasicSubscriber.mjs +1 -1
- package/dist/basic/index.cjs +1 -1
- package/dist/basic/index.d.cts +4 -4
- package/dist/basic/index.d.mts +4 -4
- package/dist/basic/index.mjs +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/rabbitmq/RabbitMQConnection.cjs +1 -1
- package/dist/rabbitmq/RabbitMQConnection.d.cts +2 -2
- package/dist/rabbitmq/RabbitMQConnection.d.mts +2 -2
- package/dist/rabbitmq/RabbitMQConnection.mjs +1 -1
- package/dist/rabbitmq/RabbitMQPublisher.d.cts +3 -3
- package/dist/rabbitmq/RabbitMQPublisher.d.mts +3 -3
- package/dist/rabbitmq/RabbitMQSubscriber.cjs +1 -1
- package/dist/rabbitmq/RabbitMQSubscriber.d.cts +3 -3
- package/dist/rabbitmq/RabbitMQSubscriber.d.mts +3 -3
- package/dist/rabbitmq/RabbitMQSubscriber.mjs +1 -1
- package/dist/rabbitmq/index.cjs +2 -2
- package/dist/rabbitmq/index.d.cts +4 -4
- package/dist/rabbitmq/index.d.mts +4 -4
- package/dist/rabbitmq/index.mjs +2 -2
- package/dist/sns/SNSConnection.d.cts +2 -2
- package/dist/sns/SNSConnection.d.mts +2 -2
- package/dist/sns/SNSPublisher.cjs +1 -1
- package/dist/sns/SNSPublisher.d.cts +3 -3
- package/dist/sns/SNSPublisher.d.mts +3 -3
- package/dist/sns/SNSPublisher.mjs +1 -1
- package/dist/sns/SNSSubscriber.cjs +2 -2
- package/dist/sns/SNSSubscriber.d.cts +3 -3
- package/dist/sns/SNSSubscriber.d.mts +3 -3
- package/dist/sns/SNSSubscriber.mjs +2 -2
- package/dist/sns/index.cjs +3 -3
- package/dist/sns/index.d.cts +4 -4
- package/dist/sns/index.d.mts +4 -4
- package/dist/sns/index.mjs +3 -3
- package/dist/sqs/SQSConnection.d.cts +2 -2
- package/dist/sqs/SQSConnection.d.mts +2 -2
- package/dist/sqs/SQSPublisher.d.cts +3 -3
- package/dist/sqs/SQSPublisher.d.mts +3 -3
- package/dist/sqs/SQSSubscriber.cjs +1 -1
- package/dist/sqs/SQSSubscriber.d.cts +3 -3
- package/dist/sqs/SQSSubscriber.d.mts +3 -3
- package/dist/sqs/SQSSubscriber.mjs +1 -1
- package/dist/sqs/index.cjs +1 -1
- package/dist/sqs/index.d.cts +4 -4
- package/dist/sqs/index.d.mts +4 -4
- package/dist/sqs/index.mjs +1 -1
- package/dist/types-B63qHVOG.mjs.map +1 -1
- package/dist/types-CvSMEO5z.cjs.map +1 -1
- package/dist/{types-D0g-o6wX.d.mts → types-DSVsPgKd.d.mts} +2 -1
- package/dist/types-DSVsPgKd.d.mts.map +1 -0
- package/dist/{types-DEGUg5BA.d.cts → types-Dmbp1U-6.d.cts} +2 -1
- package/dist/types-Dmbp1U-6.d.cts.map +1 -0
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +24 -5
- package/src/EventConnection.ts +31 -31
- package/src/Publisher.ts +78 -78
- package/src/Subscriber.ts +91 -91
- package/src/__tests__/EventConnection.spec.ts +42 -0
- package/src/__tests__/Publisher.spec.ts +96 -0
- package/src/__tests__/Subscriber.spec.ts +91 -23
- package/src/basic/BasicConnection.ts +35 -35
- package/src/basic/BasicPublisher.ts +8 -8
- package/src/basic/BasicSubscriber.ts +13 -16
- package/src/basic/__tests__/BasicSubscriber.spec.ts +116 -116
- package/src/index.ts +13 -13
- package/src/rabbitmq/RabbitMQConnection.ts +139 -139
- package/src/rabbitmq/RabbitMQPublisher.ts +48 -48
- package/src/rabbitmq/RabbitMQSubscriber.ts +100 -102
- package/src/rabbitmq/__tests__/RabbitMQPublisher.spec.ts +364 -364
- package/src/rabbitmq/__tests__/RabbitMQSubscriber.spec.ts +204 -204
- package/src/rabbitmq/index.ts +3 -3
- package/src/sns/SNSConnection.ts +76 -76
- package/src/sns/SNSPublisher.ts +54 -56
- package/src/sns/SNSSubscriber.ts +187 -191
- package/src/sns/__tests__/SNS-SQS-Integration.spec.ts +321 -321
- package/src/sns/__tests__/SNSPublisher.spec.ts +449 -0
- package/src/sns/__tests__/SNSSubscriber.spec.ts +527 -0
- package/src/sns/index.ts +3 -3
- package/src/sqs/SQSConnection.ts +75 -75
- package/src/sqs/SQSPublisher.ts +93 -93
- package/src/sqs/SQSSubscriber.ts +157 -165
- package/src/sqs/__tests__/SQSPublisher.spec.ts +178 -178
- package/src/sqs/index.ts +3 -3
- package/src/types.ts +28 -29
- package/tsconfig.json +9 -0
- package/tsdown.config.ts +1 -1
- package/dist/BasicSubscriber-BGqv3c8x.cjs.map +0 -1
- package/dist/BasicSubscriber-CCKfbRMo.mjs.map +0 -1
- package/dist/RabbitMQConnection-kU3MvN5P.mjs.map +0 -1
- package/dist/RabbitMQConnection-ungB0Zl6.cjs.map +0 -1
- package/dist/RabbitMQSubscriber-BaXqs5nO.mjs.map +0 -1
- package/dist/RabbitMQSubscriber-BplEJEk8.cjs.map +0 -1
- package/dist/SNSPublisher-BZ6N3JI9.cjs.map +0 -1
- package/dist/SNSPublisher-BfWjzNiB.mjs.map +0 -1
- package/dist/SNSSubscriber-C9YJWa7m.mjs.map +0 -1
- package/dist/SNSSubscriber-UE0ia4XM.cjs.map +0 -1
- package/dist/SQSSubscriber-CD66P7hH.mjs.map +0 -1
- package/dist/SQSSubscriber-DZDNgLE5.cjs.map +0 -1
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateTopicCommand,
|
|
3
|
+
DeleteTopicCommand,
|
|
4
|
+
SNSClient,
|
|
5
|
+
SubscribeCommand,
|
|
6
|
+
UnsubscribeCommand,
|
|
7
|
+
} from '@aws-sdk/client-sns';
|
|
8
|
+
import {
|
|
9
|
+
CreateQueueCommand,
|
|
10
|
+
DeleteQueueCommand,
|
|
11
|
+
GetQueueAttributesCommand,
|
|
12
|
+
ReceiveMessageCommand,
|
|
13
|
+
SetQueueAttributesCommand,
|
|
14
|
+
SQSClient,
|
|
15
|
+
} from '@aws-sdk/client-sqs';
|
|
16
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
17
|
+
import type { PublishableMessage } from '../../types';
|
|
18
|
+
import { SNSConnection } from '../SNSConnection';
|
|
19
|
+
import { SNSPublisher } from '../SNSPublisher';
|
|
20
|
+
|
|
21
|
+
type TestMessage = PublishableMessage<
|
|
22
|
+
'user.created' | 'user.updated' | 'order.placed',
|
|
23
|
+
any
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
const LOCALSTACK_ENDPOINT = 'http://localhost:4566';
|
|
27
|
+
const AWS_REGION = 'us-east-1';
|
|
28
|
+
const AWS_CREDENTIALS = {
|
|
29
|
+
accessKeyId: 'test',
|
|
30
|
+
secretAccessKey: 'test',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Test infrastructure helpers
|
|
34
|
+
async function createTopic(topicName: string): Promise<string> {
|
|
35
|
+
const client = new SNSClient({
|
|
36
|
+
region: AWS_REGION,
|
|
37
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
38
|
+
credentials: AWS_CREDENTIALS,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const command = new CreateTopicCommand({ Name: topicName });
|
|
42
|
+
const response = await client.send(command);
|
|
43
|
+
client.destroy();
|
|
44
|
+
|
|
45
|
+
if (!response.TopicArn) {
|
|
46
|
+
throw new Error('Failed to create topic');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return response.TopicArn;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function deleteTopic(topicArn: string): Promise<void> {
|
|
53
|
+
const client = new SNSClient({
|
|
54
|
+
region: AWS_REGION,
|
|
55
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
56
|
+
credentials: AWS_CREDENTIALS,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await client.send(new DeleteTopicCommand({ TopicArn: topicArn }));
|
|
60
|
+
client.destroy();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function createQueue(queueName: string): Promise<string> {
|
|
64
|
+
const client = new SQSClient({
|
|
65
|
+
region: AWS_REGION,
|
|
66
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
67
|
+
credentials: AWS_CREDENTIALS,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const response = await client.send(
|
|
71
|
+
new CreateQueueCommand({ QueueName: queueName }),
|
|
72
|
+
);
|
|
73
|
+
client.destroy();
|
|
74
|
+
|
|
75
|
+
if (!response.QueueUrl) {
|
|
76
|
+
throw new Error('Failed to create queue');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return response.QueueUrl;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function deleteQueue(queueUrl: string): Promise<void> {
|
|
83
|
+
const client = new SQSClient({
|
|
84
|
+
region: AWS_REGION,
|
|
85
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
86
|
+
credentials: AWS_CREDENTIALS,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await client.send(new DeleteQueueCommand({ QueueUrl: queueUrl }));
|
|
90
|
+
client.destroy();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function getQueueArn(queueUrl: string): Promise<string> {
|
|
94
|
+
const client = new SQSClient({
|
|
95
|
+
region: AWS_REGION,
|
|
96
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
97
|
+
credentials: AWS_CREDENTIALS,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const response = await client.send(
|
|
101
|
+
new GetQueueAttributesCommand({
|
|
102
|
+
QueueUrl: queueUrl,
|
|
103
|
+
AttributeNames: ['QueueArn'],
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
client.destroy();
|
|
107
|
+
|
|
108
|
+
const queueArn = response.Attributes?.QueueArn;
|
|
109
|
+
if (!queueArn) {
|
|
110
|
+
throw new Error('Failed to get queue ARN');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return queueArn;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function setQueuePolicy(
|
|
117
|
+
queueUrl: string,
|
|
118
|
+
queueArn: string,
|
|
119
|
+
topicArn: string,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
const client = new SQSClient({
|
|
122
|
+
region: AWS_REGION,
|
|
123
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
124
|
+
credentials: AWS_CREDENTIALS,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const policy = {
|
|
128
|
+
Version: '2012-10-17',
|
|
129
|
+
Statement: [
|
|
130
|
+
{
|
|
131
|
+
Effect: 'Allow',
|
|
132
|
+
Principal: '*',
|
|
133
|
+
Action: 'SQS:SendMessage',
|
|
134
|
+
Resource: queueArn,
|
|
135
|
+
Condition: {
|
|
136
|
+
ArnEquals: { 'aws:SourceArn': topicArn },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await client.send(
|
|
143
|
+
new SetQueueAttributesCommand({
|
|
144
|
+
QueueUrl: queueUrl,
|
|
145
|
+
Attributes: { Policy: JSON.stringify(policy) },
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
client.destroy();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function subscribeQueueToTopic(
|
|
152
|
+
topicArn: string,
|
|
153
|
+
queueArn: string,
|
|
154
|
+
): Promise<string> {
|
|
155
|
+
const client = new SNSClient({
|
|
156
|
+
region: AWS_REGION,
|
|
157
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
158
|
+
credentials: AWS_CREDENTIALS,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const response = await client.send(
|
|
162
|
+
new SubscribeCommand({
|
|
163
|
+
TopicArn: topicArn,
|
|
164
|
+
Protocol: 'sqs',
|
|
165
|
+
Endpoint: queueArn,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
client.destroy();
|
|
169
|
+
|
|
170
|
+
if (!response.SubscriptionArn) {
|
|
171
|
+
throw new Error('Failed to subscribe queue to topic');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return response.SubscriptionArn;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function unsubscribe(subscriptionArn: string): Promise<void> {
|
|
178
|
+
const client = new SNSClient({
|
|
179
|
+
region: AWS_REGION,
|
|
180
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
181
|
+
credentials: AWS_CREDENTIALS,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await client.send(
|
|
185
|
+
new UnsubscribeCommand({ SubscriptionArn: subscriptionArn }),
|
|
186
|
+
);
|
|
187
|
+
client.destroy();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function receiveMessages(queueUrl: string): Promise<any[]> {
|
|
191
|
+
const client = new SQSClient({
|
|
192
|
+
region: AWS_REGION,
|
|
193
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
194
|
+
credentials: AWS_CREDENTIALS,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const response = await client.send(
|
|
198
|
+
new ReceiveMessageCommand({
|
|
199
|
+
QueueUrl: queueUrl,
|
|
200
|
+
WaitTimeSeconds: 5,
|
|
201
|
+
MaxNumberOfMessages: 10,
|
|
202
|
+
MessageAttributeNames: ['All'],
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
client.destroy();
|
|
206
|
+
|
|
207
|
+
return (response.Messages || []).map((msg) => {
|
|
208
|
+
// SNS wraps the message in its own envelope
|
|
209
|
+
const snsEnvelope = JSON.parse(msg.Body || '{}');
|
|
210
|
+
return JSON.parse(snsEnvelope.Message || '{}');
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
describe('SNSPublisher', () => {
|
|
215
|
+
const uniqueId = () =>
|
|
216
|
+
`${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
217
|
+
|
|
218
|
+
describe('constructor', () => {
|
|
219
|
+
it('should create publisher with connection', async () => {
|
|
220
|
+
const topicArn = await createTopic(`test-constructor-${uniqueId()}`);
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const connection = new SNSConnection({
|
|
224
|
+
topicArn,
|
|
225
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
226
|
+
region: AWS_REGION,
|
|
227
|
+
credentials: AWS_CREDENTIALS,
|
|
228
|
+
});
|
|
229
|
+
await connection.connect();
|
|
230
|
+
|
|
231
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
232
|
+
expect(publisher).toBeDefined();
|
|
233
|
+
|
|
234
|
+
await connection.close();
|
|
235
|
+
} finally {
|
|
236
|
+
await deleteTopic(topicArn);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('publish', () => {
|
|
242
|
+
let topicArn: string;
|
|
243
|
+
let queueUrl: string;
|
|
244
|
+
let queueArn: string;
|
|
245
|
+
let subscriptionArn: string;
|
|
246
|
+
let connection: SNSConnection;
|
|
247
|
+
|
|
248
|
+
beforeAll(async () => {
|
|
249
|
+
topicArn = await createTopic(`test-publish-${uniqueId()}`);
|
|
250
|
+
queueUrl = await createQueue(`test-publish-queue-${uniqueId()}`);
|
|
251
|
+
queueArn = await getQueueArn(queueUrl);
|
|
252
|
+
await setQueuePolicy(queueUrl, queueArn, topicArn);
|
|
253
|
+
subscriptionArn = await subscribeQueueToTopic(topicArn, queueArn);
|
|
254
|
+
|
|
255
|
+
// Wait for subscription to be ready
|
|
256
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
257
|
+
|
|
258
|
+
connection = new SNSConnection({
|
|
259
|
+
topicArn,
|
|
260
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
261
|
+
region: AWS_REGION,
|
|
262
|
+
credentials: AWS_CREDENTIALS,
|
|
263
|
+
});
|
|
264
|
+
await connection.connect();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
afterAll(async () => {
|
|
268
|
+
await connection.close();
|
|
269
|
+
await unsubscribe(subscriptionArn);
|
|
270
|
+
await deleteQueue(queueUrl);
|
|
271
|
+
await deleteTopic(topicArn);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should return early if messages array is empty', async () => {
|
|
275
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
276
|
+
|
|
277
|
+
// Should complete immediately without error or sending anything
|
|
278
|
+
const startTime = Date.now();
|
|
279
|
+
await publisher.publish([]);
|
|
280
|
+
const elapsed = Date.now() - startTime;
|
|
281
|
+
|
|
282
|
+
// Should complete almost instantly (< 100ms) since no messages are sent
|
|
283
|
+
expect(elapsed).toBeLessThan(100);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should publish a single message with correct format', async () => {
|
|
287
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
288
|
+
|
|
289
|
+
await publisher.publish([
|
|
290
|
+
{
|
|
291
|
+
type: 'user.created',
|
|
292
|
+
payload: { userId: '123', email: 'test@example.com' },
|
|
293
|
+
},
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
// Wait for message to arrive
|
|
297
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
298
|
+
|
|
299
|
+
const messages = await receiveMessages(queueUrl);
|
|
300
|
+
expect(messages).toHaveLength(1);
|
|
301
|
+
expect(messages[0].type).toBe('user.created');
|
|
302
|
+
expect(messages[0].payload.userId).toBe('123');
|
|
303
|
+
expect(messages[0].payload.email).toBe('test@example.com');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should publish multiple messages', async () => {
|
|
307
|
+
// Create fresh queue to avoid picking up old messages
|
|
308
|
+
const freshQueueUrl = await createQueue(`test-multi-${uniqueId()}`);
|
|
309
|
+
const freshQueueArn = await getQueueArn(freshQueueUrl);
|
|
310
|
+
await setQueuePolicy(freshQueueUrl, freshQueueArn, topicArn);
|
|
311
|
+
const freshSubscriptionArn = await subscribeQueueToTopic(
|
|
312
|
+
topicArn,
|
|
313
|
+
freshQueueArn,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
320
|
+
|
|
321
|
+
await publisher.publish([
|
|
322
|
+
{ type: 'user.created', payload: { userId: '1' } },
|
|
323
|
+
{ type: 'user.updated', payload: { userId: '2' } },
|
|
324
|
+
{ type: 'order.placed', payload: { orderId: '3' } },
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
328
|
+
|
|
329
|
+
const messages = await receiveMessages(freshQueueUrl);
|
|
330
|
+
expect(messages).toHaveLength(3);
|
|
331
|
+
|
|
332
|
+
const types = messages.map((m) => m.type).sort();
|
|
333
|
+
expect(types).toEqual(['order.placed', 'user.created', 'user.updated']);
|
|
334
|
+
} finally {
|
|
335
|
+
await unsubscribe(freshSubscriptionArn);
|
|
336
|
+
await deleteQueue(freshQueueUrl);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should handle complex payloads', async () => {
|
|
341
|
+
const freshQueueUrl = await createQueue(`test-complex-${uniqueId()}`);
|
|
342
|
+
const freshQueueArn = await getQueueArn(freshQueueUrl);
|
|
343
|
+
await setQueuePolicy(freshQueueUrl, freshQueueArn, topicArn);
|
|
344
|
+
const freshSubscriptionArn = await subscribeQueueToTopic(
|
|
345
|
+
topicArn,
|
|
346
|
+
freshQueueArn,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
353
|
+
const complexPayload = {
|
|
354
|
+
userId: '123',
|
|
355
|
+
nested: {
|
|
356
|
+
data: [1, 2, 3],
|
|
357
|
+
metadata: { key: 'value' },
|
|
358
|
+
},
|
|
359
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
await publisher.publish([
|
|
363
|
+
{ type: 'user.created', payload: complexPayload },
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
367
|
+
|
|
368
|
+
const messages = await receiveMessages(freshQueueUrl);
|
|
369
|
+
expect(messages).toHaveLength(1);
|
|
370
|
+
expect(messages[0].payload).toEqual(complexPayload);
|
|
371
|
+
} finally {
|
|
372
|
+
await unsubscribe(freshSubscriptionArn);
|
|
373
|
+
await deleteQueue(freshQueueUrl);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should auto-connect if not connected', async () => {
|
|
378
|
+
const freshConnection = new SNSConnection({
|
|
379
|
+
topicArn,
|
|
380
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
381
|
+
region: AWS_REGION,
|
|
382
|
+
credentials: AWS_CREDENTIALS,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Don't call connect() explicitly
|
|
386
|
+
expect(freshConnection.isConnected()).toBe(false);
|
|
387
|
+
|
|
388
|
+
const publisher = new SNSPublisher<TestMessage>(freshConnection);
|
|
389
|
+
|
|
390
|
+
// Should auto-connect and publish
|
|
391
|
+
await publisher.publish([
|
|
392
|
+
{ type: 'user.created', payload: { test: 'auto-connect' } },
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
expect(freshConnection.isConnected()).toBe(true);
|
|
396
|
+
|
|
397
|
+
await freshConnection.close();
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('fromConnectionString', () => {
|
|
402
|
+
it('should create publisher from connection string', async () => {
|
|
403
|
+
const topicArn = await createTopic(`test-fromcs-${uniqueId()}`);
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const connectionString = `sns://?topicArn=${encodeURIComponent(topicArn)}®ion=${AWS_REGION}&endpoint=${encodeURIComponent(LOCALSTACK_ENDPOINT)}&accessKeyId=test&secretAccessKey=test`;
|
|
407
|
+
|
|
408
|
+
const publisher =
|
|
409
|
+
await SNSPublisher.fromConnectionString<TestMessage>(
|
|
410
|
+
connectionString,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
expect(publisher).toBeInstanceOf(SNSPublisher);
|
|
414
|
+
|
|
415
|
+
await publisher.close();
|
|
416
|
+
} finally {
|
|
417
|
+
await deleteTopic(topicArn);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('close', () => {
|
|
423
|
+
it('should complete without error (publisher does not own connection)', async () => {
|
|
424
|
+
const topicArn = await createTopic(`test-close-${uniqueId()}`);
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const connection = new SNSConnection({
|
|
428
|
+
topicArn,
|
|
429
|
+
endpoint: LOCALSTACK_ENDPOINT,
|
|
430
|
+
region: AWS_REGION,
|
|
431
|
+
credentials: AWS_CREDENTIALS,
|
|
432
|
+
});
|
|
433
|
+
await connection.connect();
|
|
434
|
+
|
|
435
|
+
const publisher = new SNSPublisher<TestMessage>(connection);
|
|
436
|
+
|
|
437
|
+
// close() should not throw and should not close the connection
|
|
438
|
+
await publisher.close();
|
|
439
|
+
|
|
440
|
+
// Connection should still be open
|
|
441
|
+
expect(connection.isConnected()).toBe(true);
|
|
442
|
+
|
|
443
|
+
await connection.close();
|
|
444
|
+
} finally {
|
|
445
|
+
await deleteTopic(topicArn);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
});
|