@gugananuvem/aws-local-simulator 1.0.0 → 1.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.
Files changed (38) hide show
  1. package/README.md +192 -192
  2. package/bin/aws-local-simulator.js +62 -62
  3. package/package.json +7 -4
  4. package/src/config/config-loader.js +113 -0
  5. package/src/config/default-config.js +65 -0
  6. package/src/config/env-loader.js +67 -0
  7. package/src/index.js +130 -130
  8. package/src/index.mjs +124 -0
  9. package/src/server.js +219 -0
  10. package/src/services/apigateway/index.js +67 -0
  11. package/src/services/apigateway/server.js +435 -0
  12. package/src/services/apigateway/simulator.js +1252 -0
  13. package/src/services/cognito/index.js +66 -0
  14. package/src/services/cognito/server.js +229 -0
  15. package/src/services/cognito/simulator.js +848 -0
  16. package/src/services/dynamodb/index.js +71 -0
  17. package/src/services/dynamodb/server.js +122 -0
  18. package/src/services/dynamodb/simulator.js +614 -0
  19. package/src/services/eventbridge/index.js +85 -0
  20. package/src/services/index.js +19 -0
  21. package/src/services/lambda/handler-loader.js +173 -0
  22. package/src/services/lambda/index.js +73 -0
  23. package/src/services/lambda/route-registry.js +275 -0
  24. package/src/services/lambda/server.js +153 -0
  25. package/src/services/lambda/simulator.js +278 -0
  26. package/src/services/s3/index.js +70 -0
  27. package/src/services/s3/server.js +239 -0
  28. package/src/services/s3/simulator.js +740 -0
  29. package/src/services/sns/index.js +76 -0
  30. package/src/services/sqs/index.js +96 -0
  31. package/src/services/sqs/server.js +274 -0
  32. package/src/services/sqs/simulator.js +660 -0
  33. package/src/template/aws-config-template.js +88 -0
  34. package/src/template/aws-config-template.mjs +91 -0
  35. package/src/template/config-template.json +165 -0
  36. package/src/utils/aws-config.js +92 -0
  37. package/src/utils/local-store.js +68 -0
  38. package/src/utils/logger.js +60 -0
