@gugananuvem/aws-local-simulator 1.0.11 → 1.0.12

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.
@@ -26,16 +26,24 @@ class SQSSimulator {
26
26
  // Carrega filas da configuração
27
27
  if (this.config.sqs?.queues) {
28
28
  for (const queueConfig of this.config.sqs.queues) {
29
- this.createQueue(queueConfig.name);
29
+ const name = typeof queueConfig === 'string' ? queueConfig : queueConfig.name;
30
+ this.createQueue(name);
31
+ // Restore persisted messages for this queue
32
+ const queue = this.queues.get(name);
33
+ if (queue) {
34
+ queue.messages = this.store.read(name) || [];
35
+ queue.messageCount = queue.messages.length;
36
+ }
30
37
  }
31
38
  }
32
-
39
+
33
40
  // Carrega filas existentes do disco
34
41
  const savedQueues = this.store.read('__queues__');
35
42
  if (savedQueues) {
36
43
  for (const [name, data] of Object.entries(savedQueues)) {
37
44
  if (!this.queues.has(name)) {
38
- this.queues.set(name, data);
45
+ const messages = this.store.read(name) || [];
46
+ this.queues.set(name, { ...data, messages });
39
47
  }
40
48
  }
41
49
  }
@@ -45,7 +53,7 @@ class SQSSimulator {
45
53
  if (this.queues.has(queueName)) {
46
54
  return { error: { code: 'QueueAlreadyExists', message: 'Queue already exists' }, status: 409 };
47
55
  }
48
-
56
+
49
57
  const queue = {
50
58
  name: queueName,
51
59
  url: `http://localhost:${this.config.ports.sqs}/queue/${queueName}`,
@@ -57,13 +65,16 @@ class SQSSimulator {
57
65
  createdAt: new Date().toISOString(),
58
66
  messageCount: 0
59
67
  };
60
-
68
+
61
69
  this.queues.set(queueName, queue);
62
70
  this.persistQueues();
63
- this.store.write(queueName, []);
64
-
71
+ // Only initialize message store if it doesn't already exist
72
+ if (!this.store.read(queueName)) {
73
+ this.store.write(queueName, []);
74
+ }
75
+
65
76
  logger.debug(`✅ Fila SQS criada: ${queueName}`);
66
-
77
+
67
78
  return { queue };
68
79
  }
69
80
 
@@ -88,30 +99,39 @@ class SQSSimulator {
88
99
  }
89
100
  }
90
101
 
102
+ // Extracts queue name from QueueName param or QueueUrl (AWS CLI v2 JSON protocol sends QueueUrl)
103
+ resolveQueueName(req) {
104
+ const name = req.query.QueueName || req.body.QueueName;
105
+ if (name) return name;
106
+ const url = req.query.QueueUrl || req.body.QueueUrl;
107
+ if (url) return url.split('/').pop();
108
+ return undefined;
109
+ }
110
+
91
111
  createQueueAction(req) {
92
112
  const queueName = req.query.QueueName || req.body.QueueName;
93
113
  const result = this.createQueue(queueName);
94
-
114
+
95
115
  if (result.error) {
96
116
  return result;
97
117
  }
98
-
118
+
99
119
  return { queueUrl: result.queue.url };
100
120
  }
101
121
 
102
122
  sendMessageAction(req) {
103
- const queueName = req.query.QueueName || req.body.QueueName;
123
+ const queueName = this.resolveQueueName(req);
104
124
  const queue = this.queues.get(queueName);
105
-
125
+
106
126
  if (!queue) {
107
127
  return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
108
128
  }
109
-
129
+
110
130
  const messageBody = req.query.MessageBody || req.body.MessageBody;
111
131
  const messageId = crypto.randomUUID();
112
132
  const receiptHandle = crypto.randomUUID();
113
133
  const md5 = crypto.createHash('md5').update(messageBody).digest('hex');
114
-
134
+
115
135
  const message = {
116
136
  MessageId: messageId,
117
137
  ReceiptHandle: receiptHandle,
@@ -120,44 +140,40 @@ class SQSSimulator {
120
140
  Attributes: {},
121
141
  timestamp: Date.now()
122
142
  };
123
-
143
+
124
144
  queue.messages.push(message);
125
145
  queue.messageCount++;
126
146
  this.persistQueue(queueName);
127
-
147
+
128
148
  logger.verboso(`📨 Mensagem enviada para ${queueName}: ${messageId}`);
129
-
130
- // Processa imediatamente se houver handler
149
+
131
150
  if (queue.handler) {
132
151
  this.processQueueMessages(queue);
133
152
  }
134
-
135
- return {
136
- messageId,
137
- md5
138
- };
153
+
154
+ return { messageId, md5 };
139
155
  }
140
156
 
141
157
  sendMessageBatchAction(req) {
142
- const queueName = req.query.QueueName || req.body.QueueName;
158
+ const queueName = this.resolveQueueName(req);
143
159
  const queue = this.queues.get(queueName);
144
-
160
+
145
161
  if (!queue) {
146
162
  return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
147
163
  }
148
-
164
+
149
165
  const entries = req.query.SendMessageBatchRequestEntry || req.body.SendMessageBatchRequestEntry;
150
166
  const batchEntries = Array.isArray(entries) ? entries : [entries];
151
-
167
+
152
168
  const successful = [];
153
169
  const failed = [];
154
-
170
+
155
171
  for (const entry of batchEntries) {
156
172
  try {
157
173
  const messageId = crypto.randomUUID();
158
174
  const receiptHandle = crypto.randomUUID();
159
175
  const md5 = crypto.createHash('md5').update(entry.MessageBody).digest('hex');
160
-
176
+
161
177
  const message = {
162
178
  MessageId: messageId,
163
179
  ReceiptHandle: receiptHandle,
@@ -166,10 +182,10 @@ class SQSSimulator {
166
182
  Attributes: {},
167
183
  timestamp: Date.now()
168
184
  };
169
-
185
+
170
186
  queue.messages.push(message);
171
187
  queue.messageCount++;
172
-
188
+
173
189
  successful.push({
174
190
  Id: entry.Id,
175
191
  MessageId: messageId,
@@ -183,61 +199,62 @@ class SQSSimulator {
183
199
  });
184
200
  }
185
201
  }
186
-
202
+
187
203
  this.persistQueue(queueName);
188
-
204
+
189
205
  if (queue.handler && successful.length > 0) {
190
206
  this.processQueueMessages(queue);
191
207
  }
192
-
208
+
193
209
  return { successful, failed };
194
210
  }
195
211
 
196
212
  receiveMessageAction(req) {
197
- const queueName = req.query.QueueName || req.body.QueueName;
213
+ const queueName = this.resolveQueueName(req);
198
214
  const queue = this.queues.get(queueName);
199
-
215
+
200
216
  if (!queue) {
201
217
  return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
202
218
  }
203
-
219
+
204
220
  const maxNumberOfMessages = parseInt(req.query.MaxNumberOfMessages || req.body.MaxNumberOfMessages || 1);
205
221
  const messages = queue.messages.splice(0, maxNumberOfMessages);
206
-
222
+
207
223
  if (messages.length > 0) {
224
+ queue.messageCount -= messages.length;
208
225
  this.persistQueue(queueName);
209
226
  }
210
-
227
+
211
228
  return { messages };
212
229
  }
213
230
 
214
231
  deleteMessageAction(req) {
215
- const queueName = req.query.QueueName || req.body.QueueName;
232
+ const queueName = this.resolveQueueName(req);
216
233
  const receiptHandle = req.query.ReceiptHandle || req.body.ReceiptHandle;
217
234
  const queue = this.queues.get(queueName);
218
-
235
+
219
236
  if (!queue) {
220
237
  return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
221
238
  }
222
-
239
+
223
240
  const index = queue.messages.findIndex(m => m.ReceiptHandle === receiptHandle);
224
241
  if (index !== -1) {
225
242
  queue.messages.splice(index, 1);
226
243
  queue.messageCount--;
227
244
  this.persistQueue(queueName);
228
245
  }
229
-
246
+
230
247
  return { success: true };
231
248
  }
232
249
 
233
250
  getQueueUrlAction(req) {
234
- const queueName = req.query.QueueName || req.body.QueueName;
251
+ const queueName = this.resolveQueueName(req);
235
252
  const queue = this.queues.get(queueName);
236
-
253
+
237
254
  if (!queue) {
238
255
  return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
239
256
  }
240
-
257
+
241
258
  return { queueUrl: queue.url };
242
259
  }
243
260
 
@@ -247,32 +264,32 @@ class SQSSimulator {
247
264
  url: q.url,
248
265
  messageCount: q.messageCount
249
266
  }));
250
-
267
+
251
268
  return { queues };
252
269
  }
253
270
 
254
271
  attachLambdaToQueue(queueName, handler, options = {}) {
255
272
  const queue = this.queues.get(queueName);
256
-
273
+
257
274
  if (!queue) {
258
275
  this.createQueue(queueName);
259
276
  return this.attachLambdaToQueue(queueName, handler, options);
260
277
  }
261
-
278
+
262
279
  queue.handler = handler;
263
280
  queue.batchSize = options.batchSize || 10;
264
281
  this.persistQueue(queueName);
265
-
282
+
266
283
  logger.debug(`🔗 Lambda associada à fila ${queueName}`);
267
-
284
+
268
285
  return queue;
269
286
  }
270
287
 
271
288
  async processQueueMessages(queue) {
272
289
  if (!queue.handler || queue.messages.length === 0) return;
273
-
290
+
274
291
  const messagesToProcess = queue.messages.splice(0, queue.batchSize);
275
-
292
+
276
293
  const event = {
277
294
  Records: messagesToProcess.map(msg => ({
278
295
  messageId: msg.MessageId,
@@ -286,27 +303,29 @@ class SQSSimulator {
286
303
  awsRegion: 'local'
287
304
  }))
288
305
  };
289
-
306
+
290
307
  logger.verboso(`🔄 Processando ${messagesToProcess.length} mensagens da fila ${queue.name}`);
291
-
308
+
292
309
  try {
293
310
  const result = await queue.handler(event);
294
-
311
+
295
312
  if (result && result.batchItemFailures && result.batchItemFailures.length > 0) {
296
313
  const failedIds = new Set(result.batchItemFailures.map(f => f.itemIdentifier));
297
314
  const failedMessages = messagesToProcess.filter(msg => failedIds.has(msg.MessageId));
298
315
  queue.messages.unshift(...failedMessages);
299
316
  logger.warn(`⚠️ ${failedMessages.length} mensagens falharam`);
317
+ } else {
318
+ queue.messageCount -= messagesToProcess.length;
300
319
  }
301
320
  } catch (error) {
302
321
  logger.error(`❌ Erro ao processar mensagens:`, error);
303
322
  queue.messages.unshift(...messagesToProcess);
304
323
  }
305
-
324
+
306
325
  this.persistQueue(queue.name);
307
326
  }
308
327
 
309
- persistQueues() {
328
+ persistQueues() {
310
329
  const queuesObj = {};
311
330
  for (const [name, queue] of this.queues.entries()) {
312
331
  queuesObj[name] = {
@@ -348,244 +367,6 @@ class SQSSimulator {
348
367
  }
349
368
  }
350
369
 
351
- attachLambdaToQueue(queueName, handler, options = {}) {
352
- const queue = this.queues.get(queueName);
353
-
354
- if (!queue) {
355
- this.createQueue(queueName);
356
- return this.attachLambdaToQueue(queueName, handler, options);
357
- }
358
-
359
- queue.handler = handler;
360
- queue.batchSize = options.batchSize || 10;
361
- this.persistQueues();
362
-
363
- logger.debug(`🔗 Lambda associada à fila ${queueName}`);
364
-
365
- return queue;
366
- }
367
-
368
- async processQueueMessages(queue) {
369
- if (!queue.handler || queue.messages.length === 0) return;
370
-
371
- const messagesToProcess = queue.messages.splice(0, queue.batchSize);
372
-
373
- const event = {
374
- Records: messagesToProcess.map(msg => ({
375
- messageId: msg.MessageId,
376
- receiptHandle: msg.ReceiptHandle,
377
- body: msg.Body,
378
- attributes: msg.Attributes,
379
- messageAttributes: {},
380
- md5OfBody: msg.MD5OfBody,
381
- eventSource: 'aws:sqs',
382
- eventSourceARN: queue.arn,
383
- awsRegion: 'local'
384
- }))
385
- };
386
-
387
- logger.verboso(`🔄 Processando ${messagesToProcess.length} mensagens da fila ${queue.name}`);
388
-
389
- try {
390
- const result = await queue.handler(event);
391
-
392
- if (result && result.batchItemFailures && result.batchItemFailures.length > 0) {
393
- const failedIds = new Set(result.batchItemFailures.map(f => f.itemIdentifier));
394
- const failedMessages = messagesToProcess.filter(msg => failedIds.has(msg.MessageId));
395
- queue.messages.unshift(...failedMessages);
396
- logger.warn(`⚠️ ${failedMessages.length} mensagens falharam`);
397
- } else {
398
- queue.messageCount -= messagesToProcess.length;
399
- }
400
- } catch (error) {
401
- logger.error(`❌ Erro ao processar mensagens:`, error);
402
- queue.messages.unshift(...messagesToProcess);
403
- }
404
-
405
- this.persistQueue(queue.name);
406
- }
407
-
408
- handleRequest(action, req, res) {
409
- switch(action) {
410
- case 'CreateQueue':
411
- return this.createQueueAction(req);
412
- case 'SendMessage':
413
- return this.sendMessageAction(req);
414
- case 'SendMessageBatch':
415
- return this.sendMessageBatchAction(req);
416
- case 'ReceiveMessage':
417
- return this.receiveMessageAction(req);
418
- case 'DeleteMessage':
419
- return this.deleteMessageAction(req);
420
- case 'GetQueueUrl':
421
- return this.getQueueUrlAction(req);
422
- case 'ListQueues':
423
- return this.listQueuesAction(req);
424
- default:
425
- return { error: { code: 'InvalidAction', message: `Action ${action} not supported` }, status: 400 };
426
- }
427
- }
428
-
429
- createQueueAction(req) {
430
- const queueName = req.query.QueueName || req.body.QueueName;
431
- const result = this.createQueue(queueName);
432
-
433
- if (result.error) {
434
- return result;
435
- }
436
-
437
- return { queueUrl: result.queue.url };
438
- }
439
-
440
- sendMessageAction(req) {
441
- const queueName = req.query.QueueName || req.body.QueueName;
442
- const queue = this.queues.get(queueName);
443
-
444
- if (!queue) {
445
- return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
446
- }
447
-
448
- const messageBody = req.query.MessageBody || req.body.MessageBody;
449
- const messageId = crypto.randomUUID();
450
- const receiptHandle = crypto.randomUUID();
451
- const md5 = crypto.createHash('md5').update(messageBody).digest('hex');
452
-
453
- const message = {
454
- MessageId: messageId,
455
- ReceiptHandle: receiptHandle,
456
- Body: messageBody,
457
- MD5OfBody: md5,
458
- Attributes: {},
459
- timestamp: Date.now()
460
- };
461
-
462
- queue.messages.push(message);
463
- queue.messageCount++;
464
- this.persistQueue(queueName);
465
-
466
- logger.verboso(`📨 Mensagem enviada para ${queueName}: ${messageId}`);
467
-
468
- if (queue.handler) {
469
- this.processQueueMessages(queue);
470
- }
471
-
472
- return { messageId, md5 };
473
- }
474
-
475
- sendMessageBatchAction(req) {
476
- const queueName = req.query.QueueName || req.body.QueueName;
477
- const queue = this.queues.get(queueName);
478
-
479
- if (!queue) {
480
- return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
481
- }
482
-
483
- const entries = req.query.SendMessageBatchRequestEntry || req.body.SendMessageBatchRequestEntry;
484
- const batchEntries = Array.isArray(entries) ? entries : [entries];
485
-
486
- const successful = [];
487
- const failed = [];
488
-
489
- for (const entry of batchEntries) {
490
- try {
491
- const messageId = crypto.randomUUID();
492
- const receiptHandle = crypto.randomUUID();
493
- const md5 = crypto.createHash('md5').update(entry.MessageBody).digest('hex');
494
-
495
- const message = {
496
- MessageId: messageId,
497
- ReceiptHandle: receiptHandle,
498
- Body: entry.MessageBody,
499
- MD5OfBody: md5,
500
- Attributes: {},
501
- timestamp: Date.now()
502
- };
503
-
504
- queue.messages.push(message);
505
- queue.messageCount++;
506
-
507
- successful.push({
508
- Id: entry.Id,
509
- MessageId: messageId,
510
- MD5OfMessageBody: md5
511
- });
512
- } catch (error) {
513
- failed.push({
514
- Id: entry.Id,
515
- Code: 'InternalError',
516
- Message: error.message
517
- });
518
- }
519
- }
520
-
521
- this.persistQueue(queueName);
522
-
523
- if (queue.handler && successful.length > 0) {
524
- this.processQueueMessages(queue);
525
- }
526
-
527
- return { successful, failed };
528
- }
529
-
530
- receiveMessageAction(req) {
531
- const queueName = req.query.QueueName || req.body.QueueName;
532
- const queue = this.queues.get(queueName);
533
-
534
- if (!queue) {
535
- return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
536
- }
537
-
538
- const maxNumberOfMessages = parseInt(req.query.MaxNumberOfMessages || req.body.MaxNumberOfMessages || 1);
539
- const messages = queue.messages.splice(0, maxNumberOfMessages);
540
-
541
- if (messages.length > 0) {
542
- queue.messageCount -= messages.length;
543
- this.persistQueue(queueName);
544
- }
545
-
546
- return { messages };
547
- }
548
-
549
- deleteMessageAction(req) {
550
- const queueName = req.query.QueueName || req.body.QueueName;
551
- const receiptHandle = req.query.ReceiptHandle || req.body.ReceiptHandle;
552
- const queue = this.queues.get(queueName);
553
-
554
- if (!queue) {
555
- return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
556
- }
557
-
558
- const index = queue.messages.findIndex(m => m.ReceiptHandle === receiptHandle);
559
- if (index !== -1) {
560
- queue.messages.splice(index, 1);
561
- queue.messageCount--;
562
- this.persistQueue(queueName);
563
- }
564
-
565
- return { success: true };
566
- }
567
-
568
- getQueueUrlAction(req) {
569
- const queueName = req.query.QueueName || req.body.QueueName;
570
- const queue = this.queues.get(queueName);
571
-
572
- if (!queue) {
573
- return { error: { code: 'QueueDoesNotExist', message: 'Queue does not exist' }, status: 400 };
574
- }
575
-
576
- return { queueUrl: queue.url };
577
- }
578
-
579
- listQueuesAction(req) {
580
- const queues = Array.from(this.queues.values()).map(q => ({
581
- name: q.name,
582
- url: q.url,
583
- messageCount: q.messageCount
584
- }));
585
-
586
- return { queues };
587
- }
588
-
589
370
  listQueues() {
590
371
  return Array.from(this.queues.values()).map(q => ({
591
372
  name: q.name,
@@ -598,7 +379,7 @@ class SQSSimulator {
598
379
  getQueue(queueName) {
599
380
  const queue = this.queues.get(queueName);
600
381
  if (!queue) return null;
601
-
382
+
602
383
  return {
603
384
  name: queue.name,
604
385
  url: queue.url,
@@ -657,4 +438,4 @@ class SQSSimulator {
657
438
  }
658
439
  }
659
440
 
660
- module.exports = SQSSimulator;
441
+ module.exports = SQSSimulator;
@@ -0,0 +1,37 @@
1
+ const STSServer = require('./server');
2
+ const logger = require('../../utils/logger');
3
+
4
+ class STSService {
5
+ constructor(config) {
6
+ this.config = config;
7
+ this.name = 'sts';
8
+ this.port = config.ports.sts || 9326;
9
+ this.server = null;
10
+ this.isRunning = false;
11
+ }
12
+
13
+ async initialize() {
14
+ this.server = new STSServer(this.port, this.config);
15
+ await this.server.initialize();
16
+ }
17
+
18
+ async start() {
19
+ if (this.isRunning) return;
20
+ await this.server.start();
21
+ this.isRunning = true;
22
+ }
23
+
24
+ async stop() {
25
+ if (!this.isRunning) return;
26
+ await this.server.stop();
27
+ this.isRunning = false;
28
+ }
29
+
30
+ async reset() {}
31
+
32
+ getStatus() {
33
+ return { running: this.isRunning, port: this.port, endpoint: `http://localhost:${this.port}` };
34
+ }
35
+ }
36
+
37
+ module.exports = STSService;