sqs-consumer 6.0.0-alpha.2 β†’ 6.0.1

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.
@@ -0,0 +1,2 @@
1
+ # All changes should be reviewed by codeowners
2
+ * @bbc/ibl @bbc/detl-engineers
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct (CoC)
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behaviour that contributes to a positive environment for our community include:
12
+
13
+ - Respect different opinions, perspectives, and experiences
14
+ - Giving and appreciating constructive feedback
15
+ - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
16
+ - Focusing on what is best for us as individuals and the overall community
17
+ - Demonstrating kindness toward other people
18
+
19
+ Examples of unacceptable behaviour include:
20
+
21
+ - The use of sexualised language or imagery, and sexual attention or advances of any kind
22
+ - Trolling, insulting or derogatory comments, and personal or political attacks
23
+ - Public or private harassment
24
+ - Publishing others’ private information, such as a physical or email address, without their explicit permission
25
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Project maintainers are responsible for clarifying and enforcing our standards of acceptable behaviour and will take appropriate and fair corrective action in response to any behaviour that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ [Project maintainers](https://github.com/bbc/sqs-consumber/blob/main/.github/CODEOWNERS) are obligated to respect the privacy and security of the reporter of any incident.
42
+
43
+ ## Enforcement Guidelines
44
+
45
+ ### 1. Correction
46
+
47
+ Community Impact: Use of inappropriate language or other behaviour deemed unprofessional or unwelcome in the community.
48
+
49
+ Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behaviour was inappropriate. A public apology may be requested.
50
+
51
+ ### 2. Warning
52
+
53
+ Community Impact: A violation through a single incident or series of actions.
54
+
55
+ Consequence: A warning with consequences for continued behaviour. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
56
+
57
+ ### 3. Temporary Ban
58
+
59
+ Community Impact: A serious violation of community standards, including sustained inappropriate behaviour.
60
+
61
+ Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
62
+
63
+ ### 4. Permanent Ban
64
+
65
+ Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behaviour, harassment of an individual, or aggression toward or disparagement of classes of individuals.
66
+
67
+ Consequence: A permanent ban from any sort of public interaction within the community.
68
+
69
+ ## Attribution
70
+
71
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/
@@ -0,0 +1,41 @@
1
+ # Contributing
2
+
3
+ Thank you for your interest in contributing to the sqs-consumer.
4
+
5
+ - If you're unsure if a feature would make a good addition, you can always [create an issue](https://github.com/bbc/sqs-consumer/issues/new) first. Raising an issue before creating a pull request is recommended.
6
+ - We aim for 100% test coverage. Please write tests for any new functionality or changes.
7
+ - Any API changes should be fully documented.
8
+ - Make sure your code meets our linting standards. Run `npm run lint` to check your code.
9
+ - Maintain the existing coding style. There are some settings in `.jsbeautifyrc` to help.
10
+ - Be mindful of others when making suggestions and/or code reviewing.
11
+
12
+ ## Reporting Issues
13
+
14
+ Before opening a new issue, first check that there is not already an [open issue or Pull Request](https://github.com/bbc/sqs-consumer/issues?utf8=%E2%9C%93&q=is%3Aopen) that addresses it.
15
+
16
+ If there is, make relevant comments and add your reaction. Use a reaction in place of a "+1" comment:
17
+
18
+ - πŸ‘ - upvote
19
+ - πŸ‘Ž - downvote
20
+
21
+ If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below.
22
+
23
+ 1. Pick an appropriate template for the type of issue [from here](https://github.com/bbc/sqs-consumer/issues/choose)
24
+ 2. Provide as much detail as possible
25
+ 3. Follow your issue in the issue tracking workflow
26
+
27
+ ## Contributing Code
28
+
29
+ If you do not have push access to the repository, please [fork it](https://help.github.com/en/articles/fork-a-repo). You should then work on your own `main` branch.
30
+
31
+ Otherwise, you may clone this repository and create a working branch with a _kebab-case_ name reflecting what you are working on (e.g. `fix-the-thing`).
32
+
33
+ Follow the setup instructions in the [README](../README.md).
34
+
35
+ Ensure all your code is thoroughly tested and that this testing is detailed in the pull request.
36
+
37
+ ## Pull Request Process
38
+
39
+ 1. Make sure you have opened an issue and it was approved by a project maintainer before working on a PR
40
+ 2. Read and complete all relevant sections of the PR template
41
+ 3. Wait for the PR get approved
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **Describe the bug**
10
+ A clear and concise description of what the bug is.
11
+
12
+ **To Reproduce**
13
+ Steps to reproduce the behaviour:
14
+
15
+ 1. Go to '...'
16
+ 2. Select '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behaviour**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Additional context**
27
+ Add any other context about the problem here, such as specific device information.
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: feature-request
6
+ assignees: ''
7
+ ---
8
+
9
+ **The problem**
10
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11
+
12
+ **Suggested solution**
13
+ A clear and concise description of what you want to happen.
14
+
15
+ **Alternatives considered**
16
+ A clear and concise description of any alternative solutions or features you've considered.
17
+
18
+ **Additional context**
19
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: Technical question
3
+ about: Want to ask a technical question about the project
4
+ title: ''
5
+ labels: question
6
+ assignees: ''
7
+ ---
8
+
9
+ **Question**
10
+ A clear technical question could be asked here.
11
+
12
+ **screenshots**
13
+ If applicable, add screenshots to help explain your question.
14
+
15
+ **Additional context**
16
+ Add any other context about the question here, such as specific device information, technology choice etc.
@@ -0,0 +1,9 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Our full security policy and vulnerability reporting procedure is documented on [this external website](https://www.bbc.com/backstage/security-disclosure-policy/#reportingavulnerability).
6
+
7
+ Please note that this is a general BBC process. Communication will not be direct with the team responsible for this repo.
8
+
9
+ If you would like to, you can also open an issue in this repo regarding your disclosure, but please never share any details of the vulnerability in the GitHub issue.
@@ -0,0 +1,29 @@
1
+ Resolves #NUMBER
2
+
3
+ **Description:**
4
+ _A very high-level summary of easily-reproducible changes that can be understood by non-devs._
5
+
6
+ **Type of change:**
7
+
8
+ - [ ] Bug fix (non-breaking change which fixes an issue)
9
+ - [ ] New feature (non-breaking change which adds functionality)
10
+ - [ ] Breaking change (fix or feature that would cause existing functionality to change)
11
+
12
+ **Why is this change required?:**
13
+ _A simple explanation of what the problem is and how this PR solves it_
14
+
15
+ **Code changes:**
16
+
17
+ - _A bullet point list of key code changes that have been made._
18
+ - _When describing code changes, try to communicate **how** and **why** you implemented something a specific way, not just **what** has changed._
19
+
20
+ ---
21
+
22
+ **Checklist:**
23
+
24
+ - [ ] My code follows the code style of this project.
25
+ - [ ] My change requires a change to the documentation.
26
+ - [ ] I have updated the documentation accordingly.
27
+ - [ ] I have read the **CONTRIBUTING** document.
28
+ - [ ] I have added tests to cover my changes.
29
+ - [ ] All new and existing tests passed.
@@ -0,0 +1,35 @@
1
+ name: Report Coverage
2
+ on:
3
+ pull_request:
4
+ branches:
5
+ - 'main'
6
+ push:
7
+ branches:
8
+ - main
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ node-version: [14.x]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v3
21
+
22
+ - name: Use Node.js ${{ matrix.node-version }}
23
+ uses: actions/setup-node@v3
24
+ with:
25
+ node-version: ${{ matrix.node-version }}
26
+
27
+ - name: Install Node Modules
28
+ run: npm ci
29
+
30
+ - name: Report Coverage
31
+ uses: paambaati/codeclimate-action@v3.2.0
32
+ env:
33
+ CC_TEST_REPORTER_ID: 2d851f8f3a9348ac4f43262305037f80a730c2660fda50af8ae4d445fd89333b
34
+ with:
35
+ coverageCommand: npm run lcov
@@ -0,0 +1,20 @@
1
+ name: 'Close stale issues and PRs'
2
+ on:
3
+ schedule:
4
+ - cron: '30 1 * * *'
5
+
6
+ jobs:
7
+ stale:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/stale@v6
11
+ with:
12
+ stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
13
+ stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
14
+ close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
15
+ close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
16
+ days-before-issue-stale: 30
17
+ days-before-pr-stale: 45
18
+ days-before-issue-close: 5
19
+ days-before-pr-close: 10
20
+ operations-per-run: 90
@@ -0,0 +1,34 @@
1
+ name: Run Tests
2
+ on:
3
+ pull_request:
4
+ branches:
5
+ - 'main'
6
+ push:
7
+ branches:
8
+ - main
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ node-version: [14.x, 16.x, 18.x]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v3
21
+
22
+ - name: Use Node.js ${{ matrix.node-version }}
23
+ uses: actions/setup-node@v3
24
+ with:
25
+ node-version: ${{ matrix.node-version }}
26
+
27
+ - name: Install Node Modules
28
+ run: npm ci
29
+
30
+ - name: NPM Audit
31
+ run: npx audit-ci
32
+
33
+ - name: Run Tests and Linting
34
+ run: npm run test
package/README.md CHANGED
@@ -71,8 +71,8 @@ const app = Consumer.create({
71
71
  sqs: new SQSClient({
72
72
  region: 'my-region',
73
73
  credentials: {
74
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
75
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
74
+ accessKeyId: 'yourAccessKey',
75
+ secretAccessKey: 'yourSecret'
76
76
  }
77
77
  })
78
78
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqs-consumer",
3
- "version": "6.0.0-alpha.2",
3
+ "version": "6.0.1",
4
4
  "description": "Build SQS-based Node applications without the boilerplate",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,979 @@
1
+ import {
2
+ ChangeMessageVisibilityBatchCommand,
3
+ ChangeMessageVisibilityCommand,
4
+ DeleteMessageBatchCommand,
5
+ DeleteMessageCommand,
6
+ ReceiveMessageCommand,
7
+ SQSClient
8
+ } from '@aws-sdk/client-sqs';
9
+ import { assert } from 'chai';
10
+ import * as sinon from 'sinon';
11
+ import * as pEvent from 'p-event';
12
+
13
+ import { AWSError } from '../src/types';
14
+ import { Consumer } from '../src/consumer';
15
+
16
+ const sandbox = sinon.createSandbox();
17
+
18
+ const AUTHENTICATION_ERROR_TIMEOUT = 20;
19
+ const POLLING_TIMEOUT = 100;
20
+ const QUEUE_URL = 'some-queue-url';
21
+ const REGION = 'some-region';
22
+
23
+ const mockReceiveMessage = sinon.match.instanceOf(ReceiveMessageCommand);
24
+ const mockDeleteMessage = sinon.match.instanceOf(DeleteMessageCommand);
25
+ const mockDeleteMessageBatch = sinon.match.instanceOf(
26
+ DeleteMessageBatchCommand
27
+ );
28
+ const mockChangeMessageVisibility = sinon.match.instanceOf(
29
+ ChangeMessageVisibilityCommand
30
+ );
31
+ const mockChangeMessageVisibilityBatch = sinon.match.instanceOf(
32
+ ChangeMessageVisibilityBatchCommand
33
+ );
34
+
35
+ class MockSQSError extends Error implements AWSError {
36
+ name: string;
37
+ $metadata: {
38
+ httpStatusCode: number;
39
+ };
40
+ $service: string;
41
+ $retryable: {
42
+ throttling: boolean;
43
+ };
44
+ $fault: 'client' | 'server';
45
+ time: Date;
46
+
47
+ constructor(message: string) {
48
+ super(message);
49
+ this.message = message;
50
+ }
51
+ }
52
+
53
+ describe('Consumer', () => {
54
+ let consumer;
55
+ let clock;
56
+ let handleMessage;
57
+ let handleMessageBatch;
58
+ let sqs;
59
+ const response = {
60
+ Messages: [
61
+ {
62
+ ReceiptHandle: 'receipt-handle',
63
+ MessageId: '123',
64
+ Body: 'body'
65
+ }
66
+ ]
67
+ };
68
+
69
+ beforeEach(() => {
70
+ clock = sinon.useFakeTimers();
71
+ handleMessage = sandbox.stub().resolves(null);
72
+ handleMessageBatch = sandbox.stub().resolves(null);
73
+ sqs = sinon.createStubInstance(SQSClient);
74
+ sqs.send = sinon.stub();
75
+
76
+ sqs.send.withArgs(mockReceiveMessage).resolves(response);
77
+ sqs.send.withArgs(mockDeleteMessage).resolves();
78
+ sqs.send.withArgs(mockDeleteMessageBatch).resolves();
79
+ sqs.send.withArgs(mockChangeMessageVisibility).resolves();
80
+ sqs.send.withArgs(mockChangeMessageVisibilityBatch).resolves();
81
+
82
+ consumer = new Consumer({
83
+ queueUrl: QUEUE_URL,
84
+ region: REGION,
85
+ handleMessage,
86
+ sqs,
87
+ authenticationErrorTimeout: AUTHENTICATION_ERROR_TIMEOUT
88
+ });
89
+ });
90
+
91
+ afterEach(() => {
92
+ clock.restore();
93
+ sandbox.restore();
94
+ });
95
+
96
+ it('requires a queueUrl to be set', () => {
97
+ assert.throws(() => {
98
+ Consumer.create({
99
+ region: REGION,
100
+ handleMessage
101
+ });
102
+ });
103
+ });
104
+
105
+ it('requires a handleMessage or handleMessagesBatch function to be set', () => {
106
+ assert.throws(() => {
107
+ new Consumer({
108
+ handleMessage: undefined,
109
+ region: REGION,
110
+ queueUrl: QUEUE_URL
111
+ });
112
+ });
113
+ });
114
+
115
+ it('requires the batchSize option to be no greater than 10', () => {
116
+ assert.throws(() => {
117
+ new Consumer({
118
+ region: REGION,
119
+ queueUrl: QUEUE_URL,
120
+ handleMessage,
121
+ batchSize: 11
122
+ });
123
+ });
124
+ });
125
+
126
+ it('requires the batchSize option to be greater than 0', () => {
127
+ assert.throws(() => {
128
+ new Consumer({
129
+ region: REGION,
130
+ queueUrl: QUEUE_URL,
131
+ handleMessage,
132
+ batchSize: -1
133
+ });
134
+ });
135
+ });
136
+
137
+ it('requires visibilityTimeout to be set with heartbeatInterval', () => {
138
+ assert.throws(() => {
139
+ new Consumer({
140
+ region: REGION,
141
+ queueUrl: QUEUE_URL,
142
+ handleMessage,
143
+ heartbeatInterval: 30
144
+ });
145
+ });
146
+ });
147
+
148
+ it('requires heartbeatInterval to be less than visibilityTimeout', () => {
149
+ assert.throws(() => {
150
+ new Consumer({
151
+ region: REGION,
152
+ queueUrl: QUEUE_URL,
153
+ handleMessage,
154
+ heartbeatInterval: 30,
155
+ visibilityTimeout: 30
156
+ });
157
+ });
158
+ });
159
+
160
+ describe('.create', () => {
161
+ it('creates a new instance of a Consumer object', () => {
162
+ const instance = Consumer.create({
163
+ region: REGION,
164
+ queueUrl: QUEUE_URL,
165
+ batchSize: 1,
166
+ visibilityTimeout: 10,
167
+ waitTimeSeconds: 10,
168
+ handleMessage
169
+ });
170
+
171
+ assert.instanceOf(instance, Consumer);
172
+ });
173
+ });
174
+
175
+ describe('.start', () => {
176
+ it('fires an error event when an error occurs receiving a message', async () => {
177
+ const receiveErr = new Error('Receive error');
178
+
179
+ sqs.send.withArgs(mockReceiveMessage).rejects(receiveErr);
180
+
181
+ consumer.start();
182
+
183
+ const err: any = await pEvent(consumer, 'error');
184
+
185
+ consumer.stop();
186
+ assert.ok(err);
187
+ assert.equal(err.message, 'SQS receive message failed: Receive error');
188
+ });
189
+
190
+ it('retains sqs error information', async () => {
191
+ const receiveErr = new MockSQSError('Receive error');
192
+ receiveErr.name = 'short code';
193
+ receiveErr.$retryable = {
194
+ throttling: false
195
+ };
196
+ receiveErr.$metadata = {
197
+ httpStatusCode: 403
198
+ };
199
+ receiveErr.time = new Date();
200
+ receiveErr.$service = 'service';
201
+
202
+ sqs.send.withArgs(mockReceiveMessage).rejects(receiveErr);
203
+
204
+ consumer.start();
205
+ const err: any = await pEvent(consumer, 'error');
206
+ consumer.stop();
207
+
208
+ assert.ok(err);
209
+ assert.equal(err.message, 'SQS receive message failed: Receive error');
210
+ assert.equal(err.code, receiveErr.name);
211
+ assert.equal(err.retryable, receiveErr.$retryable.throttling);
212
+ assert.equal(err.statusCode, receiveErr.$metadata.httpStatusCode);
213
+ assert.equal(err.time.toString(), receiveErr.time.toString());
214
+ assert.equal(err.service, receiveErr.$service);
215
+ assert.equal(err.fault, receiveErr.$fault);
216
+ });
217
+
218
+ it('fires a timeout event if handler function takes too long', async () => {
219
+ const handleMessageTimeout = 500;
220
+ consumer = new Consumer({
221
+ queueUrl: QUEUE_URL,
222
+ region: REGION,
223
+ handleMessage: () =>
224
+ new Promise((resolve) => setTimeout(resolve, 1000)),
225
+ handleMessageTimeout,
226
+ sqs,
227
+ authenticationErrorTimeout: AUTHENTICATION_ERROR_TIMEOUT
228
+ });
229
+
230
+ consumer.start();
231
+ const [err]: any = await Promise.all([
232
+ pEvent(consumer, 'timeout_error'),
233
+ clock.tickAsync(handleMessageTimeout)
234
+ ]);
235
+ consumer.stop();
236
+
237
+ assert.ok(err);
238
+ assert.equal(
239
+ err.message,
240
+ `Message handler timed out after ${handleMessageTimeout}ms: Operation timed out.`
241
+ );
242
+ });
243
+
244
+ it('handles unexpected exceptions thrown by the handler function', async () => {
245
+ consumer = new Consumer({
246
+ queueUrl: QUEUE_URL,
247
+ region: REGION,
248
+ handleMessage: () => {
249
+ throw new Error('unexpected parsing error');
250
+ },
251
+ sqs,
252
+ authenticationErrorTimeout: AUTHENTICATION_ERROR_TIMEOUT
253
+ });
254
+
255
+ consumer.start();
256
+ const err: any = await pEvent(consumer, 'processing_error');
257
+ consumer.stop();
258
+
259
+ assert.ok(err);
260
+ assert.equal(
261
+ err.message,
262
+ 'Unexpected message handler failure: unexpected parsing error'
263
+ );
264
+ });
265
+
266
+ it('fires an error event when an error occurs deleting a message', async () => {
267
+ const deleteErr = new Error('Delete error');
268
+
269
+ handleMessage.resolves(null);
270
+ sqs.send.withArgs(mockDeleteMessage).rejects(deleteErr);
271
+
272
+ consumer.start();
273
+ const err: any = await pEvent(consumer, 'error');
274
+ consumer.stop();
275
+
276
+ assert.ok(err);
277
+ assert.equal(err.message, 'SQS delete message failed: Delete error');
278
+ });
279
+
280
+ it('fires a `processing_error` event when a non-`SQSError` error occurs processing a message', async () => {
281
+ const processingErr = new Error('Processing error');
282
+
283
+ handleMessage.rejects(processingErr);
284
+
285
+ consumer.start();
286
+ const [err, message] = await pEvent<
287
+ string | symbol,
288
+ { [key: string]: string }[]
289
+ >(consumer, 'processing_error', {
290
+ multiArgs: true
291
+ });
292
+ consumer.stop();
293
+
294
+ assert.equal(
295
+ err instanceof Error ? err.message : '',
296
+ 'Unexpected message handler failure: Processing error'
297
+ );
298
+ assert.equal(message.MessageId, '123');
299
+ });
300
+
301
+ it('fires an `error` event when an `SQSError` occurs processing a message', async () => {
302
+ const sqsError = new Error('Processing error');
303
+ sqsError.name = 'SQSError';
304
+
305
+ handleMessage.resolves(sqsError);
306
+ sqs.send.withArgs(mockDeleteMessage).rejects(sqsError);
307
+
308
+ consumer.start();
309
+ const [err, message] = await pEvent<
310
+ string | symbol,
311
+ { [key: string]: string }[]
312
+ >(consumer, 'error', {
313
+ multiArgs: true
314
+ });
315
+ consumer.stop();
316
+
317
+ assert.equal(err.message, 'SQS delete message failed: Processing error');
318
+ assert.equal(message.MessageId, '123');
319
+ });
320
+
321
+ it('waits before repolling when a credentials error occurs', async () => {
322
+ const credentialsErr = {
323
+ name: 'CredentialsError',
324
+ message: 'Missing credentials in config'
325
+ };
326
+ sqs.send.withArgs(mockReceiveMessage).rejects(credentialsErr);
327
+ const errorListener = sandbox.stub();
328
+ consumer.on('error', errorListener);
329
+
330
+ consumer.start();
331
+ await clock.tickAsync(AUTHENTICATION_ERROR_TIMEOUT);
332
+ consumer.stop();
333
+
334
+ sandbox.assert.calledTwice(errorListener);
335
+ sandbox.assert.calledWithMatch(sqs.send.firstCall, mockReceiveMessage);
336
+ sandbox.assert.calledWithMatch(sqs.send.secondCall, mockReceiveMessage);
337
+ });
338
+
339
+ it('waits before repolling when a 403 error occurs', async () => {
340
+ const invalidSignatureErr = {
341
+ $metadata: {
342
+ httpStatusCode: 403
343
+ },
344
+ message: 'The security token included in the request is invalid'
345
+ };
346
+ sqs.send.withArgs(mockReceiveMessage).rejects(invalidSignatureErr);
347
+ const errorListener = sandbox.stub();
348
+ consumer.on('error', errorListener);
349
+
350
+ consumer.start();
351
+ await clock.tickAsync(AUTHENTICATION_ERROR_TIMEOUT);
352
+ consumer.stop();
353
+
354
+ sandbox.assert.calledTwice(errorListener);
355
+ sandbox.assert.calledWithMatch(sqs.send.firstCall, mockReceiveMessage);
356
+ sandbox.assert.calledWithMatch(sqs.send.secondCall, mockReceiveMessage);
357
+ });
358
+
359
+ it('waits before repolling when a UnknownEndpoint error occurs', async () => {
360
+ const unknownEndpointErr = {
361
+ name: 'UnknownEndpoint',
362
+ message:
363
+ 'Inaccessible host: `sqs.eu-west-1.amazonaws.com`. This service may not be available in the `eu-west-1` region.'
364
+ };
365
+ sqs.send.withArgs(mockReceiveMessage).rejects(unknownEndpointErr);
366
+ const errorListener = sandbox.stub();
367
+ consumer.on('error', errorListener);
368
+
369
+ consumer.start();
370
+ await clock.tickAsync(AUTHENTICATION_ERROR_TIMEOUT);
371
+ consumer.stop();
372
+
373
+ sandbox.assert.calledTwice(errorListener);
374
+ sandbox.assert.calledTwice(sqs.send);
375
+ sandbox.assert.calledWithMatch(sqs.send.firstCall, mockReceiveMessage);
376
+ sandbox.assert.calledWithMatch(sqs.send.secondCall, mockReceiveMessage);
377
+ });
378
+
379
+ it('waits before repolling when a polling timeout is set', async () => {
380
+ consumer = new Consumer({
381
+ queueUrl: QUEUE_URL,
382
+ region: REGION,
383
+ handleMessage,
384
+ sqs,
385
+ authenticationErrorTimeout: AUTHENTICATION_ERROR_TIMEOUT,
386
+ pollingWaitTimeMs: POLLING_TIMEOUT
387
+ });
388
+
389
+ consumer.start();
390
+ await clock.tickAsync(POLLING_TIMEOUT);
391
+ consumer.stop();
392
+
393
+ sandbox.assert.callCount(sqs.send, 4);
394
+ sandbox.assert.calledWithMatch(sqs.send.firstCall, mockReceiveMessage);
395
+ sandbox.assert.calledWithMatch(sqs.send.secondCall, mockDeleteMessage);
396
+ sandbox.assert.calledWithMatch(sqs.send.thirdCall, mockReceiveMessage);
397
+ sandbox.assert.calledWithMatch(sqs.send.getCall(3), mockDeleteMessage);
398
+ });
399
+
400
+ it('fires a message_received event when a message is received', async () => {
401
+ consumer.start();
402
+ const message = await pEvent(consumer, 'message_received');
403
+ consumer.stop();
404
+
405
+ assert.equal(message, response.Messages[0]);
406
+ });
407
+
408
+ it('fires a message_processed event when a message is successfully deleted', async () => {
409
+ handleMessage.resolves();
410
+
411
+ consumer.start();
412
+ const message = await pEvent(consumer, 'message_received');
413
+ consumer.stop();
414
+
415
+ assert.equal(message, response.Messages[0]);
416
+ });
417
+
418
+ it('calls the handleMessage function when a message is received', async () => {
419
+ consumer.start();
420
+ await pEvent(consumer, 'message_processed');
421
+ consumer.stop();
422
+
423
+ sandbox.assert.calledWith(handleMessage, response.Messages[0]);
424
+ });
425
+
426
+ it('deletes the message when the handleMessage function is called', async () => {
427
+ handleMessage.resolves();
428
+
429
+ consumer.start();
430
+ await pEvent(consumer, 'message_processed');
431
+ consumer.stop();
432
+
433
+ sandbox.assert.calledWith(sqs.send.secondCall, mockDeleteMessage);
434
+ sandbox.assert.match(
435
+ sqs.send.secondCall.args[0].input,
436
+ sinon.match({
437
+ QueueUrl: QUEUE_URL,
438
+ ReceiptHandle: 'receipt-handle'
439
+ })
440
+ );
441
+ });
442
+
443
+ it("doesn't delete the message when a processing error is reported", async () => {
444
+ handleMessage.rejects(new Error('Processing error'));
445
+
446
+ consumer.start();
447
+ await pEvent(consumer, 'processing_error');
448
+ consumer.stop();
449
+
450
+ sandbox.assert.neverCalledWithMatch(sqs.send, mockDeleteMessage);
451
+ });
452
+
453
+ it('consumes another message once one is processed', async () => {
454
+ handleMessage.resolves();
455
+
456
+ consumer.start();
457
+ await clock.runToLastAsync();
458
+ consumer.stop();
459
+
460
+ sandbox.assert.calledTwice(handleMessage);
461
+ });
462
+
463
+ it("doesn't consume more messages when called multiple times", () => {
464
+ sqs.send
465
+ .withArgs(mockReceiveMessage)
466
+ .resolves(new Promise((res) => setTimeout(res, 100)));
467
+ consumer.start();
468
+ consumer.start();
469
+ consumer.start();
470
+ consumer.start();
471
+ consumer.start();
472
+ consumer.stop();
473
+
474
+ sqs.send.calledOnceWith(mockReceiveMessage);
475
+ });
476
+
477
+ it('consumes multiple messages when the batchSize is greater than 1', async () => {
478
+ sqs.send.withArgs(mockReceiveMessage).resolves({
479
+ Messages: [
480
+ {
481
+ ReceiptHandle: 'receipt-handle-1',
482
+ MessageId: '1',
483
+ Body: 'body-1'
484
+ },
485
+ {
486
+ ReceiptHandle: 'receipt-handle-2',
487
+ MessageId: '2',
488
+ Body: 'body-2'
489
+ },
490
+ {
491
+ ReceiptHandle: 'receipt-handle-3',
492
+ MessageId: '3',
493
+ Body: 'body-3'
494
+ }
495
+ ]
496
+ });
497
+
498
+ consumer = new Consumer({
499
+ queueUrl: QUEUE_URL,
500
+ messageAttributeNames: ['attribute-1', 'attribute-2'],
501
+ region: REGION,
502
+ handleMessage,
503
+ batchSize: 3,
504
+ sqs
505
+ });
506
+
507
+ consumer.start();
508
+ await pEvent(consumer, 'message_received');
509
+ consumer.stop();
510
+
511
+ sandbox.assert.callCount(handleMessage, 3);
512
+ sandbox.assert.calledWithMatch(sqs.send.firstCall, mockReceiveMessage);
513
+ sandbox.assert.match(
514
+ sqs.send.firstCall.args[0].input,
515
+ sinon.match({
516
+ QueueUrl: QUEUE_URL,
517
+ AttributeNames: [],
518
+ MessageAttributeNames: ['attribute-1', 'attribute-2'],
519
+ MaxNumberOfMessages: 3,
520
+ WaitTimeSeconds: AUTHENTICATION_ERROR_TIMEOUT,
521
+ VisibilityTimeout: undefined
522
+ })
523
+ );
524
+ });
525
+
526
+ it("consumes messages with message attribute 'ApproximateReceiveCount'", async () => {
527
+ const messageWithAttr = {
528
+ ReceiptHandle: 'receipt-handle-1',
529
+ MessageId: '1',
530
+ Body: 'body-1',
531
+ Attributes: {
532
+ ApproximateReceiveCount: 1
533
+ }
534
+ };
535
+
536
+ sqs.send.withArgs(mockReceiveMessage).resolves({
537
+ Messages: [messageWithAttr]
538
+ });
539
+
540
+ consumer = new Consumer({
541
+ queueUrl: QUEUE_URL,
542
+ attributeNames: ['ApproximateReceiveCount'],
543
+ region: REGION,
544
+ handleMessage,
545
+ sqs
546
+ });
547
+
548
+ consumer.start();
549
+ const message = await pEvent(consumer, 'message_received');
550
+ consumer.stop();
551
+
552
+ sandbox.assert.calledWith(sqs.send, mockReceiveMessage);
553
+ sandbox.assert.match(
554
+ sqs.send.firstCall.args[0].input,
555
+ sinon.match({
556
+ QueueUrl: QUEUE_URL,
557
+ AttributeNames: ['ApproximateReceiveCount'],
558
+ MessageAttributeNames: [],
559
+ MaxNumberOfMessages: 1,
560
+ WaitTimeSeconds: AUTHENTICATION_ERROR_TIMEOUT,
561
+ VisibilityTimeout: undefined
562
+ })
563
+ );
564
+
565
+ assert.equal(message, messageWithAttr);
566
+ });
567
+
568
+ it('fires an emptyQueue event when all messages have been consumed', async () => {
569
+ sqs.send.withArgs(mockReceiveMessage).resolves({});
570
+
571
+ consumer.start();
572
+ await pEvent(consumer, 'empty');
573
+ consumer.stop();
574
+ });
575
+
576
+ it('terminate message visibility timeout on processing error', async () => {
577
+ handleMessage.rejects(new Error('Processing error'));
578
+
579
+ consumer.terminateVisibilityTimeout = true;
580
+
581
+ consumer.start();
582
+ await pEvent(consumer, 'processing_error');
583
+ consumer.stop();
584
+
585
+ sandbox.assert.calledWith(
586
+ sqs.send.secondCall,
587
+ mockChangeMessageVisibility
588
+ );
589
+ sandbox.assert.match(
590
+ sqs.send.secondCall.args[0].input,
591
+ sinon.match({
592
+ QueueUrl: QUEUE_URL,
593
+ ReceiptHandle: 'receipt-handle',
594
+ VisibilityTimeout: 0
595
+ })
596
+ );
597
+ });
598
+
599
+ it('does not terminate visibility timeout when `terminateVisibilityTimeout` option is false', async () => {
600
+ handleMessage.rejects(new Error('Processing error'));
601
+ consumer.terminateVisibilityTimeout = false;
602
+
603
+ consumer.start();
604
+ await pEvent(consumer, 'processing_error');
605
+ consumer.stop();
606
+
607
+ sqs.send.neverCalledWith(mockChangeMessageVisibility);
608
+ });
609
+
610
+ it('fires error event when failed to terminate visibility timeout on processing error', async () => {
611
+ handleMessage.rejects(new Error('Processing error'));
612
+
613
+ const sqsError = new Error('Processing error');
614
+ sqsError.name = 'SQSError';
615
+ sqs.send.withArgs(mockChangeMessageVisibility).rejects(sqsError);
616
+ consumer.terminateVisibilityTimeout = true;
617
+
618
+ consumer.start();
619
+ await pEvent(consumer, 'error');
620
+ consumer.stop();
621
+
622
+ sandbox.assert.calledWith(
623
+ sqs.send.secondCall,
624
+ mockChangeMessageVisibility
625
+ );
626
+ sandbox.assert.match(
627
+ sqs.send.secondCall.args[0].input,
628
+ sinon.match({
629
+ QueueUrl: QUEUE_URL,
630
+ ReceiptHandle: 'receipt-handle',
631
+ VisibilityTimeout: 0
632
+ })
633
+ );
634
+ });
635
+
636
+ it('fires response_processed event for each batch', async () => {
637
+ sqs.send.withArgs(mockReceiveMessage).resolves({
638
+ Messages: [
639
+ {
640
+ ReceiptHandle: 'receipt-handle-1',
641
+ MessageId: '1',
642
+ Body: 'body-1'
643
+ },
644
+ {
645
+ ReceiptHandle: 'receipt-handle-2',
646
+ MessageId: '2',
647
+ Body: 'body-2'
648
+ }
649
+ ]
650
+ });
651
+ handleMessage.resolves(null);
652
+
653
+ consumer = new Consumer({
654
+ queueUrl: QUEUE_URL,
655
+ messageAttributeNames: ['attribute-1', 'attribute-2'],
656
+ region: REGION,
657
+ handleMessage,
658
+ batchSize: 2,
659
+ sqs
660
+ });
661
+
662
+ consumer.start();
663
+ await pEvent(consumer, 'response_processed');
664
+ consumer.stop();
665
+
666
+ sandbox.assert.callCount(handleMessage, 2);
667
+ });
668
+
669
+ it('calls the handleMessagesBatch function when a batch of messages is received', async () => {
670
+ consumer = new Consumer({
671
+ queueUrl: QUEUE_URL,
672
+ messageAttributeNames: ['attribute-1', 'attribute-2'],
673
+ region: REGION,
674
+ handleMessageBatch,
675
+ batchSize: 2,
676
+ sqs
677
+ });
678
+
679
+ consumer.start();
680
+ await pEvent(consumer, 'response_processed');
681
+ consumer.stop();
682
+
683
+ sandbox.assert.callCount(handleMessageBatch, 1);
684
+ });
685
+
686
+ it('prefers handleMessagesBatch over handleMessage when both are set', async () => {
687
+ consumer = new Consumer({
688
+ queueUrl: QUEUE_URL,
689
+ messageAttributeNames: ['attribute-1', 'attribute-2'],
690
+ region: REGION,
691
+ handleMessageBatch,
692
+ handleMessage,
693
+ batchSize: 2,
694
+ sqs
695
+ });
696
+
697
+ consumer.start();
698
+ await pEvent(consumer, 'response_processed');
699
+ consumer.stop();
700
+
701
+ sandbox.assert.callCount(handleMessageBatch, 1);
702
+ sandbox.assert.callCount(handleMessage, 0);
703
+ });
704
+
705
+ it('uses the correct visibility timeout for long running handler functions', async () => {
706
+ consumer = new Consumer({
707
+ queueUrl: QUEUE_URL,
708
+ region: REGION,
709
+ handleMessage: () =>
710
+ new Promise((resolve) => setTimeout(resolve, 75000)),
711
+ sqs,
712
+ visibilityTimeout: 40,
713
+ heartbeatInterval: 30
714
+ });
715
+ const clearIntervalSpy = sinon.spy(global, 'clearInterval');
716
+
717
+ consumer.start();
718
+ await Promise.all([
719
+ pEvent(consumer, 'response_processed'),
720
+ clock.tickAsync(75000)
721
+ ]);
722
+ consumer.stop();
723
+
724
+ sandbox.assert.calledWith(
725
+ sqs.send.secondCall,
726
+ mockChangeMessageVisibility
727
+ );
728
+ sandbox.assert.match(
729
+ sqs.send.secondCall.args[0].input,
730
+ sinon.match({
731
+ QueueUrl: QUEUE_URL,
732
+ ReceiptHandle: 'receipt-handle',
733
+ VisibilityTimeout: 40
734
+ })
735
+ );
736
+ sandbox.assert.calledWith(
737
+ sqs.send.thirdCall,
738
+ mockChangeMessageVisibility
739
+ );
740
+ sandbox.assert.match(
741
+ sqs.send.thirdCall.args[0].input,
742
+ sinon.match({
743
+ QueueUrl: QUEUE_URL,
744
+ ReceiptHandle: 'receipt-handle',
745
+ VisibilityTimeout: 40
746
+ })
747
+ );
748
+ sandbox.assert.calledOnce(clearIntervalSpy);
749
+ });
750
+
751
+ it('passes in the correct visibility timeout for long running batch handler functions', async () => {
752
+ sqs.send.withArgs(mockReceiveMessage).resolves({
753
+ Messages: [
754
+ { MessageId: '1', ReceiptHandle: 'receipt-handle-1', Body: 'body-1' },
755
+ { MessageId: '2', ReceiptHandle: 'receipt-handle-2', Body: 'body-2' },
756
+ { MessageId: '3', ReceiptHandle: 'receipt-handle-3', Body: 'body-3' }
757
+ ]
758
+ });
759
+ consumer = new Consumer({
760
+ queueUrl: QUEUE_URL,
761
+ region: REGION,
762
+ handleMessageBatch: () =>
763
+ new Promise((resolve) => setTimeout(resolve, 75000)),
764
+ batchSize: 3,
765
+ sqs,
766
+ visibilityTimeout: 40,
767
+ heartbeatInterval: 30
768
+ });
769
+ const clearIntervalSpy = sinon.spy(global, 'clearInterval');
770
+
771
+ consumer.start();
772
+ await Promise.all([
773
+ pEvent(consumer, 'response_processed'),
774
+ clock.tickAsync(75000)
775
+ ]);
776
+ consumer.stop();
777
+
778
+ sandbox.assert.calledWith(
779
+ sqs.send.secondCall,
780
+ mockChangeMessageVisibilityBatch
781
+ );
782
+ sandbox.assert.match(
783
+ sqs.send.secondCall.args[0].input,
784
+ sinon.match({
785
+ QueueUrl: QUEUE_URL,
786
+ Entries: sinon.match.array.deepEquals([
787
+ {
788
+ Id: '1',
789
+ ReceiptHandle: 'receipt-handle-1',
790
+ VisibilityTimeout: 40
791
+ },
792
+ {
793
+ Id: '2',
794
+ ReceiptHandle: 'receipt-handle-2',
795
+ VisibilityTimeout: 40
796
+ },
797
+ {
798
+ Id: '3',
799
+ ReceiptHandle: 'receipt-handle-3',
800
+ VisibilityTimeout: 40
801
+ }
802
+ ])
803
+ })
804
+ );
805
+ sandbox.assert.calledWith(
806
+ sqs.send.thirdCall,
807
+ mockChangeMessageVisibilityBatch
808
+ );
809
+ sandbox.assert.match(
810
+ sqs.send.thirdCall.args[0].input,
811
+ sinon.match({
812
+ QueueUrl: QUEUE_URL,
813
+ Entries: [
814
+ {
815
+ Id: '1',
816
+ ReceiptHandle: 'receipt-handle-1',
817
+ VisibilityTimeout: 40
818
+ },
819
+ {
820
+ Id: '2',
821
+ ReceiptHandle: 'receipt-handle-2',
822
+ VisibilityTimeout: 40
823
+ },
824
+ {
825
+ Id: '3',
826
+ ReceiptHandle: 'receipt-handle-3',
827
+ VisibilityTimeout: 40
828
+ }
829
+ ]
830
+ })
831
+ );
832
+ sandbox.assert.calledOnce(clearIntervalSpy);
833
+ });
834
+
835
+ it('emit error when changing visibility timeout fails', async () => {
836
+ sqs.send.withArgs(mockReceiveMessage).resolves({
837
+ Messages: [
838
+ { MessageId: '1', ReceiptHandle: 'receipt-handle-1', Body: 'body-1' }
839
+ ]
840
+ });
841
+ consumer = new Consumer({
842
+ queueUrl: QUEUE_URL,
843
+ region: REGION,
844
+ handleMessage: () =>
845
+ new Promise((resolve) => setTimeout(resolve, 75000)),
846
+ sqs,
847
+ visibilityTimeout: 40,
848
+ heartbeatInterval: 30
849
+ });
850
+
851
+ const receiveErr = new MockSQSError('failed');
852
+ sqs.send.withArgs(mockChangeMessageVisibility).rejects(receiveErr);
853
+
854
+ consumer.start();
855
+ const [err]: any[] = await Promise.all([
856
+ pEvent(consumer, 'error'),
857
+ clock.tickAsync(75000)
858
+ ]);
859
+ consumer.stop();
860
+
861
+ assert.ok(err);
862
+ assert.equal(err.message, 'Error changing visibility timeout: failed');
863
+ });
864
+
865
+ it('emit error when changing visibility timeout fails for batch handler functions', async () => {
866
+ sqs.send.withArgs(mockReceiveMessage).resolves({
867
+ Messages: [
868
+ { MessageId: '1', ReceiptHandle: 'receipt-handle-1', Body: 'body-1' },
869
+ { MessageId: '2', ReceiptHandle: 'receipt-handle-2', Body: 'body-2' }
870
+ ]
871
+ });
872
+ consumer = new Consumer({
873
+ queueUrl: QUEUE_URL,
874
+ region: REGION,
875
+ handleMessageBatch: () =>
876
+ new Promise((resolve) => setTimeout(resolve, 75000)),
877
+ sqs,
878
+ batchSize: 2,
879
+ visibilityTimeout: 40,
880
+ heartbeatInterval: 30
881
+ });
882
+
883
+ const receiveErr = new MockSQSError('failed');
884
+ sqs.send.withArgs(mockChangeMessageVisibilityBatch).rejects(receiveErr);
885
+
886
+ consumer.start();
887
+ const [err]: any[] = await Promise.all([
888
+ pEvent(consumer, 'error'),
889
+ clock.tickAsync(75000)
890
+ ]);
891
+ consumer.stop();
892
+
893
+ assert.ok(err);
894
+ assert.equal(err.message, 'Error changing visibility timeout: failed');
895
+ });
896
+ });
897
+
898
+ describe('.stop', () => {
899
+ it('stops the consumer polling for messages', async () => {
900
+ consumer.start();
901
+ consumer.stop();
902
+
903
+ await Promise.all([pEvent(consumer, 'stopped'), clock.runAllAsync()]);
904
+
905
+ sandbox.assert.calledOnce(handleMessage);
906
+ });
907
+
908
+ it('fires a stopped event when last poll occurs after stopping', async () => {
909
+ consumer.start();
910
+ consumer.stop();
911
+ await Promise.all([pEvent(consumer, 'stopped'), clock.runAllAsync()]);
912
+ });
913
+
914
+ it('fires a stopped event only once when stopped multiple times', async () => {
915
+ const handleStop = sandbox.stub().returns(null);
916
+
917
+ consumer.on('stopped', handleStop);
918
+
919
+ consumer.start();
920
+ consumer.stop();
921
+ consumer.stop();
922
+ consumer.stop();
923
+ await clock.runAllAsync();
924
+
925
+ sandbox.assert.calledOnce(handleStop);
926
+ });
927
+
928
+ it('fires a stopped event a second time if started and stopped twice', async () => {
929
+ const handleStop = sandbox.stub().returns(null);
930
+
931
+ consumer.on('stopped', handleStop);
932
+
933
+ consumer.start();
934
+ consumer.stop();
935
+ consumer.start();
936
+ consumer.stop();
937
+ await clock.runAllAsync();
938
+
939
+ sandbox.assert.calledTwice(handleStop);
940
+ });
941
+ });
942
+
943
+ describe('isRunning', async () => {
944
+ it('returns true if the consumer is polling', () => {
945
+ consumer.start();
946
+ assert.isTrue(consumer.isRunning);
947
+ consumer.stop();
948
+ });
949
+
950
+ it('returns false if the consumer is not polling', () => {
951
+ consumer.start();
952
+ consumer.stop();
953
+ assert.isFalse(consumer.isRunning);
954
+ });
955
+ });
956
+
957
+ describe('delete messages property', () => {
958
+ beforeEach(() => {
959
+ consumer = new Consumer({
960
+ queueUrl: QUEUE_URL,
961
+ region: REGION,
962
+ handleMessage,
963
+ sqs,
964
+ authenticationErrorTimeout: AUTHENTICATION_ERROR_TIMEOUT,
965
+ shouldDeleteMessages: false
966
+ });
967
+ });
968
+
969
+ it('do not deletes the message when the handleMessage function is called', async () => {
970
+ handleMessage.resolves();
971
+
972
+ consumer.start();
973
+ await pEvent(consumer, 'message_processed');
974
+ consumer.stop();
975
+
976
+ sandbox.assert.neverCalledWithMatch(sqs.send, mockDeleteMessage);
977
+ });
978
+ });
979
+ });