sqs-consumer 6.0.1 → 6.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqs-consumer",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Build SQS-based Node applications without the boilerplate",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,7 @@
8
8
  "build": "npm run clean && tsc",
9
9
  "watch": "tsc --watch",
10
10
  "clean": "rm -fr dist/*",
11
- "prepublish": "npm run build",
11
+ "prepare": "npm run build",
12
12
  "pretest": "npm run build",
13
13
  "test": "mocha --recursive --full-trace --exit",
14
14
  "coverage": "nyc mocha && nyc report --reporter=html && nyc report --reporter=json-summary",
@@ -1,979 +0,0 @@
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
- });