diff --git a/.eslintrc b/.eslintrc
index 71b46ba..122285f 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,7 +1,7 @@
{
"extends": "@aptoma/eslint-config",
"parserOptions": {
- "ecmaVersion": "2017"
+ "ecmaVersion": 2023
},
"env": {
"node": true,
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7638967
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,22 @@
+name: Node.js CI
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [20]
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ registry-url: 'https://registry.npmjs.org'
+ cache: 'npm'
+ cache-dependency-path: ./package.json
+ - run: npm install
+ - run: npm test
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6d4bba3..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-language: node_js
-node_js:
- - 10
-
diff --git a/handlers.js b/handlers.js
index c29365d..476e10c 100644
--- a/handlers.js
+++ b/handlers.js
@@ -1,8 +1,6 @@
'use strict';
-const Promise = require('bluebird');
const Hoek = require('@hapi/hoek');
-const request = require('request-prom');
exports.hook = (sns, {handlers, skipPayloadValidation, topic}) => {
handlers = Hoek.applyToDefaults({
@@ -41,9 +39,8 @@ async function confirmSubscription(sns, topicOpts, req, h, payload) {
}
try {
- await request.get(payload.SubscribeURL);
+ await fetch(payload.SubscribeURL);
req.log(['hookido', 'info'], `SNS subscription confirmed for ${payload.TopicArn}`);
-
} catch (err) {
req.log(['hookido', 'error'], `Unable to confirm SNS subscription for ${payload.TopicArn}, err: ${err.message}`);
throw err;
diff --git a/index.js b/index.js
index 4d0ebc1..1d9f54b 100644
--- a/index.js
+++ b/index.js
@@ -37,23 +37,22 @@ function register(server, opts) {
snsInstances.push(sns);
const subscribe = Hoek.reach(config, 'topic.subscribe');
- function requestSubscription() {
- return sns
- .subscribe(config.topic.arn, subscribe.protocol, subscribe.endpoint)
- .then(() => server.log(['hookido', 'subscribe'], `Subscription request sent for ${config.topic.arn}`));
- }
-
if (subscribe) {
-
- server.ext('onPostStart', () => {
- return sns
- .findSubscriptionArn(config.topic.arn, subscribe.protocol, subscribe.endpoint)
- .then(() => server.log(['hookido', 'subscribe'], `Subscription already exists for ${config.topic.arn}`))
- .catch({code: 'NOT_FOUND'}, requestSubscription)
- .catch({code: 'PENDING'}, requestSubscription)
- .catch((err) => server.log(['hookido', 'subscribe', 'error'], err));
+ server.ext('onPostStart', async () => {
+ try {
+ await sns.findSubscriptionArn(config.topic.arn, subscribe.protocol, subscribe.endpoint);
+ server.log(['hookido', 'subscribe'], `Subscription already exists for ${config.topic.arn}`);
+ } catch (err) {
+ if (err.code === 'NOT_FOUND' || err.code === 'PENDING') {
+ await sns
+ .subscribe(config.topic.arn, subscribe.protocol, subscribe.endpoint)
+ .then(() => server.log(['hookido', 'subscribe'], `Subscription request sent for ${config.topic.arn}`))
+ .catch((err) => server.log(['hookido', 'subscribe', 'error'], err));
+ } else {
+ server.log(['hookido', 'subscribe', 'error'], err);
+ }
+ }
});
-
}
const topicAttributes = Hoek.reach(config, 'topic.attributes');
diff --git a/lib/sns.js b/lib/sns.js
index a39a2f8..e5d04fa 100644
--- a/lib/sns.js
+++ b/lib/sns.js
@@ -1,51 +1,52 @@
'use strict';
-const Promise = require('bluebird');
-const AWS = require('aws-sdk');
+const {
+ SNSClient,
+ SubscribeCommand,
+ SetTopicAttributesCommand,
+ ListSubscriptionsByTopicCommand,
+ SetSubscriptionAttributesCommand
+} = require('@aws-sdk/client-sns');
const MessageValidator = require('sns-validator');
const validator = new MessageValidator();
class SNS {
constructor(awsConfig) {
- const sns = new AWS.SNS(awsConfig || {});
- this.sns = {
- subscribe: Promise.promisify(sns.subscribe, {context: sns}),
- setTopicAttributes: Promise.promisify(sns.setTopicAttributes, {context: sns}),
- listSubscriptionsByTopic: Promise.promisify(sns.listSubscriptionsByTopic, {context: sns}),
- setSubscriptionAttributes: Promise.promisify(sns.setSubscriptionAttributes, {context: sns})
- };
+ this.snsClient = new SNSClient(awsConfig || {});
}
- setTopicAttributes(TopicArn, attributes) {
- // this needs to be done sequentially otherwise only one attribute will be set, have no idea why.
- return Promise.mapSeries(Object.keys(attributes), (key) => {
- return this.sns.setTopicAttributes({
+ async setTopicAttributes(TopicArn, attributes) {
+ // Process attributes sequentially
+ for (const key of Object.keys(attributes)) {
+ const command = new SetTopicAttributesCommand({
TopicArn,
AttributeName: key,
AttributeValue: attributes[key]
});
- });
+ await this.snsClient.send(command);
+ }
}
subscribe(TopicArn, Protocol, Endpoint) {
- const params = {
+ const command = new SubscribeCommand({
TopicArn,
Protocol,
Endpoint
- };
+ });
- return this.sns.subscribe(params);
+ return this.snsClient.send(command);
}
- setSubscriptionAttributes(SubscriptionArn, attributes) {
- return Promise.mapSeries(Object.keys(attributes), (key) => {
- return this.sns.setSubscriptionAttributes({
+ async setSubscriptionAttributes(SubscriptionArn, attributes) {
+ for (const key of Object.keys(attributes)) {
+ const command = new SetSubscriptionAttributesCommand({
SubscriptionArn,
AttributeName: key,
AttributeValue: attributes[key]
});
- });
+ await this.snsClient.send(command);
+ }
}
/**
@@ -63,8 +64,10 @@ class SNS {
params.NextToken = NextToken;
}
- return this.sns
- .listSubscriptionsByTopic(params)
+ const command = new ListSubscriptionsByTopicCommand(params);
+
+ return this.snsClient
+ .send(command)
.then((data) => {
const arn = data.Subscriptions.find((sub) => {
return sub.Protocol.toLowerCase() === Protocol.toLowerCase() &&
diff --git a/package.json b/package.json
index 4145d0b..74dbd1c 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"release:major": "npm run test && release-it -n -i major"
},
"engines": {
- "node": ">=10.x.x"
+ "node": ">=20.x.x"
},
"repository": {
"type": "git",
@@ -31,21 +31,20 @@
},
"homepage": "https://github.com/martinj/hookido",
"dependencies": {
- "@hapi/hoek": "^9.2.1",
- "aws-sdk": "^2.1094.0",
- "bluebird": "^3.7.2",
- "joi": "^17.6.0",
- "request-prom": "^4.0.1",
- "sns-validator": "^0.3.4"
+ "@aws-sdk/client-sns": "^3.731.1",
+ "@hapi/hoek": "^11.0.7",
+ "joi": "^17.13.3",
+ "sns-validator": "^0.3.5"
},
"devDependencies": {
"@aptoma/eslint-config": "^7.0.1",
- "@hapi/hapi": "^20.2.1",
- "chai": "^4.3.6",
- "eslint": "^8.11.0",
- "mocha": "^9.2.2",
- "nock": "^13.2.4",
+ "@hapi/hapi": "^21.3.12",
+ "aws-sdk-client-mock": "^4.1.0",
+ "chai": "^4.5.0",
+ "eslint": "^8.57.1",
+ "mocha": "^11.0.1",
"nyc": "^15.1.0",
- "release-it": "^2.7.3"
+ "release-it": "^2.7.3",
+ "sinon": "^19.0.2"
}
}
diff --git a/test/index.test.js b/test/index.test.js
index 202858f..3d0f8ec 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -1,15 +1,35 @@
'use strict';
-const Promise = require('bluebird');
const Hapi = require('@hapi/hapi');
+const {mockClient} = require('aws-sdk-client-mock');
+const {
+ SNSClient,
+ SubscribeCommand,
+ SetTopicAttributesCommand,
+ ListSubscriptionsByTopicCommand,
+ SetSubscriptionAttributesCommand
+} = require('@aws-sdk/client-sns');
const plugin = require('../');
-const expect = require('chai').expect;
-const nock = require('nock');
+const {expect} = require('chai');
+const sinon = require('sinon');
describe('Hookido Hapi Plugin', () => {
+ let snsMock;
+ let fetchStub;
+
+ beforeEach(() => {
+ snsMock = mockClient(SNSClient);
+ // Mock global fetch
+ fetchStub = sinon.stub(global, 'fetch');
+ fetchStub.resolves(new Response());
+ });
- it('merges route options with route', async () => {
+ afterEach(() => {
+ snsMock.reset();
+ fetchStub.restore();
+ });
+ it('merges route options with route', async () => {
const server = new Hapi.Server();
await server.register({
@@ -26,11 +46,9 @@ describe('Hookido Hapi Plugin', () => {
const table = server.table();
expect(table[0].path).to.equal('/foobar');
-
});
it('skips subscribe request if subscription exist', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -49,18 +67,19 @@ describe('Hookido Hapi Plugin', () => {
}
});
- server.plugins.hookido.snsInstances[0].findSubscriptionArn = () => {
- return Promise.resolve('foo');
- };
+ snsMock.on(ListSubscriptionsByTopicCommand).resolves({
+ Subscriptions: [{
+ SubscriptionArn: 'foo',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foo.com'
+ }]
+ });
await server.start();
await server.stop();
-
});
-
it('sends subscribe request onPostStart if subscribe option is set and subscription does not exist', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -79,28 +98,25 @@ describe('Hookido Hapi Plugin', () => {
}
});
- server.plugins.hookido.snsInstances[0].findSubscriptionArn = () => {
- const err = new Error();
- err.code = 'NOT_FOUND';
- return Promise.reject(err);
- };
-
- server.plugins.hookido.snsInstances[0].subscribe = (arn, protocol, endpoint) => {
-
- expect(arn).to.equal('foo');
- expect(protocol).to.equal('HTTP');
- expect(endpoint).to.equal('http://foo.com');
- return Promise.resolve();
-
- };
+ snsMock
+ .on(ListSubscriptionsByTopicCommand)
+ .rejects({code: 'NOT_FOUND'})
+ .on(SubscribeCommand)
+ .resolves({});
await server.start();
await server.stop();
+ const subscribeCalls = snsMock.commandCalls(SubscribeCommand);
+ expect(subscribeCalls).length(1);
+ expect(subscribeCalls[0].args[0].input).to.deep.equal({
+ TopicArn: 'foo',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foo.com'
+ });
});
it('sends new subscribe request onPostStart if subscribe option is set and subscription is pending', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -119,28 +135,31 @@ describe('Hookido Hapi Plugin', () => {
}
});
- server.plugins.hookido.snsInstances[0].findSubscriptionArn = () => {
- const err = new Error();
- err.code = 'PENDING';
- return Promise.reject(err);
- };
-
- server.plugins.hookido.snsInstances[0].subscribe = (arn, protocol, endpoint) => {
-
- expect(arn).to.equal('foo');
- expect(protocol).to.equal('HTTP');
- expect(endpoint).to.equal('http://foo.com');
- return Promise.resolve();
-
- };
+ snsMock
+ .on(ListSubscriptionsByTopicCommand)
+ .resolves({
+ Subscriptions: [{
+ SubscriptionArn: 'PendingConfirmation',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foo.com'
+ }]
+ })
+ .on(SubscribeCommand)
+ .resolves({});
await server.start();
await server.stop();
+ const subscribeCalls = snsMock.commandCalls(SubscribeCommand);
+ expect(subscribeCalls).length(1);
+ expect(subscribeCalls[0].args[0].input).to.deep.equal({
+ TopicArn: 'foo',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foo.com'
+ });
});
it('sends setTopicAttributes request onPostStart if topicAttributes option is set', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -158,21 +177,21 @@ describe('Hookido Hapi Plugin', () => {
}
});
- server.plugins.hookido.snsInstances[0].setTopicAttributes = (topic, attributes) => {
-
- expect(topic).to.equal('foo');
- expect(attributes).to.deep.equal({foo: 'bar'});
- return Promise.resolve();
-
- };
+ snsMock.on(SetTopicAttributesCommand).resolves({});
await server.start();
await server.stop();
+ const attributeCalls = snsMock.commandCalls(SetTopicAttributesCommand);
+ expect(attributeCalls).length(1);
+ expect(attributeCalls[0].args[0].input).to.deep.equal({
+ TopicArn: 'foo',
+ AttributeName: 'foo',
+ AttributeValue: 'bar'
+ });
});
it('dispatches SNS message of type Notification to configured handler', async () => {
-
const server = new Hapi.Server();
const payload = {Type: 'Notification'};
@@ -192,11 +211,9 @@ describe('Hookido Hapi Plugin', () => {
const res = await server.inject({method: 'POST', url: '/hookido', payload});
expect(res.statusCode).to.equal(200);
expect(res.result).to.deep.equal({called: true});
-
});
it('dispatches SNS message of type SubscriptionConfirmation to configured handler', async () => {
-
const server = new Hapi.Server();
const payload = {Type: 'SubscriptionConfirmation'};
@@ -218,19 +235,10 @@ describe('Hookido Hapi Plugin', () => {
expect(res.statusCode).to.equal(200);
expect(res.result).to.deep.equal({called: true});
-
});
describe('ConfirmSubscription', () => {
-
- afterEach(() => nock.cleanAll());
-
it('handles SubscriptionConfirmation if no handler is registered for that type', async () => {
-
- const confirmRequest = nock('http://localtest.com')
- .get('/foo')
- .reply(200);
-
const server = new Hapi.Server();
const payload = {
@@ -253,12 +261,11 @@ describe('Hookido Hapi Plugin', () => {
const res = await server.inject({method: 'POST', url: '/hookido', payload});
expect(res.statusCode).to.equal(200);
- expect(confirmRequest.isDone()).to.be.true;
-
+ expect(fetchStub.calledOnce).to.be.true;
+ expect(fetchStub.firstCall.args[0]).to.equal('http://localtest.com/foo');
});
it('fails if configured topic arn doesn\'t match TopicArn in SubscriptionConfirmation payload', async () => {
-
const server = new Hapi.Server();
const payload = {
@@ -281,35 +288,9 @@ describe('Hookido Hapi Plugin', () => {
const res = await server.inject({method: 'POST', url: '/hookido', payload});
expect(res.statusCode).to.equal(500);
-
});
it('sets subscription attributes if supplied in options', async () => {
-
- const mock1 = nock('http://localtest.com')
- .get('/foo')
- .reply(200);
-
- const mock2 = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=ListSubscriptionsByTopic&TopicArn=mytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- http
- arn:aws:sns:eu-west-1:111111111111:mytopic
- 123456789012
- http://foo.com/bar
-
-
-
-
- `)
- .post('/', 'Action=SetSubscriptionAttributes&AttributeName=foo&AttributeValue=bar&SubscriptionArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200);
-
const server = new Hapi.Server();
const payload = {
@@ -318,7 +299,20 @@ describe('Hookido Hapi Plugin', () => {
TopicArn: 'mytopic'
};
- server.register({
+ snsMock
+ .on(ListSubscriptionsByTopicCommand)
+ .resolves({
+ Subscriptions: [{
+ TopicArn: 'mytopic',
+ Protocol: 'http',
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ Endpoint: 'http://foo.com/bar'
+ }]
+ })
+ .on(SetSubscriptionAttributesCommand)
+ .resolves({});
+
+ await server.register({
plugin,
options: {
skipPayloadValidation: true,
@@ -345,15 +339,20 @@ describe('Hookido Hapi Plugin', () => {
await server.inject({method: 'POST', url: '/hookido', payload});
- expect(mock1.isDone()).to.be.true;
- expect(mock2.isDone()).to.be.true;
+ expect(fetchStub.calledOnce).to.be.true;
+ expect(fetchStub.firstCall.args[0]).to.equal('http://localtest.com/foo');
+ const attributeCalls = snsMock.commandCalls(SetSubscriptionAttributesCommand);
+ expect(attributeCalls).length(1);
+ expect(attributeCalls[0].args[0].input).to.deep.equal({
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ AttributeName: 'foo',
+ AttributeValue: 'bar'
+ });
});
-
});
it('supports multiple configurations', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -380,11 +379,9 @@ describe('Hookido Hapi Plugin', () => {
expect(table[0].path).to.equal('/foobar');
expect(table[1].path).to.equal('/foobar2');
expect(server.plugins.hookido.snsInstances).to.have.a.lengthOf(2);
-
});
it('supports to load multiple times', async () => {
-
const server = new Hapi.Server();
await server.register({
@@ -416,7 +413,5 @@ describe('Hookido Hapi Plugin', () => {
expect(table[0].path).to.equal('/foobar');
expect(table[1].path).to.equal('/foobar2');
expect(server.plugins.hookido.snsInstances).to.have.a.lengthOf(2);
-
});
-
});
diff --git a/test/lib/sns.test.js b/test/lib/sns.test.js
index 8c042eb..8e4427d 100644
--- a/test/lib/sns.test.js
+++ b/test/lib/sns.test.js
@@ -1,298 +1,221 @@
'use strict';
-const nock = require('nock');
-const expect = require('chai').expect;
+const {mockClient} = require('aws-sdk-client-mock');
+const {
+ SNSClient,
+ SubscribeCommand,
+ SetTopicAttributesCommand,
+ ListSubscriptionsByTopicCommand,
+ SetSubscriptionAttributesCommand
+} = require('@aws-sdk/client-sns');
const SNS = require('../../lib/sns');
+const {expect} = require('chai');
describe('SNS', () => {
let sns;
+ let snsMock;
beforeEach(() => {
- sns = new SNS({
- region: 'eu-west-1',
- accessKeyId: 'a',
- secretAccessKey: 'a'
- });
+ snsMock = mockClient(SNSClient);
+ sns = new SNS({region: 'eu-west-1'});
});
- describe('#validatePayload', () => {
-
- it('rejects on invalid JSON', (done) => {
-
- sns
- .validatePayload('ada')
- .catch((err) => {
- expect(err.message).to.equal('Invalid SNS payload: Unexpected token a in JSON at position 0');
- return sns.validatePayload(null);
- })
- .catch((err) => {
-
- expect(err.message).to.equal('Invalid SNS payload: Not valid JSON');
- done();
-
- });
+ afterEach(() => {
+ snsMock.reset();
+ });
+ describe('#validatePayload', () => {
+ it('rejects on invalid JSON', async () => {
+ try {
+ await sns.validatePayload('ada');
+ throw new Error('Expected error');
+ } catch (err) {
+ expect(err.message).to.match(/Invalid SNS payload: Unexpected token/);
+ }
});
- it('rejects on invalid payload', (done) => {
-
- sns
- .validatePayload('{"foo":"bar"}')
- .catch((err) => {
-
- expect(err.message).to.equal('Message missing required keys.');
- done();
-
- });
-
+ it('rejects on null payload', async () => {
+ try {
+ await sns.validatePayload(null);
+ throw new Error('Expected error');
+ } catch (err) {
+ expect(err.message).to.equal('Invalid SNS payload: Not valid JSON');
+ }
});
- it('accepts object or array', () => {
-
- return sns
- .validatePayload([], true)
- .then((data) => expect(data).to.deep.equal([]))
- .then(() => sns.validatePayload({}, true))
- .then((data) => expect(data).to.deep.equal({}));
-
+ it('rejects on invalid payload', async () => {
+ try {
+ await sns.validatePayload('{"foo":"bar"}');
+ throw new Error('Expected error');
+ } catch (err) {
+ expect(err.message).to.equal('Message missing required keys.');
+ }
});
- it('skipValidation skips SNS message validation only parses', () => {
-
- return sns
- .validatePayload('{"foo":"bar"}', true)
- .then((data) => {
- expect(data).to.deep.equal({foo: 'bar'});
- });
+ it('accepts array', async () => {
+ const data = await sns.validatePayload([], true);
+ expect(data).to.deep.equal([]);
+ });
+ it('accepts object', async () => {
+ const data = await sns.validatePayload({}, true);
+ expect(data).to.deep.equal({});
});
+ it('skipValidation skips SNS message validation only parses', async () => {
+ const data = await sns.validatePayload('{"foo":"bar"}', true);
+ expect(data).to.deep.equal({foo: 'bar'});
+ });
});
describe('#subscribe', () => {
-
- afterEach(() => nock.cleanAll());
-
- it('sends subscribe request', () => {
-
- const req = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=Subscribe&Endpoint=http%3A%2F%2Ffoobar.com&Protocol=HTTP&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200);
-
- return sns
- .subscribe('arn:aws:sns:eu-west-1:111111111111:mytopic', 'HTTP', 'http://foobar.com')
- .then(() => {
-
- expect(req.isDone()).to.be.true;
-
- });
+ it('sends subscribe request', async () => {
+ snsMock.on(SubscribeCommand).resolves({
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id'
+ });
+
+ await sns.subscribe(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ 'HTTP',
+ 'http://foobar.com'
+ );
+
+ expect(snsMock.calls()).length(1);
+ const [subscribeCall] = snsMock.calls();
+ expect(subscribeCall.args[0].input).to.deep.equal({
+ TopicArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foobar.com'
+ });
});
-
});
describe('#setTopicAttributes', () => {
-
- afterEach(() => nock.cleanAll());
-
- it('sends setTopicAttributes request', () => {
-
- const firstReq = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=SetTopicAttributes&AttributeName=HTTPSuccessFeedbackRoleArn&AttributeValue=arn%3Aaws%3Aiam%3A%3Axxxx%3Arole%2FmyRole&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200);
-
- const secondReq = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=SetTopicAttributes&AttributeName=HTTPSuccessFeedbackSampleRate&AttributeValue=100&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200);
-
- return sns
- .setTopicAttributes('arn:aws:sns:eu-west-1:111111111111:mytopic', {
- HTTPSuccessFeedbackRoleArn: 'arn:aws:iam::xxxx:role/myRole',
- HTTPSuccessFeedbackSampleRate: '100'
- })
- .then(() => {
-
- expect(firstReq.isDone()).to.be.true;
- expect(secondReq.isDone()).to.be.true;
-
- });
-
- });
-
- });
-
- describe('#setSubscriptionAttributes', () => {
-
- afterEach(() => nock.cleanAll());
-
- it('sends setSubscriptionAttributes request', () => {
-
- const firstReq = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=SetSubscriptionAttributes&AttributeName=foo&AttributeValue=bar&SubscriptionArn=arn&Version=2010-03-31')
- .reply(200);
-
- const secondReq = nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=SetSubscriptionAttributes&AttributeName=bar&AttributeValue=foo&SubscriptionArn=arn&Version=2010-03-31')
- .reply(200);
-
- return sns
- .setSubscriptionAttributes('arn', {
- foo: 'bar',
- bar: 'foo'
- })
- .then(() => {
-
- expect(firstReq.isDone()).to.be.true;
- expect(secondReq.isDone()).to.be.true;
-
- });
-
+ it('sets topic attributes sequentially', async () => {
+ snsMock.on(SetTopicAttributesCommand).resolves({});
+
+ await sns.setTopicAttributes(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ {
+ DisplayName: 'My Topic',
+ Policy: '{"Version":"2012-10-17"}'
+ }
+ );
+
+ expect(snsMock.calls()).length(2);
+ const calls = snsMock.calls();
+
+ expect(calls[0].args[0].input).to.deep.equal({
+ TopicArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ AttributeName: 'DisplayName',
+ AttributeValue: 'My Topic'
+ });
+
+ expect(calls[1].args[0].input).to.deep.equal({
+ TopicArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ AttributeName: 'Policy',
+ AttributeValue: '{"Version":"2012-10-17"}'
+ });
});
-
});
describe('#findSubscriptionArn', () => {
-
- afterEach(() => nock.cleanAll());
-
- it('rejects with error containing code NOT_FOUND if not found', (done) => {
-
- nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=ListSubscriptionsByTopic&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- email
- arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca
- 123456789012
- example@amazon.com
-
-
-
-
- `);
-
- sns
- .findSubscriptionArn('arn:aws:sns:eu-west-1:111111111111:mytopic', 'HTTP', 'http://foo.com/bar')
- .catch((err) => {
-
- expect(err.code).to.equal('NOT_FOUND');
- done();
-
- });
-
+ it('finds subscription arn', async () => {
+ snsMock.on(ListSubscriptionsByTopicCommand).resolves({
+ Subscriptions: [{
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foobar.com'
+ }]
+ });
+
+ const arn = await sns.findSubscriptionArn(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ 'HTTP',
+ 'http://foobar.com'
+ );
+
+ expect(arn).to.equal('arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id');
+ expect(snsMock.calls()).length(1);
+ expect(snsMock.calls()[0].args[0].input).to.deep.equal({
+ TopicArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic'
+ });
});
- it('rejects with error containing code PENDING if subscription exists but is pending confirmation', (done) => {
-
- nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=ListSubscriptionsByTopic&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- http
- PendingConfirmation
- 123456789012
- http://foo.com/bar
-
-
-
-
- `);
-
- sns
- .findSubscriptionArn('arn:aws:sns:eu-west-1:111111111111:mytopic', 'HTTP', 'http://foo.com/bar')
- .catch((err) => {
-
- expect(err.code).to.equal('PENDING');
- done();
-
- });
+ it('handles pending confirmation', async () => {
+ snsMock.on(ListSubscriptionsByTopicCommand).resolves({
+ Subscriptions: [{
+ SubscriptionArn: 'PendingConfirmation',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foobar.com'
+ }]
+ });
+
+ try {
+ await sns.findSubscriptionArn(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ 'HTTP',
+ 'http://foobar.com'
+ );
+ throw new Error('Should have thrown');
+ } catch (err) {
+ expect(err.code).to.equal('PENDING');
+ expect(err.message).to.equal('Subscription is pending confirmation');
+ }
});
-
- it('handles NextToken for paged results', () => {
-
- nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=ListSubscriptionsByTopic&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- email
- arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca
- 123456789012
- fo@foo.com
-
-
- foo
-
-
- `)
- .post('/', 'Action=ListSubscriptionsByTopic&NextToken=foo&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- http
- arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca
- 123456789012
- http://foo.com/bar
-
-
- foo
-
-
- `);
-
- return sns
- .findSubscriptionArn('arn:aws:sns:eu-west-1:111111111111:mytopic', 'HTTP', 'http://foo.com/bar')
- .then((arn) => {
-
- expect(arn).to.equal('arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca');
-
+ it('handles pagination', async () => {
+ snsMock
+ .on(ListSubscriptionsByTopicCommand)
+ .resolvesOnce({
+ NextToken: 'next-token',
+ Subscriptions: []
+ })
+ .resolvesOnce({
+ Subscriptions: [{
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id',
+ Protocol: 'HTTP',
+ Endpoint: 'http://foobar.com'
+ }]
});
- });
-
- it('resolves with the subscription arn if found', () => {
-
- nock('https://sns.eu-west-1.amazonaws.com:443')
- .post('/', 'Action=ListSubscriptionsByTopic&TopicArn=arn%3Aaws%3Asns%3Aeu-west-1%3A111111111111%3Amytopic&Version=2010-03-31')
- .reply(200, `
-
-
-
-
- arn:aws:sns:us-east-1:123456789012:My-Topic
- http
- arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca
- 123456789012
- http://foo.com/bar
-
-
-
-
- `);
-
- return sns
- .findSubscriptionArn('arn:aws:sns:eu-west-1:111111111111:mytopic', 'HTTP', 'http://foo.com/bar')
- .then((arn) => {
-
- expect(arn).to.equal('arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca');
-
- });
+ const arn = await sns.findSubscriptionArn(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic',
+ 'HTTP',
+ 'http://foobar.com'
+ );
+ expect(arn).to.equal('arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id');
+ expect(snsMock.calls()).length(2);
});
+ });
+ describe('#setSubscriptionAttributes', () => {
+ it('sets subscription attributes sequentially', async () => {
+ snsMock.on(SetSubscriptionAttributesCommand).resolves({});
+
+ await sns.setSubscriptionAttributes(
+ 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id',
+ {
+ RawMessageDelivery: 'true',
+ FilterPolicy: '{"event":["order_placed"]}'
+ }
+ );
+
+ expect(snsMock.calls()).length(2);
+ const calls = snsMock.calls();
+
+ expect(calls[0].args[0].input).to.deep.equal({
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id',
+ AttributeName: 'RawMessageDelivery',
+ AttributeValue: 'true'
+ });
+
+ expect(calls[1].args[0].input).to.deep.equal({
+ SubscriptionArn: 'arn:aws:sns:eu-west-1:111111111111:mytopic:subscription-id',
+ AttributeName: 'FilterPolicy',
+ AttributeValue: '{"event":["order_placed"]}'
+ });
+ });
});
});