@@ -0,0 +1,740 @@
1
+ /**
2
+ * S3 Simulator Core
3
+ */
4
+
5
+ const crypto = require("crypto");
6
+ const LocalStore = require("../../utils/local-store");
7
+ const logger = require("../../utils/logger");
8
+ const path = require("path");
9
+
10
+ class S3Simulator {
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, "s3");
14
+ this.store = new LocalStore(this.dataDir);
15
+ this.buckets = new Map();
16
+ }
17
+
18
+ async initialize() {
19
+ logger.debug("Inicializando S3 Simulator...");
20
+ this.loadBuckets();
21
+ logger.debug(`✅ S3 Simulator inicializado com ${this.buckets.size} buckets`);
22
+ }
23
+
24
+ loadBuckets() {
25
+ // Carrega buckets da configuração
26
+ if (this.config.s3?.buckets) {
27
+ for (const bucketName of this.config.s3.buckets) {
28
+ this.createBucket(bucketName);
29
+ }
30
+ }
31
+
32
+ // Carrega buckets existentes do disco
33
+ const savedBuckets = this.store.read("__buckets__");
34
+ if (savedBuckets) {
35
+ for (const [name, data] of Object.entries(savedBuckets)) {
36
+ if (!this.buckets.has(name)) {
37
+ this.buckets.set(name, {
38
+ name,
39
+ creationDate: new Date(data.creationDate),
40
+ objects: new Map(Object.entries(data.objects || {})),
41
+ objectCount: data.objectCount || 0,
42
+ totalSize: data.totalSize || 0,
43
+ });
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ createBucket(bucketName) {
50
+ if (!this.isValidBucketName(bucketName)) {
51
+ return { error: { code: "InvalidBucketName", message: "Bucket name is invalid" }, status: 400 };
52
+ }
53
+
54
+ if (this.buckets.has(bucketName)) {
55
+ return { error: { code: "BucketAlreadyExists", message: "Bucket already exists" }, status: 409 };
56
+ }
57
+
58
+ const bucket = {
59
+ name: bucketName,
60
+ creationDate: new Date(),
61
+ objects: new Map(),
62
+ objectCount: 0,
63
+ totalSize: 0,
64
+ };
65
+
66
+ this.buckets.set(bucketName, bucket);
67
+ this.persistBuckets();
68
+
69
+ logger.debug(`✅ Bucket S3 criado: ${bucketName}`);
70
+
71
+ return { bucket };
72
+ }
73
+
74
+ deleteBucket(bucketName) {
75
+ const bucket = this.buckets.get(bucketName);
76
+
77
+ if (!bucket) {
78
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
79
+ }
80
+
81
+ if (bucket.objects.size > 0) {
82
+ return { error: { code: "BucketNotEmpty", message: "Bucket is not empty" }, status: 409 };
83
+ }
84
+
85
+ this.buckets.delete(bucketName);
86
+ this.store.delete(bucketName);
87
+ this.persistBuckets();
88
+
89
+ logger.debug(`🗑️ Bucket S3 deletado: ${bucketName}`);
90
+
91
+ return { success: true };
92
+ }
93
+
94
+ putObject(bucketName, key, content, headers) {
95
+ const bucket = this.buckets.get(bucketName);
96
+
97
+ if (!bucket) {
98
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
99
+ }
100
+
101
+ // Normaliza o conteúdo
102
+ let body = content;
103
+ if (Buffer.isBuffer(content)) {
104
+ body = content;
105
+ } else if (typeof content === "object") {
106
+ body = Buffer.from(JSON.stringify(content));
107
+ } else if (typeof content === "string") {
108
+ body = Buffer.from(content);
109
+ }
110
+
111
+ const contentType = headers["content-type"] || "application/octet-stream";
112
+ const metadata = this.extractMetadata(headers);
113
+ const etag = crypto.createHash("md5").update(body).digest("hex");
114
+
115
+ const object = {
116
+ key,
117
+ size: body.length,
118
+ etag,
119
+ contentType,
120
+ metadata,
121
+ lastModified: new Date(),
122
+ content: body,
123
+ };
124
+
125
+ // Atualiza ou adiciona objeto
126
+ const oldObject = bucket.objects.get(key);
127
+ if (oldObject) {
128
+ bucket.totalSize -= oldObject.size;
129
+ } else {
130
+ bucket.objectCount++;
131
+ }
132
+
133
+ bucket.objects.set(key, object);
134
+ bucket.totalSize += body.length;
135
+
136
+ this.persistBucket(bucketName);
137
+
138
+ logger.verboso(`📤 Upload S3: ${bucketName}/${key} (${body.length} bytes)`);
139
+
140
+ return { etag };
141
+ }
142
+
143
+ getObject(bucketName, key, headers) {
144
+ const bucket = this.buckets.get(bucketName);
145
+
146
+ if (!bucket) {
147
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
148
+ }
149
+
150
+ const object = bucket.objects.get(key);
151
+ if (!object) {
152
+ return { error: { code: "NoSuchKey", message: "The specified key does not exist" }, status: 404 };
153
+ }
154
+
155
+ let content = object.content;
156
+ let start = 0;
157
+ let end = content.length - 1;
158
+
159
+ // Suporte a Range headers
160
+ if (headers.range) {
161
+ const range = headers.range.match(/bytes=(\d+)-(\d+)?/);
162
+ if (range) {
163
+ start = parseInt(range[1], 10);
164
+ end = range[2] ? parseInt(range[2], 10) : content.length - 1;
165
+ content = content.slice(start, end + 1);
166
+ }
167
+ }
168
+
169
+ return {
170
+ content,
171
+ etag: object.etag,
172
+ lastModified: object.lastModified.toUTCString(),
173
+ contentType: object.contentType,
174
+ size: content.length,
175
+ metadata: object.metadata,
176
+ };
177
+ }
178
+
179
+ deleteObject(bucketName, key) {
180
+ const bucket = this.buckets.get(bucketName);
181
+
182
+ if (!bucket) {
183
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
184
+ }
185
+
186
+ const object = bucket.objects.get(key);
187
+ if (object) {
188
+ bucket.objects.delete(key);
189
+ bucket.objectCount--;
190
+ bucket.totalSize -= object.size;
191
+ this.persistBucket(bucketName);
192
+ logger.verboso(`🗑️ Delete S3: ${bucketName}/${key}`);
193
+ }
194
+
195
+ return { success: true };
196
+ }
197
+
198
+ listObjects(bucketName, options = {}) {
199
+ const bucket = this.buckets.get(bucketName);
200
+
201
+ if (!bucket) {
202
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
203
+ }
204
+
205
+ const { prefix = "", delimiter, maxKeys = 1000 } = options;
206
+
207
+ let objects = Array.from(bucket.objects.values())
208
+ .filter((obj) => obj.key.startsWith(prefix))
209
+ .sort((a, b) => a.key.localeCompare(b.key));
210
+
211
+ const commonPrefixes = new Set();
212
+
213
+ if (delimiter) {
214
+ const filteredObjects = [];
215
+ for (const obj of objects) {
216
+ const afterPrefix = obj.key.substring(prefix.length);
217
+ const delimiterIndex = afterPrefix.indexOf(delimiter);
218
+
219
+ if (delimiterIndex !== -1) {
220
+ const prefixPath = prefix + afterPrefix.substring(0, delimiterIndex + 1);
221
+ commonPrefixes.add(prefixPath);
222
+ } else {
223
+ filteredObjects.push(obj);
224
+ }
225
+ }
226
+ objects = filteredObjects;
227
+ }
228
+
229
+ const contents = objects.slice(0, maxKeys).map((obj) => ({
230
+ Key: obj.key,
231
+ LastModified: obj.lastModified.toISOString(),
232
+ ETag: `"${obj.etag}"`,
233
+ Size: obj.size,
234
+ StorageClass: "STANDARD",
235
+ }));
236
+
237
+ return {
238
+ name: bucketName,
239
+ prefix,
240
+ maxKeys,
241
+ isTruncated: objects.length > maxKeys,
242
+ contents,
243
+ commonPrefixes: Array.from(commonPrefixes),
244
+ };
245
+ }
246
+
247
+ listBuckets() {
248
+ return Array.from(this.buckets.values()).map((bucket) => ({
249
+ Name: bucket.name,
250
+ CreationDate: bucket.creationDate.toISOString(),
251
+ }));
252
+ }
253
+
254
+ getBucketsInfo() {
255
+ return Array.from(this.buckets.values()).map((bucket) => ({
256
+ name: bucket.name,
257
+ creationDate: bucket.creationDate,
258
+ objectCount: bucket.objectCount,
259
+ totalSize: bucket.totalSize,
260
+ }));
261
+ }
262
+
263
+ getBucketInfo(bucketName) {
264
+ const bucket = this.buckets.get(bucketName);
265
+ if (!bucket) {
266
+ return { error: { code: "NoSuchBucket", message: "Bucket not found" } };
267
+ }
268
+
269
+ return {
270
+ name: bucket.name,
271
+ creationDate: bucket.creationDate,
272
+ objectCount: bucket.objectCount,
273
+ totalSize: bucket.totalSize,
274
+ objects: Array.from(bucket.objects.values())
275
+ .slice(0, 20)
276
+ .map((obj) => ({
277
+ key: obj.key,
278
+ size: obj.size,
279
+ etag: obj.etag,
280
+ lastModified: obj.lastModified,
281
+ })),
282
+ };
283
+ }
284
+
285
+ clearBucket(bucketName) {
286
+ const bucket = this.buckets.get(bucketName);
287
+ if (bucket) {
288
+ bucket.objects.clear();
289
+ bucket.objectCount = 0;
290
+ bucket.totalSize = 0;
291
+ this.persistBucket(bucketName);
292
+ }
293
+ }
294
+
295
+ getBucketsCount() {
296
+ return this.buckets.size;
297
+ }
298
+
299
+ isValidBucketName(bucketName) {
300
+ const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
301
+ return regex.test(bucketName) && !bucketName.includes("..") && !bucketName.includes(".-") && !bucketName.includes("-.");
302
+ }
303
+
304
+ extractMetadata(headers) {
305
+ const metadata = {};
306
+ for (const [key, value] of Object.entries(headers)) {
307
+ if (key.startsWith("x-amz-meta-")) {
308
+ const metaKey = key.replace("x-amz-meta-", "");
309
+ metadata[metaKey] = value;
310
+ }
311
+ }
312
+ return metadata;
313
+ }
314
+ persistBuckets() {
315
+ const bucketsObj = {};
316
+ for (const [name, bucket] of this.buckets.entries()) {
317
+ const objectsObj = {};
318
+ for (const [key, obj] of bucket.objects.entries()) {
319
+ objectsObj[key] = {
320
+ key: obj.key,
321
+ size: obj.size,
322
+ etag: obj.etag,
323
+ contentType: obj.contentType,
324
+ metadata: obj.metadata,
325
+ lastModified: obj.lastModified,
326
+ content: obj.content,
327
+ };
328
+ }
329
+
330
+ bucketsObj[name] = {
331
+ creationDate: bucket.creationDate.toISOString(),
332
+ objects: objectsObj,
333
+ objectCount: bucket.objectCount,
334
+ totalSize: bucket.totalSize,
335
+ };
336
+ }
337
+ this.store.write("__buckets__", bucketsObj);
338
+ }
339
+
340
+ isValidBucketName(bucketName) {
341
+ const regex = /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/;
342
+ return regex.test(bucketName) && !bucketName.includes("..") && !bucketName.includes(".-") && !bucketName.includes("-.");
343
+ }
344
+
345
+ async reset() {
346
+ for (const [name] of this.buckets) {
347
+ const bucket = this.buckets.get(name);
348
+ if (bucket) {
349
+ bucket.objects.clear();
350
+ bucket.objectCount = 0;
351
+ bucket.totalSize = 0;
352
+ this.store.write(name, {});
353
+ }
354
+ }
355
+ this.persistBuckets();
356
+ logger.debug("S3: Todos os dados resetados");
357
+ }
358
+
359
+ getBucketsCount() {
360
+ return this.buckets.size;
361
+ }
362
+
363
+ getTotalObjectsCount() {
364
+ let total = 0;
365
+ for (const bucket of this.buckets.values()) {
366
+ total += bucket.objectCount;
367
+ }
368
+ return total;
369
+ }
370
+
371
+ getBucket(bucketName) {
372
+ return this.buckets.get(bucketName);
373
+ }
374
+
375
+ listBuckets() {
376
+ return Array.from(this.buckets.values()).map((bucket) => ({
377
+ Name: bucket.name,
378
+ CreationDate: bucket.creationDate.toISOString(),
379
+ }));
380
+ }
381
+
382
+ getBucketsInfo() {
383
+ return Array.from(this.buckets.values()).map((bucket) => ({
384
+ name: bucket.name,
385
+ creationDate: bucket.creationDate,
386
+ objectCount: bucket.objectCount,
387
+ totalSize: bucket.totalSize,
388
+ }));
389
+ }
390
+
391
+ getBucketInfo(bucketName) {
392
+ const bucket = this.buckets.get(bucketName);
393
+ if (!bucket) {
394
+ return { error: { code: "NoSuchBucket", message: "Bucket not found" } };
395
+ }
396
+
397
+ return {
398
+ name: bucket.name,
399
+ creationDate: bucket.creationDate,
400
+ objectCount: bucket.objectCount,
401
+ totalSize: bucket.totalSize,
402
+ objects: Array.from(bucket.objects.values())
403
+ .slice(0, 20)
404
+ .map((obj) => ({
405
+ key: obj.key,
406
+ size: obj.size,
407
+ etag: obj.etag,
408
+ lastModified: obj.lastModified,
409
+ })),
410
+ };
411
+ }
412
+
413
+ listAllObjects(bucketName) {
414
+ const bucket = this.buckets.get(bucketName);
415
+ if (!bucket) return [];
416
+ return Array.from(bucket.objects.values()).map((obj) => ({
417
+ key: obj.key,
418
+ size: obj.size,
419
+ etag: obj.etag,
420
+ lastModified: obj.lastModified,
421
+ }));
422
+ }
423
+
424
+ headObject(bucketName, key) {
425
+ const bucket = this.buckets.get(bucketName);
426
+ if (!bucket) {
427
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
428
+ }
429
+
430
+ const object = bucket.objects.get(key);
431
+ if (!object) {
432
+ return { error: { code: "NoSuchKey", message: "Key does not exist" }, status: 404 };
433
+ }
434
+
435
+ return {
436
+ etag: object.etag,
437
+ lastModified: object.lastModified.toUTCString(),
438
+ contentType: object.contentType,
439
+ size: object.size,
440
+ };
441
+ }
442
+
443
+ putObject(bucketName, key, content, headers) {
444
+ const bucket = this.buckets.get(bucketName);
445
+
446
+ if (!bucket) {
447
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
448
+ }
449
+
450
+ let body = content;
451
+ if (Buffer.isBuffer(content)) {
452
+ body = content;
453
+ } else if (typeof content === "object") {
454
+ body = Buffer.from(JSON.stringify(content));
455
+ } else if (typeof content === "string") {
456
+ body = Buffer.from(content);
457
+ }
458
+
459
+ const contentType = headers["content-type"] || "application/octet-stream";
460
+ const metadata = this.extractMetadata(headers);
461
+ const etag = crypto.createHash("md5").update(body).digest("hex");
462
+
463
+ const object = {
464
+ key,
465
+ size: body.length,
466
+ etag,
467
+ contentType,
468
+ metadata,
469
+ lastModified: new Date(),
470
+ content: body,
471
+ };
472
+
473
+ const oldObject = bucket.objects.get(key);
474
+ if (oldObject) {
475
+ bucket.totalSize -= oldObject.size;
476
+ } else {
477
+ bucket.objectCount++;
478
+ }
479
+
480
+ bucket.objects.set(key, object);
481
+ bucket.totalSize += body.length;
482
+
483
+ this.persistBucket(bucketName);
484
+
485
+ logger.verboso(`📤 Upload S3: ${bucketName}/${key} (${body.length} bytes)`);
486
+
487
+ return { etag };
488
+ }
489
+
490
+ getObject(bucketName, key, headers) {
491
+ const bucket = this.buckets.get(bucketName);
492
+
493
+ if (!bucket) {
494
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
495
+ }
496
+
497
+ const object = bucket.objects.get(key);
498
+ if (!object) {
499
+ return { error: { code: "NoSuchKey", message: "The specified key does not exist" }, status: 404 };
500
+ }
501
+
502
+ let content = object.content;
503
+ let start = 0;
504
+ let end = content.length - 1;
505
+
506
+ if (headers.range) {
507
+ const range = headers.range.match(/bytes=(\d+)-(\d+)?/);
508
+ if (range) {
509
+ start = parseInt(range[1], 10);
510
+ end = range[2] ? parseInt(range[2], 10) : content.length - 1;
511
+ content = content.slice(start, end + 1);
512
+ }
513
+ }
514
+
515
+ return {
516
+ content,
517
+ etag: object.etag,
518
+ lastModified: object.lastModified.toUTCString(),
519
+ contentType: object.contentType,
520
+ size: content.length,
521
+ metadata: object.metadata,
522
+ };
523
+ }
524
+
525
+ deleteObject(bucketName, key) {
526
+ const bucket = this.buckets.get(bucketName);
527
+
528
+ if (!bucket) {
529
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
530
+ }
531
+
532
+ const object = bucket.objects.get(key);
533
+ if (object) {
534
+ bucket.objects.delete(key);
535
+ bucket.objectCount--;
536
+ bucket.totalSize -= object.size;
537
+ this.persistBucket(bucketName);
538
+ logger.verboso(`🗑️ Delete S3: ${bucketName}/${key}`);
539
+ }
540
+
541
+ return { success: true };
542
+ }
543
+
544
+ listObjects(bucketName, options = {}) {
545
+ const bucket = this.buckets.get(bucketName);
546
+
547
+ if (!bucket) {
548
+ return { error: { code: "NoSuchBucket", message: "Bucket does not exist" }, status: 404 };
549
+ }
550
+
551
+ const { prefix = "", delimiter, maxKeys = 1000 } = options;
552
+
553
+ let objects = Array.from(bucket.objects.values())
554
+ .filter((obj) => obj.key.startsWith(prefix))
555
+ .sort((a, b) => a.key.localeCompare(b.key));
556
+
557
+ const commonPrefixes = new Set();
558
+
559
+ if (delimiter) {
560
+ const filteredObjects = [];
561
+ for (const obj of objects) {
562
+ const afterPrefix = obj.key.substring(prefix.length);
563
+ const delimiterIndex = afterPrefix.indexOf(delimiter);
564
+
565
+ if (delimiterIndex !== -1) {
566
+ const prefixPath = prefix + afterPrefix.substring(0, delimiterIndex + 1);
567
+ commonPrefixes.add(prefixPath);
568
+ } else {
569
+ filteredObjects.push(obj);
570
+ }
571
+ }
572
+ objects = filteredObjects;
573
+ }
574
+
575
+ const contents = objects.slice(0, maxKeys).map((obj) => ({
576
+ Key: obj.key,
577
+ LastModified: obj.lastModified.toISOString(),
578
+ ETag: `"${obj.etag}"`,
579
+ Size: obj.size,
580
+ StorageClass: "STANDARD",
581
+ }));
582
+
583
+ return {
584
+ name: bucketName,
585
+ prefix,
586
+ maxKeys,
587
+ isTruncated: objects.length > maxKeys,
588
+ contents,
589
+ commonPrefixes: Array.from(commonPrefixes),
590
+ };
591
+ }
592
+
593
+ extractMetadata(headers) {
594
+ const metadata = {};
595
+ for (const [key, value] of Object.entries(headers)) {
596
+ if (key.startsWith("x-amz-meta-")) {
597
+ const metaKey = key.replace("x-amz-meta-", "");
598
+ metadata[metaKey] = value;
599
+ }
600
+ }
601
+ return metadata;
602
+ }
603
+
604
+ persistBucket(bucketName) {
605
+ const bucket = this.buckets.get(bucketName);
606
+ if (bucket) {
607
+ const objectsObj = {};
608
+ for (const [key, obj] of bucket.objects.entries()) {
609
+ objectsObj[key] = {
610
+ key: obj.key,
611
+ size: obj.size,
612
+ etag: obj.etag,
613
+ contentType: obj.contentType,
614
+ metadata: obj.metadata,
615
+ lastModified: obj.lastModified,
616
+ content: obj.content,
617
+ };
618
+ }
619
+ this.store.write(bucketName, objectsObj);
620
+ this.persistBuckets();
621
+ }
622
+ }
623
+
624
+ clearBucket(bucketName) {
625
+ const bucket = this.buckets.get(bucketName);
626
+ if (bucket) {
627
+ bucket.objects.clear();
628
+ bucket.objectCount = 0;
629
+ bucket.totalSize = 0;
630
+ this.persistBucket(bucketName);
631
+ }
632
+ }
633
+
634
+ getStats() {
635
+ return {
636
+ bucketsCount: this.buckets.size,
637
+ totalObjects: this.getTotalObjectsCount(),
638
+ buckets: Array.from(this.buckets.keys()),
639
+ };
640
+ }
641
+
642
+ generateListBucketsResponse(buckets) {
643
+ return `<?xml version="1.0" encoding="UTF-8"?>
644
+ <ListAllMyBucketsResult>
645
+ <Owner>
646
+ <ID>local-simulator</ID>
647
+ <DisplayName>local-simulator</DisplayName>
648
+ </Owner>
649
+ <Buckets>
650
+ ${buckets
651
+ .map(
652
+ (bucket) => `
653
+ <Bucket>
654
+ <Name>${bucket.Name}</Name>
655
+ <CreationDate>${bucket.CreationDate}</CreationDate>
656
+ </Bucket>
657
+ `,
658
+ )
659
+ .join("")}
660
+ </Buckets>
661
+ </ListAllMyBucketsResult>`;
662
+ }
663
+
664
+ generateListObjectsResponse(data) {
665
+ return `<?xml version="1.0" encoding="UTF-8"?>
666
+ <ListBucketResult>
667
+ <Name>${data.name}</Name>
668
+ <Prefix>${data.prefix}</Prefix>
669
+ <MaxKeys>${data.maxKeys}</MaxKeys>
670
+ <IsTruncated>${data.isTruncated}</IsTruncated>
671
+ ${data.contents
672
+ .map(
673
+ (obj) => `
674
+ <Contents>
675
+ <Key>${obj.Key}</Key>
676
+ <LastModified>${obj.LastModified}</LastModified>
677
+ <ETag>${obj.ETag}</ETag>
678
+ <Size>${obj.Size}</Size>
679
+ <StorageClass>${obj.StorageClass}</StorageClass>
680
+ </Contents>
681
+ `,
682
+ )
683
+ .join("")}
684
+ ${data.commonPrefixes
685
+ .map(
686
+ (prefix) => `
687
+ <CommonPrefixes>
688
+ <Prefix>${prefix}</Prefix>
689
+ </CommonPrefixes>
690
+ `,
691
+ )
692
+ .join("")}
693
+ </ListBucketResult>`;
694
+ }
695
+
696
+ generateListObjectsV2Response(data) {
697
+ return `<?xml version="1.0" encoding="UTF-8"?>
698
+ <ListBucketResult>
699
+ <Name>${data.name}</Name>
700
+ <Prefix>${data.prefix}</Prefix>
701
+ <MaxKeys>${data.maxKeys}</MaxKeys>
702
+ <IsTruncated>${data.isTruncated}</IsTruncated>
703
+ <KeyCount>${data.contents.length}</KeyCount>
704
+ ${data.contents
705
+ .map(
706
+ (obj) => `
707
+ <Contents>
708
+ <Key>${obj.Key}</Key>
709
+ <LastModified>${obj.LastModified}</LastModified>
710
+ <ETag>${obj.ETag}</ETag>
711
+ <Size>${obj.Size}</Size>
712
+ <StorageClass>${obj.StorageClass}</StorageClass>
713
+ </Contents>
714
+ `,
715
+ )
716
+ .join("")}
717
+ ${data.commonPrefixes
718
+ .map(
719
+ (prefix) => `
720
+ <CommonPrefixes>
721
+ <Prefix>${prefix}</Prefix>
722
+ </CommonPrefixes>
723
+ `,
724
+ )
725
+ .join("")}
726
+ </ListBucketResult>`;
727
+ }
728
+
729
+ generateErrorResponse(code, message) {
730
+ return `<?xml version="1.0" encoding="UTF-8"?>
731
+ <Error>
732
+ <Code>${code}</Code>
733
+ <Message>${message}</Message>
734
+ <RequestId>${crypto.randomUUID()}</RequestId>
735
+ <HostId>local-simulator</HostId>
736
+ </Error>`;
737
+ }
738
+ }
739
+
740
+ module.exports = S3Simulator;