@gugananuvem/aws-local-simulator 1.0.15 → 1.0.16

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 (77) hide show
  1. package/README.md +789 -594
  2. package/bin/aws-local-simulator.js +63 -63
  3. package/package.json +2 -2
  4. package/src/config/config-loader.js +114 -114
  5. package/src/config/default-config.js +68 -68
  6. package/src/config/env-loader.js +68 -68
  7. package/src/index.js +146 -146
  8. package/src/index.mjs +123 -123
  9. package/src/server.js +227 -227
  10. package/src/services/apigateway/index.js +75 -73
  11. package/src/services/apigateway/server.js +570 -507
  12. package/src/services/apigateway/simulator.js +1261 -1261
  13. package/src/services/athena/index.js +75 -75
  14. package/src/services/athena/server.js +101 -101
  15. package/src/services/athena/simulador.js +998 -998
  16. package/src/services/athena/simulator.js +346 -346
  17. package/src/services/cloudformation/index.js +106 -106
  18. package/src/services/cloudformation/server.js +417 -417
  19. package/src/services/cloudformation/simulador.js +1045 -1045
  20. package/src/services/cloudtrail/index.js +84 -84
  21. package/src/services/cloudtrail/server.js +235 -235
  22. package/src/services/cloudtrail/simulador.js +719 -719
  23. package/src/services/cloudwatch/index.js +84 -84
  24. package/src/services/cloudwatch/server.js +366 -366
  25. package/src/services/cloudwatch/simulador.js +1173 -1173
  26. package/src/services/cognito/index.js +79 -79
  27. package/src/services/cognito/server.js +301 -301
  28. package/src/services/cognito/simulator.js +1655 -1655
  29. package/src/services/config/index.js +96 -96
  30. package/src/services/config/server.js +215 -215
  31. package/src/services/config/simulador.js +1260 -1260
  32. package/src/services/dynamodb/index.js +74 -74
  33. package/src/services/dynamodb/server.js +125 -125
  34. package/src/services/dynamodb/simulator.js +630 -630
  35. package/src/services/ecs/index.js +65 -65
  36. package/src/services/ecs/server.js +235 -235
  37. package/src/services/ecs/simulator.js +844 -844
  38. package/src/services/eventbridge/index.js +89 -89
  39. package/src/services/eventbridge/server.js +209 -209
  40. package/src/services/eventbridge/simulator.js +684 -684
  41. package/src/services/index.js +45 -45
  42. package/src/services/kms/index.js +75 -75
  43. package/src/services/kms/server.js +67 -67
  44. package/src/services/kms/simulator.js +324 -324
  45. package/src/services/lambda/handler-loader.js +183 -183
  46. package/src/services/lambda/index.js +78 -78
  47. package/src/services/lambda/route-registry.js +274 -274
  48. package/src/services/lambda/server.js +145 -145
  49. package/src/services/lambda/simulator.js +199 -199
  50. package/src/services/parameter-store/index.js +80 -80
  51. package/src/services/parameter-store/server.js +50 -50
  52. package/src/services/parameter-store/simulator.js +201 -201
  53. package/src/services/s3/index.js +73 -73
  54. package/src/services/s3/server.js +329 -329
  55. package/src/services/s3/simulator.js +565 -565
  56. package/src/services/secret-manager/index.js +80 -80
  57. package/src/services/secret-manager/server.js +50 -50
  58. package/src/services/secret-manager/simulator.js +171 -171
  59. package/src/services/sns/index.js +89 -89
  60. package/src/services/sns/server.js +580 -580
  61. package/src/services/sns/simulator.js +1482 -1482
  62. package/src/services/sqs/index.js +98 -93
  63. package/src/services/sqs/server.js +349 -349
  64. package/src/services/sqs/simulator.js +441 -441
  65. package/src/services/sts/index.js +37 -37
  66. package/src/services/sts/server.js +144 -144
  67. package/src/services/sts/simulator.js +69 -69
  68. package/src/services/xray/index.js +83 -83
  69. package/src/services/xray/server.js +308 -308
  70. package/src/services/xray/simulador.js +994 -994
  71. package/src/template/aws-config-template.js +87 -87
  72. package/src/template/aws-config-template.mjs +90 -90
  73. package/src/template/config-template.json +203 -203
  74. package/src/utils/aws-config.js +91 -91
  75. package/src/utils/cloudtrail-audit.js +129 -129
  76. package/src/utils/local-store.js +83 -83
  77. package/src/utils/logger.js +59 -59
@@ -1,1262 +1,1262 @@
1
- /**
2
- * API Gateway Simulator Core
3
- * Simula REST APIs, HTTP APIs, WebSocket APIs, Routes, Integrations
4
- */
5
-
6
- const crypto = require('crypto');
7
- const { v4: uuidv4 } = require('uuid');
8
- const logger = require('../../utils/logger');
9
- const LocalStore = require('../../utils/local-store');
10
- const path = require('path');
11
- const { URLPattern } = require('urlpattern-polyfill');
12
- const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
13
-
14
- class APIGatewaySimulator {
15
- constructor(config) {
16
- this.config = config;
17
- this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'apigateway');
18
- this.store = new LocalStore(this.dataDir);
19
- this.apis = new Map();
20
- this.websocketApis = new Map();
21
- this.deployments = new Map();
22
- this.stages = new Map();
23
- this.resources = new Map();
24
- this.methods = new Map();
25
- this.integrations = new Map();
26
- this.models = new Map();
27
- this.usagePlans = new Map();
28
- this.apiKeys = new Map();
29
- this.domainNames = new Map();
30
- this.audit = new CloudTrailAudit('execute-api.amazonaws.com');
31
- }
32
-
33
- async initialize() {
34
- logger.debug('Inicializando API Gateway Simulator...');
35
- this.loadAPIs();
36
- this.loadWebSocketAPIs();
37
- this.loadDeployments();
38
- this.loadStages();
39
- this.loadResources();
40
- this.loadMethods();
41
- this.loadIntegrations();
42
- this.loadModels();
43
- this.loadUsagePlans();
44
- this.loadApiKeys();
45
- this.loadDomainNames();
46
-
47
- logger.debug(`✅ API Gateway Simulator inicializado com ${this.apis.size} APIs, ${this.resources.size} resources, ${this.methods.size} methods`);
48
- }
49
-
50
- // ============ REST API Operations ============
51
-
52
- createRestApi(params) {
53
- const { name, description, version, apiKeySource, endpointConfiguration, tags } = params;
54
-
55
- const apiId = `api_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
56
- const api = {
57
- id: apiId,
58
- name: name,
59
- description: description || '',
60
- version: version || '1.0',
61
- createdDate: new Date().toISOString(),
62
- apiKeySource: apiKeySource || 'HEADER',
63
- endpointConfiguration: endpointConfiguration || {
64
- types: ['REGIONAL']
65
- },
66
- tags: tags || {},
67
- resources: new Map(),
68
- stages: new Map(),
69
- deployments: new Map(),
70
- models: new Map(),
71
- authorizers: new Map(),
72
- gatewayResponses: new Map(),
73
- documentationParts: new Map()
74
- };
75
-
76
- // Cria recurso raiz
77
- const rootResource = {
78
- id: uuidv4(),
79
- path: '/',
80
- pathPart: '',
81
- parentId: null,
82
- resourceMethods: new Map()
83
- };
84
-
85
- api.resources.set('/', rootResource);
86
-
87
- this.apis.set(apiId, api);
88
- this.persistAPIs();
89
-
90
- logger.debug(`✅ REST API criada: ${name} (${apiId})`);
91
-
92
- return {
93
- id: apiId,
94
- name: api.name,
95
- createdDate: api.createdDate
96
- };
97
- }
98
-
99
- getRestApis() {
100
- return {
101
- items: Array.from(this.apis.values()).map(api => ({
102
- id: api.id,
103
- name: api.name,
104
- description: api.description,
105
- version: api.version,
106
- createdDate: api.createdDate,
107
- apiKeySource: api.apiKeySource
108
- }))
109
- };
110
- }
111
-
112
- getRestApi(params) {
113
- const { restApiId } = params;
114
- const api = this.apis.get(restApiId);
115
-
116
- if (!api) {
117
- throw new Error(`API ${restApiId} not found`);
118
- }
119
-
120
- return {
121
- id: api.id,
122
- name: api.name,
123
- description: api.description,
124
- version: api.version,
125
- createdDate: api.createdDate,
126
- apiKeySource: api.apiKeySource,
127
- endpointConfiguration: api.endpointConfiguration,
128
- tags: api.tags
129
- };
130
- }
131
-
132
- deleteRestApi(params) {
133
- const { restApiId } = params;
134
-
135
- if (!this.apis.has(restApiId)) {
136
- throw new Error(`API ${restApiId} not found`);
137
- }
138
-
139
- this.apis.delete(restApiId);
140
- this.persistAPIs();
141
-
142
- return {};
143
- }
144
-
145
- // ============ Resource Operations ============
146
-
147
- createResource(params) {
148
- const { restApiId, parentId, pathPart } = params;
149
- const api = this.apis.get(restApiId);
150
-
151
- if (!api) {
152
- throw new Error(`API ${restApiId} not found`);
153
- }
154
-
155
- const parentResource = api.resources.get(parentId);
156
- if (!parentResource) {
157
- throw new Error(`Parent resource ${parentId} not found`);
158
- }
159
-
160
- const resourceId = uuidv4();
161
- const fullPath = parentResource.path === '/'
162
- ? `/${pathPart}`
163
- : `${parentResource.path}/${pathPart}`;
164
-
165
- const resource = {
166
- id: resourceId,
167
- path: fullPath,
168
- pathPart: pathPart,
169
- parentId: parentId,
170
- resourceMethods: new Map()
171
- };
172
-
173
- api.resources.set(resourceId, resource);
174
- this.persistResources(restApiId);
175
-
176
- logger.debug(`📁 Recurso criado: ${fullPath} (${resourceId})`);
177
-
178
- return {
179
- id: resourceId,
180
- path: resource.path,
181
- parentId: resource.parentId
182
- };
183
- }
184
-
185
- getResources(params) {
186
- const { restApiId } = params;
187
- const api = this.apis.get(restApiId);
188
-
189
- if (!api) {
190
- throw new Error(`API ${restApiId} not found`);
191
- }
192
-
193
- const items = Array.from(api.resources.values()).map(resource => ({
194
- id: resource.id,
195
- path: resource.path,
196
- pathPart: resource.pathPart,
197
- parentId: resource.parentId,
198
- resourceMethods: Object.fromEntries(resource.resourceMethods)
199
- }));
200
-
201
- return { items };
202
- }
203
-
204
- deleteResource(params) {
205
- const { restApiId, resourceId } = params;
206
- const api = this.apis.get(restApiId);
207
-
208
- if (!api) {
209
- throw new Error(`API ${restApiId} not found`);
210
- }
211
-
212
- if (!api.resources.has(resourceId)) {
213
- throw new Error(`Resource ${resourceId} not found`);
214
- }
215
-
216
- // Verifica se tem filhos
217
- const hasChildren = Array.from(api.resources.values()).some(
218
- r => r.parentId === resourceId
219
- );
220
-
221
- if (hasChildren) {
222
- throw new Error('Resource has children');
223
- }
224
-
225
- api.resources.delete(resourceId);
226
- this.persistResources(restApiId);
227
-
228
- return {};
229
- }
230
-
231
- // ============ Method Operations ============
232
-
233
- putMethod(params) {
234
- const { restApiId, resourceId, httpMethod, authorizationType, apiKeyRequired, requestParameters, requestModels, authorizerId } = params;
235
- const api = this.apis.get(restApiId);
236
-
237
- if (!api) {
238
- throw new Error(`API ${restApiId} not found`);
239
- }
240
-
241
- const resource = api.resources.get(resourceId);
242
- if (!resource) {
243
- throw new Error(`Resource ${resourceId} not found`);
244
- }
245
-
246
- const method = {
247
- httpMethod: httpMethod,
248
- authorizationType: authorizationType || 'NONE',
249
- apiKeyRequired: apiKeyRequired || false,
250
- requestParameters: requestParameters || {},
251
- requestModels: requestModels || {},
252
- authorizerId: authorizerId || null,
253
- methodResponses: new Map(),
254
- integration: null
255
- };
256
-
257
- resource.resourceMethods.set(httpMethod, method);
258
- this.persistMethods(restApiId, resourceId);
259
-
260
- logger.debug(`🔧 Método criado: ${httpMethod} ${resource.path}`);
261
-
262
- return {
263
- httpMethod: method.httpMethod,
264
- authorizationType: method.authorizationType,
265
- apiKeyRequired: method.apiKeyRequired
266
- };
267
- }
268
-
269
- getMethod(params) {
270
- const { restApiId, resourceId, httpMethod } = params;
271
- const api = this.apis.get(restApiId);
272
-
273
- if (!api) {
274
- throw new Error(`API ${restApiId} not found`);
275
- }
276
-
277
- const resource = api.resources.get(resourceId);
278
- if (!resource) {
279
- throw new Error(`Resource ${resourceId} not found`);
280
- }
281
-
282
- const method = resource.resourceMethods.get(httpMethod);
283
- if (!method) {
284
- throw new Error(`Method ${httpMethod} not found`);
285
- }
286
-
287
- return {
288
- httpMethod: method.httpMethod,
289
- authorizationType: method.authorizationType,
290
- apiKeyRequired: method.apiKeyRequired,
291
- requestParameters: method.requestParameters,
292
- requestModels: method.requestModels
293
- };
294
- }
295
-
296
- deleteMethod(params) {
297
- const { restApiId, resourceId, httpMethod } = params;
298
- const api = this.apis.get(restApiId);
299
-
300
- if (!api) {
301
- throw new Error(`API ${restApiId} not found`);
302
- }
303
-
304
- const resource = api.resources.get(resourceId);
305
- if (!resource) {
306
- throw new Error(`Resource ${resourceId} not found`);
307
- }
308
-
309
- resource.resourceMethods.delete(httpMethod);
310
- this.persistMethods(restApiId, resourceId);
311
-
312
- return {};
313
- }
314
-
315
- // ============ Integration Operations ============
316
-
317
- putIntegration(params) {
318
- const { restApiId, resourceId, httpMethod, type, integrationHttpMethod, uri, credentials, requestParameters, requestTemplates, passthroughBehavior, timeoutInMillis, cacheNamespace, cacheKeyParameters, contentHandling } = params;
319
- const api = this.apis.get(restApiId);
320
-
321
- if (!api) {
322
- throw new Error(`API ${restApiId} not found`);
323
- }
324
-
325
- const resource = api.resources.get(resourceId);
326
- if (!resource) {
327
- throw new Error(`Resource ${resourceId} not found`);
328
- }
329
-
330
- const method = resource.resourceMethods.get(httpMethod);
331
- if (!method) {
332
- throw new Error(`Method ${httpMethod} not found`);
333
- }
334
-
335
- const integration = {
336
- type: type || 'HTTP',
337
- integrationHttpMethod: integrationHttpMethod,
338
- uri: uri,
339
- credentials: credentials || null,
340
- requestParameters: requestParameters || {},
341
- requestTemplates: requestTemplates || {},
342
- passthroughBehavior: passthroughBehavior || 'WHEN_NO_MATCH',
343
- timeoutInMillis: timeoutInMillis || 29000,
344
- cacheNamespace: cacheNamespace || '',
345
- cacheKeyParameters: cacheKeyParameters || [],
346
- contentHandling: contentHandling || null,
347
- integrationResponses: new Map()
348
- };
349
-
350
- method.integration = integration;
351
- this.persistIntegrations(restApiId, resourceId);
352
-
353
- logger.debug(`🔌 Integração criada: ${type} -> ${uri}`);
354
-
355
- return {
356
- type: integration.type,
357
- integrationHttpMethod: integration.integrationHttpMethod,
358
- uri: integration.uri
359
- };
360
- }
361
-
362
- getIntegration(params) {
363
- const { restApiId, resourceId, httpMethod } = params;
364
- const api = this.apis.get(restApiId);
365
-
366
- if (!api) {
367
- throw new Error(`API ${restApiId} not found`);
368
- }
369
-
370
- const resource = api.resources.get(resourceId);
371
- if (!resource) {
372
- throw new Error(`Resource ${resourceId} not found`);
373
- }
374
-
375
- const method = resource.resourceMethods.get(httpMethod);
376
- if (!method || !method.integration) {
377
- throw new Error(`Integration not found for ${httpMethod}`);
378
- }
379
-
380
- const integration = method.integration;
381
-
382
- return {
383
- type: integration.type,
384
- integrationHttpMethod: integration.integrationHttpMethod,
385
- uri: integration.uri,
386
- credentials: integration.credentials,
387
- requestParameters: integration.requestParameters,
388
- requestTemplates: integration.requestTemplates,
389
- passthroughBehavior: integration.passthroughBehavior,
390
- timeoutInMillis: integration.timeoutInMillis
391
- };
392
- }
393
-
394
- deleteIntegration(params) {
395
- const { restApiId, resourceId, httpMethod } = params;
396
- const api = this.apis.get(restApiId);
397
-
398
- if (!api) {
399
- throw new Error(`API ${restApiId} not found`);
400
- }
401
-
402
- const resource = api.resources.get(resourceId);
403
- if (!resource) {
404
- throw new Error(`Resource ${resourceId} not found`);
405
- }
406
-
407
- const method = resource.resourceMethods.get(httpMethod);
408
- if (method) {
409
- method.integration = null;
410
- this.persistIntegrations(restApiId, resourceId);
411
- }
412
-
413
- return {};
414
- }
415
-
416
- // ============ Integration Response Operations ============
417
-
418
- putIntegrationResponse(params) {
419
- const { restApiId, resourceId, httpMethod, statusCode, selectionPattern, responseParameters, responseTemplates, contentHandling } = params;
420
- const api = this.apis.get(restApiId);
421
-
422
- if (!api) {
423
- throw new Error(`API ${restApiId} not found`);
424
- }
425
-
426
- const resource = api.resources.get(resourceId);
427
- if (!resource) {
428
- throw new Error(`Resource ${resourceId} not found`);
429
- }
430
-
431
- const method = resource.resourceMethods.get(httpMethod);
432
- if (!method || !method.integration) {
433
- throw new Error(`Integration not found for ${httpMethod}`);
434
- }
435
-
436
- const integrationResponse = {
437
- statusCode: statusCode,
438
- selectionPattern: selectionPattern || '',
439
- responseParameters: responseParameters || {},
440
- responseTemplates: responseTemplates || {},
441
- contentHandling: contentHandling || null
442
- };
443
-
444
- method.integration.integrationResponses.set(statusCode, integrationResponse);
445
- this.persistIntegrations(restApiId, resourceId);
446
-
447
- return {
448
- statusCode: integrationResponse.statusCode,
449
- selectionPattern: integrationResponse.selectionPattern
450
- };
451
- }
452
-
453
- // ============ Deployment Operations ============
454
-
455
- createDeployment(params) {
456
- const { restApiId, stageName, stageDescription, description, variables } = params;
457
- const api = this.apis.get(restApiId);
458
-
459
- if (!api) {
460
- throw new Error(`API ${restApiId} not found`);
461
- }
462
-
463
- const deploymentId = uuidv4();
464
- const deployment = {
465
- id: deploymentId,
466
- description: description || '',
467
- createdDate: new Date().toISOString(),
468
- apiId: restApiId
469
- };
470
-
471
- api.deployments.set(deploymentId, deployment);
472
-
473
- if (stageName) {
474
- this.createStage({
475
- restApiId,
476
- stageName,
477
- description: stageDescription,
478
- variables
479
- });
480
- }
481
-
482
- this.persistDeployments(restApiId);
483
-
484
- logger.debug(`🚀 Deployment criado: ${deploymentId} para stage: ${stageName || 'N/A'}`);
485
-
486
- return {
487
- id: deploymentId,
488
- createdDate: deployment.createdDate
489
- };
490
- }
491
-
492
- // ============ Stage Operations ============
493
-
494
- createStage(params) {
495
- const { restApiId, stageName, description, variables, deploymentId, cacheClusterEnabled, cacheClusterSize, tracingEnabled } = params;
496
- const api = this.apis.get(restApiId);
497
-
498
- if (!api) {
499
- throw new Error(`API ${restApiId} not found`);
500
- }
501
-
502
- const stage = {
503
- stageName: stageName,
504
- description: description || '',
505
- createdDate: new Date().toISOString(),
506
- lastUpdatedDate: new Date().toISOString(),
507
- deploymentId: deploymentId,
508
- variables: variables || {},
509
- cacheClusterEnabled: cacheClusterEnabled || false,
510
- cacheClusterSize: cacheClusterSize || null,
511
- tracingEnabled: tracingEnabled || false,
512
- methodSettings: new Map()
513
- };
514
-
515
- api.stages.set(stageName, stage);
516
- this.persistStages(restApiId);
517
-
518
- // Cria endpoint URL
519
- const endpointUrl = `http://localhost:${this.config.ports.apigateway}/${restApiId}/${stageName}`;
520
-
521
- logger.debug(`📡 Stage criado: ${stageName} em ${endpointUrl}`);
522
-
523
- return {
524
- stageName: stage.stageName,
525
- createdDate: stage.createdDate,
526
- deploymentId: stage.deploymentId
527
- };
528
- }
529
-
530
- getStage(params) {
531
- const { restApiId, stageName } = params;
532
- const api = this.apis.get(restApiId);
533
-
534
- if (!api) {
535
- throw new Error(`API ${restApiId} not found`);
536
- }
537
-
538
- const stage = api.stages.get(stageName);
539
- if (!stage) {
540
- throw new Error(`Stage ${stageName} not found`);
541
- }
542
-
543
- return {
544
- stageName: stage.stageName,
545
- description: stage.description,
546
- createdDate: stage.createdDate,
547
- lastUpdatedDate: stage.lastUpdatedDate,
548
- deploymentId: stage.deploymentId,
549
- variables: stage.variables,
550
- methodSettings: Object.fromEntries(stage.methodSettings),
551
- cacheClusterEnabled: stage.cacheClusterEnabled,
552
- tracingEnabled: stage.tracingEnabled
553
- };
554
- }
555
-
556
- updateStage(params) {
557
- const { restApiId, stageName, description, variables, deploymentId, tracingEnabled } = params;
558
- const api = this.apis.get(restApiId);
559
-
560
- if (!api) {
561
- throw new Error(`API ${restApiId} not found`);
562
- }
563
-
564
- const stage = api.stages.get(stageName);
565
- if (!stage) {
566
- throw new Error(`Stage ${stageName} not found`);
567
- }
568
-
569
- if (description !== undefined) stage.description = description;
570
- if (variables !== undefined) stage.variables = { ...stage.variables, ...variables };
571
- if (deploymentId !== undefined) stage.deploymentId = deploymentId;
572
- if (tracingEnabled !== undefined) stage.tracingEnabled = tracingEnabled;
573
-
574
- stage.lastUpdatedDate = new Date().toISOString();
575
- this.persistStages(restApiId);
576
-
577
- return {
578
- stageName: stage.stageName,
579
- description: stage.description,
580
- lastUpdatedDate: stage.lastUpdatedDate
581
- };
582
- }
583
-
584
- deleteStage(params) {
585
- const { restApiId, stageName } = params;
586
- const api = this.apis.get(restApiId);
587
-
588
- if (!api) {
589
- throw new Error(`API ${restApiId} not found`);
590
- }
591
-
592
- api.stages.delete(stageName);
593
- this.persistStages(restApiId);
594
-
595
- return {};
596
- }
597
-
598
- // ============ API Key Operations ============
599
-
600
- createApiKey(params) {
601
- const { name, description, enabled, stageKeys, customerId } = params;
602
-
603
- const apiKeyId = uuidv4();
604
- const apiKey = {
605
- id: apiKeyId,
606
- name: name || '',
607
- description: description || '',
608
- enabled: enabled !== false,
609
- value: crypto.randomBytes(20).toString('hex'),
610
- stageKeys: stageKeys || [],
611
- customerId: customerId || null,
612
- createdDate: new Date().toISOString(),
613
- lastUpdatedDate: new Date().toISOString()
614
- };
615
-
616
- this.apiKeys.set(apiKeyId, apiKey);
617
- this.persistApiKeys();
618
-
619
- logger.debug(`🔑 API Key criada: ${name || apiKeyId}`);
620
-
621
- return {
622
- id: apiKey.id,
623
- name: apiKey.name,
624
- value: apiKey.value,
625
- enabled: apiKey.enabled
626
- };
627
- }
628
-
629
- getApiKeys(params) {
630
- const items = Array.from(this.apiKeys.values()).map(key => ({
631
- id: key.id,
632
- name: key.name,
633
- description: key.description,
634
- enabled: key.enabled,
635
- createdDate: key.createdDate,
636
- lastUpdatedDate: key.lastUpdatedDate
637
- }));
638
-
639
- return { items };
640
- }
641
-
642
- // ============ Usage Plan Operations ============
643
-
644
- createUsagePlan(params) {
645
- const { name, description, apiStages, throttle, quota } = params;
646
-
647
- const usagePlanId = uuidv4();
648
- const usagePlan = {
649
- id: usagePlanId,
650
- name: name,
651
- description: description || '',
652
- apiStages: apiStages || [],
653
- throttle: throttle || {
654
- burstLimit: 100,
655
- rateLimit: 10
656
- },
657
- quota: quota || {
658
- limit: 10000,
659
- period: 'DAY',
660
- offset: 0
661
- },
662
- createdDate: new Date().toISOString(),
663
- lastUpdatedDate: new Date().toISOString()
664
- };
665
-
666
- this.usagePlans.set(usagePlanId, usagePlan);
667
- this.persistUsagePlans();
668
-
669
- logger.debug(`📊 Usage Plan criado: ${name}`);
670
-
671
- return {
672
- id: usagePlan.id,
673
- name: usagePlan.name,
674
- createdDate: usagePlan.createdDate
675
- };
676
- }
677
-
678
- // ============ Request Execution ============
679
-
680
- async executeRequest(apiId, stageName, method, path, headers, body, queryString) {
681
- const api = this.apis.get(apiId);
682
- if (!api) {
683
- return this.createResponse(404, 'API not found');
684
- }
685
-
686
- const stage = api.stages.get(stageName);
687
- if (!stage) {
688
- return this.createResponse(404, 'Stage not found');
689
- }
690
-
691
- // Encontra o recurso e método correspondente
692
- const { resource, methodDef } = this.findMatchingResource(api, method, path);
693
-
694
- if (!resource || !methodDef) {
695
- return this.createResponse(404, 'Resource or method not found');
696
- }
697
-
698
- // Verifica API Key
699
- if (methodDef.apiKeyRequired) {
700
- const apiKey = this.extractApiKey(headers);
701
- if (!this.validateApiKey(apiKey)) {
702
- return this.createResponse(403, 'Forbidden: Invalid API Key');
703
- }
704
- }
705
-
706
- // Verifica throttling
707
- const usagePlan = this.getUsagePlanForApi(apiId, stageName);
708
- if (usagePlan && !this.checkThrottle(usagePlan)) {
709
- return this.createResponse(429, 'Too Many Requests');
710
- }
711
-
712
- // Processa integração
713
- const integration = methodDef.integration;
714
- if (!integration) {
715
- return this.createResponse(500, 'Integration not configured');
716
- }
717
-
718
- // Executa a integração baseada no tipo
719
- const response = await this.executeIntegration(integration, {
720
- method,
721
- path,
722
- headers,
723
- body,
724
- queryString,
725
- resource,
726
- stage
727
- });
728
-
729
- // Aplica resposta da integração
730
- const integrationResponse = this.getMatchingIntegrationResponse(
731
- integration.integrationResponses,
732
- response.statusCode
733
- );
734
-
735
- if (integrationResponse) {
736
- response.headers = { ...response.headers, ...integrationResponse.responseParameters };
737
- response.body = this.applyResponseTemplate(integrationResponse, response.body);
738
- }
739
-
740
- this.audit.record({
741
- eventName: 'Invoke',
742
- readOnly: method === 'GET' || method === 'HEAD',
743
- isDataEvent: true,
744
- resources: [{ ARN: `arn:aws:execute-api:local:000000000000:${apiId}/${stageName}/${method}${path}`, type: 'AWS::APIGateway::Stage' }],
745
- requestParameters: { apiId, stageName, method, path },
746
- });
747
-
748
- return response;
749
- }
750
-
751
- findMatchingResource(api, method, path) {
752
- // Busca recurso que corresponda ao path
753
- const resources = Array.from(api.resources.values());
754
-
755
- // Ordena por path mais específico primeiro
756
- resources.sort((a, b) => b.path.length - a.path.length);
757
-
758
- for (const resource of resources) {
759
- if (this.matchPath(resource.path, path)) {
760
- const methodDef = resource.resourceMethods.get(method);
761
- if (methodDef) {
762
- return { resource, methodDef };
763
- }
764
- }
765
- }
766
-
767
- return { resource: null, methodDef: null };
768
- }
769
-
770
- matchPath(pattern, path) {
771
- // Converte path com parâmetros para regex
772
- // Ex: /users/{userId}/posts/{postId}
773
- const regexPattern = pattern
774
- .replace(/\{([^}]+)\}/g, '([^/]+)')
775
- .replace(/\//g, '\\/');
776
-
777
- const regex = new RegExp(`^${regexPattern}$`);
778
- return regex.test(path);
779
- }
780
-
781
- extractPathParams(pattern, path) {
782
- const paramNames = [];
783
- const regexPattern = pattern
784
- .replace(/\{([^}]+)\}/g, (match, paramName) => {
785
- paramNames.push(paramName);
786
- return '([^/]+)';
787
- })
788
- .replace(/\//g, '\\/');
789
-
790
- const regex = new RegExp(`^${regexPattern}$`);
791
- const match = path.match(regex);
792
-
793
- if (match) {
794
- const params = {};
795
- paramNames.forEach((name, index) => {
796
- params[name] = match[index + 1];
797
- });
798
- return params;
799
- }
800
-
801
- return {};
802
- }
803
-
804
- async executeIntegration(integration, context) {
805
- const { type, uri, integrationHttpMethod, requestTemplates, requestParameters } = integration;
806
-
807
- // Prepara o request
808
- let requestBody = context.body;
809
- let requestHeaders = { ...context.headers };
810
-
811
- // Aplica templates de request
812
- if (requestTemplates && requestTemplates['application/json']) {
813
- const template = requestTemplates['application/json'];
814
- requestBody = this.applyRequestTemplate(template, context);
815
- }
816
-
817
- // Aplica mapeamento de parâmetros
818
- if (requestParameters) {
819
- for (const [key, value] of Object.entries(requestParameters)) {
820
- requestHeaders[key] = this.resolveParameter(value, context);
821
- }
822
- }
823
-
824
- switch(type) {
825
- case 'AWS':
826
- case 'AWS_PROXY':
827
- return this.executeAWSIntegration(integration, context, requestBody, requestHeaders);
828
- case 'HTTP':
829
- case 'HTTP_PROXY':
830
- return this.executeHTTPIntegration(integration, context, requestBody, requestHeaders);
831
- case 'MOCK':
832
- return this.executeMockIntegration(integration);
833
- default:
834
- return this.createResponse(501, `Integration type ${type} not supported`);
835
- }
836
- }
837
-
838
- async executeAWSIntegration(integration, context, body, headers) {
839
- // Simula integração com Lambda
840
- const uriParts = integration.uri.split(':');
841
- const functionName = uriParts[uriParts.length - 1];
842
-
843
- // Aqui poderia chamar o simulador Lambda
844
- logger.debug(`Invoking Lambda: ${functionName}`);
845
-
846
- return this.createResponse(200, {
847
- message: `Lambda ${functionName} invoked`,
848
- event: context
849
- });
850
- }
851
-
852
- async executeHTTPIntegration(integration, context, body, headers) {
853
- const { uri, integrationHttpMethod } = integration;
854
-
855
- logger.debug(`HTTP ${integrationHttpMethod} to ${uri}`);
856
-
857
- // Simula chamada HTTP
858
- try {
859
- const response = await this.makeHttpRequest(uri, integrationHttpMethod, headers, body);
860
- return {
861
- statusCode: response.status,
862
- headers: response.headers,
863
- body: response.data
864
- };
865
- } catch (error) {
866
- return this.createResponse(502, 'Bad Gateway');
867
- }
868
- }
869
-
870
- async makeHttpRequest(url, method, headers, body) {
871
- // Simulação - em implementação real, usaria axios ou fetch
872
- return {
873
- status: 200,
874
- headers: {},
875
- data: { message: 'Mock HTTP response' }
876
- };
877
- }
878
-
879
- executeMockIntegration(integration) {
880
- // Retorna resposta mock
881
- const mockResponse = integration.requestTemplates?.mock || {};
882
- return this.createResponse(200, mockResponse);
883
- }
884
-
885
- getMatchingIntegrationResponse(responses, statusCode) {
886
- // Busca response que corresponda ao status code
887
- if (responses.has(statusCode.toString())) {
888
- return responses.get(statusCode.toString());
889
- }
890
-
891
- // Busca default
892
- if (responses.has('default')) {
893
- return responses.get('default');
894
- }
895
-
896
- return null;
897
- }
898
-
899
- applyRequestTemplate(template, context) {
900
- // Implementação simples de template
901
- try {
902
- return JSON.stringify(context);
903
- } catch (error) {
904
- return template;
905
- }
906
- }
907
-
908
- applyResponseTemplate(response, body) {
909
- const templates = response.responseTemplates;
910
- if (templates && templates['application/json']) {
911
- // Aplica template de resposta
912
- try {
913
- return JSON.parse(templates['application/json'].replace(/\$\{([^}]+)\}/g, (match, path) => {
914
- return this.getNestedValue(body, path);
915
- }));
916
- } catch (error) {
917
- return body;
918
- }
919
- }
920
- return body;
921
- }
922
-
923
- resolveParameter(value, context) {
924
- // Resolve parâmetros como method.request.header.X-Header
925
- if (value.includes('method.request.')) {
926
- const parts = value.split('.');
927
- const type = parts[2]; // header, querystring, path
928
- const name = parts[3];
929
-
930
- switch(type) {
931
- case 'header':
932
- return context.headers[name];
933
- case 'querystring':
934
- return context.queryString[name];
935
- case 'path':
936
- return context.pathParams[name];
937
- default:
938
- return null;
939
- }
940
- }
941
- return value;
942
- }
943
-
944
- getNestedValue(obj, path) {
945
- return path.split('.').reduce((current, key) => current?.[key], obj);
946
- }
947
-
948
- extractApiKey(headers) {
949
- return headers['x-api-key'] || headers['X-Api-Key'];
950
- }
951
-
952
- validateApiKey(apiKeyValue) {
953
- if (!apiKeyValue) return false;
954
-
955
- for (const key of this.apiKeys.values()) {
956
- if (key.value === apiKeyValue && key.enabled) {
957
- return true;
958
- }
959
- }
960
- return false;
961
- }
962
-
963
- getUsagePlanForApi(apiId, stageName) {
964
- for (const plan of this.usagePlans.values()) {
965
- const apiStage = plan.apiStages.find(
966
- as => as.apiId === apiId && as.stage === stageName
967
- );
968
- if (apiStage) {
969
- return plan;
970
- }
971
- }
972
- return null;
973
- }
974
-
975
- checkThrottle(usagePlan) {
976
- // Implementação simplificada de throttling
977
- const { rateLimit, burstLimit } = usagePlan.throttle;
978
- // Aqui poderia implementar contagem de requests
979
- return true;
980
- }
981
-
982
- createResponse(statusCode, body) {
983
- return {
984
- statusCode: statusCode,
985
- headers: {
986
- 'Content-Type': 'application/json',
987
- 'Access-Control-Allow-Origin': '*'
988
- },
989
- body: typeof body === 'string' ? body : JSON.stringify(body)
990
- };
991
- }
992
-
993
- // ============ HTTP API Operations ============
994
-
995
- createHttpApi(params) {
996
- const { name, description, protocolType, routeSelectionExpression, corsConfiguration } = params;
997
-
998
- const apiId = `http_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
999
- const api = {
1000
- id: apiId,
1001
- name: name,
1002
- description: description || '',
1003
- protocolType: protocolType || 'HTTP',
1004
- routeSelectionExpression: routeSelectionExpression || '$request.method $request.path',
1005
- corsConfiguration: corsConfiguration || {
1006
- allowOrigins: ['*'],
1007
- allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
1008
- allowHeaders: ['*'],
1009
- maxAge: 300
1010
- },
1011
- routes: new Map(),
1012
- integrations: new Map(),
1013
- stages: new Map(),
1014
- createdDate: new Date().toISOString()
1015
- };
1016
-
1017
- this.apis.set(apiId, api);
1018
- this.persistAPIs();
1019
-
1020
- logger.debug(`✅ HTTP API criada: ${name} (${apiId})`);
1021
-
1022
- return {
1023
- ApiId: apiId,
1024
- Name: api.name,
1025
- ProtocolType: api.protocolType
1026
- };
1027
- }
1028
-
1029
- createRoute(params) {
1030
- const { apiId, routeKey, authorizationType, target } = params;
1031
- const api = this.apis.get(apiId);
1032
-
1033
- if (!api) {
1034
- throw new Error(`API ${apiId} not found`);
1035
- }
1036
-
1037
- const route = {
1038
- routeKey: routeKey,
1039
- authorizationType: authorizationType || 'NONE',
1040
- target: target,
1041
- createdAt: new Date().toISOString()
1042
- };
1043
-
1044
- api.routes.set(routeKey, route);
1045
- this.persistAPIs();
1046
-
1047
- return { route };
1048
- }
1049
-
1050
- // ============ Persistence ============
1051
-
1052
- loadAPIs() {
1053
- const saved = this.store.read('__apis__');
1054
- if (saved) {
1055
- for (const [id, data] of Object.entries(saved)) {
1056
- // Reconstitui Maps
1057
- data.resources = new Map(Object.entries(data.resources || {}));
1058
- data.stages = new Map(Object.entries(data.stages || {}));
1059
- data.deployments = new Map(Object.entries(data.deployments || {}));
1060
- data.models = new Map(Object.entries(data.models || {}));
1061
- data.authorizers = new Map(Object.entries(data.authorizers || {}));
1062
- this.apis.set(id, data);
1063
- }
1064
- }
1065
- }
1066
-
1067
- loadWebSocketAPIs() {
1068
- const saved = this.store.read('__websocket_apis__');
1069
- if (saved) {
1070
- for (const [id, data] of Object.entries(saved)) {
1071
- this.websocketApis.set(id, data);
1072
- }
1073
- }
1074
- }
1075
-
1076
- loadDeployments() {
1077
- const saved = this.store.read('__deployments__');
1078
- if (saved) {
1079
- for (const [id, data] of Object.entries(saved)) {
1080
- this.deployments.set(id, data);
1081
- }
1082
- }
1083
- }
1084
-
1085
- loadStages() {
1086
- const saved = this.store.read('__stages__');
1087
- if (saved) {
1088
- for (const [id, data] of Object.entries(saved)) {
1089
- this.stages.set(id, data);
1090
- }
1091
- }
1092
- }
1093
-
1094
- loadResources() {
1095
- const saved = this.store.read('__resources__');
1096
- if (saved) {
1097
- for (const [id, data] of Object.entries(saved)) {
1098
- this.resources.set(id, data);
1099
- }
1100
- }
1101
- }
1102
-
1103
- loadMethods() {
1104
- const saved = this.store.read('__methods__');
1105
- if (saved) {
1106
- for (const [id, data] of Object.entries(saved)) {
1107
- this.methods.set(id, data);
1108
- }
1109
- }
1110
- }
1111
-
1112
- loadIntegrations() {
1113
- const saved = this.store.read('__integrations__');
1114
- if (saved) {
1115
- for (const [id, data] of Object.entries(saved)) {
1116
- this.integrations.set(id, data);
1117
- }
1118
- }
1119
- }
1120
-
1121
- loadModels() {
1122
- const saved = this.store.read('__models__');
1123
- if (saved) {
1124
- for (const [id, data] of Object.entries(saved)) {
1125
- this.models.set(id, data);
1126
- }
1127
- }
1128
- }
1129
-
1130
- loadUsagePlans() {
1131
- const saved = this.store.read('__usage_plans__');
1132
- if (saved) {
1133
- for (const [id, data] of Object.entries(saved)) {
1134
- this.usagePlans.set(id, data);
1135
- }
1136
- }
1137
- }
1138
-
1139
- loadApiKeys() {
1140
- const saved = this.store.read('__api_keys__');
1141
- if (saved) {
1142
- for (const [id, data] of Object.entries(saved)) {
1143
- this.apiKeys.set(id, data);
1144
- }
1145
- }
1146
- }
1147
-
1148
- loadDomainNames() {
1149
- const saved = this.store.read('__domain_names__');
1150
- if (saved) {
1151
- for (const [id, data] of Object.entries(saved)) {
1152
- this.domainNames.set(id, data);
1153
- }
1154
- }
1155
- }
1156
-
1157
- persistAPIs() {
1158
- const apisObj = {};
1159
- for (const [id, api] of this.apis.entries()) {
1160
- apisObj[id] = {
1161
- ...api,
1162
- resources: Object.fromEntries(api.resources),
1163
- stages: Object.fromEntries(api.stages),
1164
- deployments: Object.fromEntries(api.deployments),
1165
- models: Object.fromEntries(api.models),
1166
- authorizers: Object.fromEntries(api.authorizers)
1167
- };
1168
- }
1169
- this.store.write('__apis__', apisObj);
1170
- }
1171
-
1172
- persistResources(apiId) {
1173
- const api = this.apis.get(apiId);
1174
- if (api) {
1175
- this.persistAPIs();
1176
- }
1177
- }
1178
-
1179
- persistMethods(apiId, resourceId) {
1180
- this.persistResources(apiId);
1181
- }
1182
-
1183
- persistIntegrations(apiId, resourceId) {
1184
- this.persistMethods(apiId, resourceId);
1185
- }
1186
-
1187
- persistStages(apiId) {
1188
- this.persistAPIs();
1189
- }
1190
-
1191
- persistDeployments(apiId) {
1192
- this.persistAPIs();
1193
- }
1194
-
1195
- persistApiKeys() {
1196
- const keysObj = {};
1197
- for (const [id, key] of this.apiKeys.entries()) {
1198
- keysObj[id] = key;
1199
- }
1200
- this.store.write('__api_keys__', keysObj);
1201
- }
1202
-
1203
- persistUsagePlans() {
1204
- const plansObj = {};
1205
- for (const [id, plan] of this.usagePlans.entries()) {
1206
- plansObj[id] = plan;
1207
- }
1208
- this.store.write('__usage_plans__', plansObj);
1209
- }
1210
-
1211
- async reset() {
1212
- this.apis.clear();
1213
- this.websocketApis.clear();
1214
- this.deployments.clear();
1215
- this.stages.clear();
1216
- this.resources.clear();
1217
- this.methods.clear();
1218
- this.integrations.clear();
1219
- this.models.clear();
1220
- this.usagePlans.clear();
1221
- this.apiKeys.clear();
1222
- this.domainNames.clear();
1223
-
1224
- this.persistAPIs();
1225
- this.persistApiKeys();
1226
- this.persistUsagePlans();
1227
-
1228
- logger.debug('API Gateway: Todos os dados resetados');
1229
- }
1230
-
1231
- // ============ Stats ============
1232
-
1233
- getAPIsCount() {
1234
- return this.apis.size;
1235
- }
1236
-
1237
- getDeploymentsCount() {
1238
- let count = 0;
1239
- for (const api of this.apis.values()) {
1240
- count += api.deployments.size;
1241
- }
1242
- return count;
1243
- }
1244
-
1245
- getStagesCount() {
1246
- let count = 0;
1247
- for (const api of this.apis.values()) {
1248
- count += api.stages.size;
1249
- }
1250
- return count;
1251
- }
1252
-
1253
- getResourcesCount() {
1254
- let count = 0;
1255
- for (const api of this.apis.values()) {
1256
- count += api.resources.size;
1257
- }
1258
- return count;
1259
- }
1260
- }
1261
-
1
+ /**
2
+ * API Gateway Simulator Core
3
+ * Simula REST APIs, HTTP APIs, WebSocket APIs, Routes, Integrations
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+ const { v4: uuidv4 } = require('uuid');
8
+ const logger = require('../../utils/logger');
9
+ const LocalStore = require('../../utils/local-store');
10
+ const path = require('path');
11
+ const { URLPattern } = require('urlpattern-polyfill');
12
+ const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
13
+
14
+ class APIGatewaySimulator {
15
+ constructor(config) {
16
+ this.config = config;
17
+ this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'apigateway');
18
+ this.store = new LocalStore(this.dataDir);
19
+ this.apis = new Map();
20
+ this.websocketApis = new Map();
21
+ this.deployments = new Map();
22
+ this.stages = new Map();
23
+ this.resources = new Map();
24
+ this.methods = new Map();
25
+ this.integrations = new Map();
26
+ this.models = new Map();
27
+ this.usagePlans = new Map();
28
+ this.apiKeys = new Map();
29
+ this.domainNames = new Map();
30
+ this.audit = new CloudTrailAudit('execute-api.amazonaws.com');
31
+ }
32
+
33
+ async initialize() {
34
+ logger.debug('Inicializando API Gateway Simulator...');
35
+ this.loadAPIs();
36
+ this.loadWebSocketAPIs();
37
+ this.loadDeployments();
38
+ this.loadStages();
39
+ this.loadResources();
40
+ this.loadMethods();
41
+ this.loadIntegrations();
42
+ this.loadModels();
43
+ this.loadUsagePlans();
44
+ this.loadApiKeys();
45
+ this.loadDomainNames();
46
+
47
+ logger.debug(`✅ API Gateway Simulator inicializado com ${this.apis.size} APIs, ${this.resources.size} resources, ${this.methods.size} methods`);
48
+ }
49
+
50
+ // ============ REST API Operations ============
51
+
52
+ createRestApi(params) {
53
+ const { name, description, version, apiKeySource, endpointConfiguration, tags } = params;
54
+
55
+ const apiId = `api_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
56
+ const api = {
57
+ id: apiId,
58
+ name: name,
59
+ description: description || '',
60
+ version: version || '1.0',
61
+ createdDate: new Date().toISOString(),
62
+ apiKeySource: apiKeySource || 'HEADER',
63
+ endpointConfiguration: endpointConfiguration || {
64
+ types: ['REGIONAL']
65
+ },
66
+ tags: tags || {},
67
+ resources: new Map(),
68
+ stages: new Map(),
69
+ deployments: new Map(),
70
+ models: new Map(),
71
+ authorizers: new Map(),
72
+ gatewayResponses: new Map(),
73
+ documentationParts: new Map()
74
+ };
75
+
76
+ // Cria recurso raiz
77
+ const rootResource = {
78
+ id: uuidv4(),
79
+ path: '/',
80
+ pathPart: '',
81
+ parentId: null,
82
+ resourceMethods: new Map()
83
+ };
84
+
85
+ api.resources.set('/', rootResource);
86
+
87
+ this.apis.set(apiId, api);
88
+ this.persistAPIs();
89
+
90
+ logger.debug(`✅ REST API criada: ${name} (${apiId})`);
91
+
92
+ return {
93
+ id: apiId,
94
+ name: api.name,
95
+ createdDate: api.createdDate
96
+ };
97
+ }
98
+
99
+ getRestApis() {
100
+ return {
101
+ items: Array.from(this.apis.values()).map(api => ({
102
+ id: api.id,
103
+ name: api.name,
104
+ description: api.description,
105
+ version: api.version,
106
+ createdDate: api.createdDate,
107
+ apiKeySource: api.apiKeySource
108
+ }))
109
+ };
110
+ }
111
+
112
+ getRestApi(params) {
113
+ const { restApiId } = params;
114
+ const api = this.apis.get(restApiId);
115
+
116
+ if (!api) {
117
+ throw new Error(`API ${restApiId} not found`);
118
+ }
119
+
120
+ return {
121
+ id: api.id,
122
+ name: api.name,
123
+ description: api.description,
124
+ version: api.version,
125
+ createdDate: api.createdDate,
126
+ apiKeySource: api.apiKeySource,
127
+ endpointConfiguration: api.endpointConfiguration,
128
+ tags: api.tags
129
+ };
130
+ }
131
+
132
+ deleteRestApi(params) {
133
+ const { restApiId } = params;
134
+
135
+ if (!this.apis.has(restApiId)) {
136
+ throw new Error(`API ${restApiId} not found`);
137
+ }
138
+
139
+ this.apis.delete(restApiId);
140
+ this.persistAPIs();
141
+
142
+ return {};
143
+ }
144
+
145
+ // ============ Resource Operations ============
146
+
147
+ createResource(params) {
148
+ const { restApiId, parentId, pathPart } = params;
149
+ const api = this.apis.get(restApiId);
150
+
151
+ if (!api) {
152
+ throw new Error(`API ${restApiId} not found`);
153
+ }
154
+
155
+ const parentResource = api.resources.get(parentId);
156
+ if (!parentResource) {
157
+ throw new Error(`Parent resource ${parentId} not found`);
158
+ }
159
+
160
+ const resourceId = uuidv4();
161
+ const fullPath = parentResource.path === '/'
162
+ ? `/${pathPart}`
163
+ : `${parentResource.path}/${pathPart}`;
164
+
165
+ const resource = {
166
+ id: resourceId,
167
+ path: fullPath,
168
+ pathPart: pathPart,
169
+ parentId: parentId,
170
+ resourceMethods: new Map()
171
+ };
172
+
173
+ api.resources.set(resourceId, resource);
174
+ this.persistResources(restApiId);
175
+
176
+ logger.debug(`📁 Recurso criado: ${fullPath} (${resourceId})`);
177
+
178
+ return {
179
+ id: resourceId,
180
+ path: resource.path,
181
+ parentId: resource.parentId
182
+ };
183
+ }
184
+
185
+ getResources(params) {
186
+ const { restApiId } = params;
187
+ const api = this.apis.get(restApiId);
188
+
189
+ if (!api) {
190
+ throw new Error(`API ${restApiId} not found`);
191
+ }
192
+
193
+ const items = Array.from(api.resources.values()).map(resource => ({
194
+ id: resource.id,
195
+ path: resource.path,
196
+ pathPart: resource.pathPart,
197
+ parentId: resource.parentId,
198
+ resourceMethods: Object.fromEntries(resource.resourceMethods)
199
+ }));
200
+
201
+ return { items };
202
+ }
203
+
204
+ deleteResource(params) {
205
+ const { restApiId, resourceId } = params;
206
+ const api = this.apis.get(restApiId);
207
+
208
+ if (!api) {
209
+ throw new Error(`API ${restApiId} not found`);
210
+ }
211
+
212
+ if (!api.resources.has(resourceId)) {
213
+ throw new Error(`Resource ${resourceId} not found`);
214
+ }
215
+
216
+ // Verifica se tem filhos
217
+ const hasChildren = Array.from(api.resources.values()).some(
218
+ r => r.parentId === resourceId
219
+ );
220
+
221
+ if (hasChildren) {
222
+ throw new Error('Resource has children');
223
+ }
224
+
225
+ api.resources.delete(resourceId);
226
+ this.persistResources(restApiId);
227
+
228
+ return {};
229
+ }
230
+
231
+ // ============ Method Operations ============
232
+
233
+ putMethod(params) {
234
+ const { restApiId, resourceId, httpMethod, authorizationType, apiKeyRequired, requestParameters, requestModels, authorizerId } = params;
235
+ const api = this.apis.get(restApiId);
236
+
237
+ if (!api) {
238
+ throw new Error(`API ${restApiId} not found`);
239
+ }
240
+
241
+ const resource = api.resources.get(resourceId);
242
+ if (!resource) {
243
+ throw new Error(`Resource ${resourceId} not found`);
244
+ }
245
+
246
+ const method = {
247
+ httpMethod: httpMethod,
248
+ authorizationType: authorizationType || 'NONE',
249
+ apiKeyRequired: apiKeyRequired || false,
250
+ requestParameters: requestParameters || {},
251
+ requestModels: requestModels || {},
252
+ authorizerId: authorizerId || null,
253
+ methodResponses: new Map(),
254
+ integration: null
255
+ };
256
+
257
+ resource.resourceMethods.set(httpMethod, method);
258
+ this.persistMethods(restApiId, resourceId);
259
+
260
+ logger.debug(`🔧 Método criado: ${httpMethod} ${resource.path}`);
261
+
262
+ return {
263
+ httpMethod: method.httpMethod,
264
+ authorizationType: method.authorizationType,
265
+ apiKeyRequired: method.apiKeyRequired
266
+ };
267
+ }
268
+
269
+ getMethod(params) {
270
+ const { restApiId, resourceId, httpMethod } = params;
271
+ const api = this.apis.get(restApiId);
272
+
273
+ if (!api) {
274
+ throw new Error(`API ${restApiId} not found`);
275
+ }
276
+
277
+ const resource = api.resources.get(resourceId);
278
+ if (!resource) {
279
+ throw new Error(`Resource ${resourceId} not found`);
280
+ }
281
+
282
+ const method = resource.resourceMethods.get(httpMethod);
283
+ if (!method) {
284
+ throw new Error(`Method ${httpMethod} not found`);
285
+ }
286
+
287
+ return {
288
+ httpMethod: method.httpMethod,
289
+ authorizationType: method.authorizationType,
290
+ apiKeyRequired: method.apiKeyRequired,
291
+ requestParameters: method.requestParameters,
292
+ requestModels: method.requestModels
293
+ };
294
+ }
295
+
296
+ deleteMethod(params) {
297
+ const { restApiId, resourceId, httpMethod } = params;
298
+ const api = this.apis.get(restApiId);
299
+
300
+ if (!api) {
301
+ throw new Error(`API ${restApiId} not found`);
302
+ }
303
+
304
+ const resource = api.resources.get(resourceId);
305
+ if (!resource) {
306
+ throw new Error(`Resource ${resourceId} not found`);
307
+ }
308
+
309
+ resource.resourceMethods.delete(httpMethod);
310
+ this.persistMethods(restApiId, resourceId);
311
+
312
+ return {};
313
+ }
314
+
315
+ // ============ Integration Operations ============
316
+
317
+ putIntegration(params) {
318
+ const { restApiId, resourceId, httpMethod, type, integrationHttpMethod, uri, credentials, requestParameters, requestTemplates, passthroughBehavior, timeoutInMillis, cacheNamespace, cacheKeyParameters, contentHandling } = params;
319
+ const api = this.apis.get(restApiId);
320
+
321
+ if (!api) {
322
+ throw new Error(`API ${restApiId} not found`);
323
+ }
324
+
325
+ const resource = api.resources.get(resourceId);
326
+ if (!resource) {
327
+ throw new Error(`Resource ${resourceId} not found`);
328
+ }
329
+
330
+ const method = resource.resourceMethods.get(httpMethod);
331
+ if (!method) {
332
+ throw new Error(`Method ${httpMethod} not found`);
333
+ }
334
+
335
+ const integration = {
336
+ type: type || 'HTTP',
337
+ integrationHttpMethod: integrationHttpMethod,
338
+ uri: uri,
339
+ credentials: credentials || null,
340
+ requestParameters: requestParameters || {},
341
+ requestTemplates: requestTemplates || {},
342
+ passthroughBehavior: passthroughBehavior || 'WHEN_NO_MATCH',
343
+ timeoutInMillis: timeoutInMillis || 29000,
344
+ cacheNamespace: cacheNamespace || '',
345
+ cacheKeyParameters: cacheKeyParameters || [],
346
+ contentHandling: contentHandling || null,
347
+ integrationResponses: new Map()
348
+ };
349
+
350
+ method.integration = integration;
351
+ this.persistIntegrations(restApiId, resourceId);
352
+
353
+ logger.debug(`🔌 Integração criada: ${type} -> ${uri}`);
354
+
355
+ return {
356
+ type: integration.type,
357
+ integrationHttpMethod: integration.integrationHttpMethod,
358
+ uri: integration.uri
359
+ };
360
+ }
361
+
362
+ getIntegration(params) {
363
+ const { restApiId, resourceId, httpMethod } = params;
364
+ const api = this.apis.get(restApiId);
365
+
366
+ if (!api) {
367
+ throw new Error(`API ${restApiId} not found`);
368
+ }
369
+
370
+ const resource = api.resources.get(resourceId);
371
+ if (!resource) {
372
+ throw new Error(`Resource ${resourceId} not found`);
373
+ }
374
+
375
+ const method = resource.resourceMethods.get(httpMethod);
376
+ if (!method || !method.integration) {
377
+ throw new Error(`Integration not found for ${httpMethod}`);
378
+ }
379
+
380
+ const integration = method.integration;
381
+
382
+ return {
383
+ type: integration.type,
384
+ integrationHttpMethod: integration.integrationHttpMethod,
385
+ uri: integration.uri,
386
+ credentials: integration.credentials,
387
+ requestParameters: integration.requestParameters,
388
+ requestTemplates: integration.requestTemplates,
389
+ passthroughBehavior: integration.passthroughBehavior,
390
+ timeoutInMillis: integration.timeoutInMillis
391
+ };
392
+ }
393
+
394
+ deleteIntegration(params) {
395
+ const { restApiId, resourceId, httpMethod } = params;
396
+ const api = this.apis.get(restApiId);
397
+
398
+ if (!api) {
399
+ throw new Error(`API ${restApiId} not found`);
400
+ }
401
+
402
+ const resource = api.resources.get(resourceId);
403
+ if (!resource) {
404
+ throw new Error(`Resource ${resourceId} not found`);
405
+ }
406
+
407
+ const method = resource.resourceMethods.get(httpMethod);
408
+ if (method) {
409
+ method.integration = null;
410
+ this.persistIntegrations(restApiId, resourceId);
411
+ }
412
+
413
+ return {};
414
+ }
415
+
416
+ // ============ Integration Response Operations ============
417
+
418
+ putIntegrationResponse(params) {
419
+ const { restApiId, resourceId, httpMethod, statusCode, selectionPattern, responseParameters, responseTemplates, contentHandling } = params;
420
+ const api = this.apis.get(restApiId);
421
+
422
+ if (!api) {
423
+ throw new Error(`API ${restApiId} not found`);
424
+ }
425
+
426
+ const resource = api.resources.get(resourceId);
427
+ if (!resource) {
428
+ throw new Error(`Resource ${resourceId} not found`);
429
+ }
430
+
431
+ const method = resource.resourceMethods.get(httpMethod);
432
+ if (!method || !method.integration) {
433
+ throw new Error(`Integration not found for ${httpMethod}`);
434
+ }
435
+
436
+ const integrationResponse = {
437
+ statusCode: statusCode,
438
+ selectionPattern: selectionPattern || '',
439
+ responseParameters: responseParameters || {},
440
+ responseTemplates: responseTemplates || {},
441
+ contentHandling: contentHandling || null
442
+ };
443
+
444
+ method.integration.integrationResponses.set(statusCode, integrationResponse);
445
+ this.persistIntegrations(restApiId, resourceId);
446
+
447
+ return {
448
+ statusCode: integrationResponse.statusCode,
449
+ selectionPattern: integrationResponse.selectionPattern
450
+ };
451
+ }
452
+
453
+ // ============ Deployment Operations ============
454
+
455
+ createDeployment(params) {
456
+ const { restApiId, stageName, stageDescription, description, variables } = params;
457
+ const api = this.apis.get(restApiId);
458
+
459
+ if (!api) {
460
+ throw new Error(`API ${restApiId} not found`);
461
+ }
462
+
463
+ const deploymentId = uuidv4();
464
+ const deployment = {
465
+ id: deploymentId,
466
+ description: description || '',
467
+ createdDate: new Date().toISOString(),
468
+ apiId: restApiId
469
+ };
470
+
471
+ api.deployments.set(deploymentId, deployment);
472
+
473
+ if (stageName) {
474
+ this.createStage({
475
+ restApiId,
476
+ stageName,
477
+ description: stageDescription,
478
+ variables
479
+ });
480
+ }
481
+
482
+ this.persistDeployments(restApiId);
483
+
484
+ logger.debug(`🚀 Deployment criado: ${deploymentId} para stage: ${stageName || 'N/A'}`);
485
+
486
+ return {
487
+ id: deploymentId,
488
+ createdDate: deployment.createdDate
489
+ };
490
+ }
491
+
492
+ // ============ Stage Operations ============
493
+
494
+ createStage(params) {
495
+ const { restApiId, stageName, description, variables, deploymentId, cacheClusterEnabled, cacheClusterSize, tracingEnabled } = params;
496
+ const api = this.apis.get(restApiId);
497
+
498
+ if (!api) {
499
+ throw new Error(`API ${restApiId} not found`);
500
+ }
501
+
502
+ const stage = {
503
+ stageName: stageName,
504
+ description: description || '',
505
+ createdDate: new Date().toISOString(),
506
+ lastUpdatedDate: new Date().toISOString(),
507
+ deploymentId: deploymentId,
508
+ variables: variables || {},
509
+ cacheClusterEnabled: cacheClusterEnabled || false,
510
+ cacheClusterSize: cacheClusterSize || null,
511
+ tracingEnabled: tracingEnabled || false,
512
+ methodSettings: new Map()
513
+ };
514
+
515
+ api.stages.set(stageName, stage);
516
+ this.persistStages(restApiId);
517
+
518
+ // Cria endpoint URL
519
+ const endpointUrl = `http://localhost:${this.config.ports.apigateway}/${restApiId}/${stageName}`;
520
+
521
+ logger.debug(`📡 Stage criado: ${stageName} em ${endpointUrl}`);
522
+
523
+ return {
524
+ stageName: stage.stageName,
525
+ createdDate: stage.createdDate,
526
+ deploymentId: stage.deploymentId
527
+ };
528
+ }
529
+
530
+ getStage(params) {
531
+ const { restApiId, stageName } = params;
532
+ const api = this.apis.get(restApiId);
533
+
534
+ if (!api) {
535
+ throw new Error(`API ${restApiId} not found`);
536
+ }
537
+
538
+ const stage = api.stages.get(stageName);
539
+ if (!stage) {
540
+ throw new Error(`Stage ${stageName} not found`);
541
+ }
542
+
543
+ return {
544
+ stageName: stage.stageName,
545
+ description: stage.description,
546
+ createdDate: stage.createdDate,
547
+ lastUpdatedDate: stage.lastUpdatedDate,
548
+ deploymentId: stage.deploymentId,
549
+ variables: stage.variables,
550
+ methodSettings: Object.fromEntries(stage.methodSettings),
551
+ cacheClusterEnabled: stage.cacheClusterEnabled,
552
+ tracingEnabled: stage.tracingEnabled
553
+ };
554
+ }
555
+
556
+ updateStage(params) {
557
+ const { restApiId, stageName, description, variables, deploymentId, tracingEnabled } = params;
558
+ const api = this.apis.get(restApiId);
559
+
560
+ if (!api) {
561
+ throw new Error(`API ${restApiId} not found`);
562
+ }
563
+
564
+ const stage = api.stages.get(stageName);
565
+ if (!stage) {
566
+ throw new Error(`Stage ${stageName} not found`);
567
+ }
568
+
569
+ if (description !== undefined) stage.description = description;
570
+ if (variables !== undefined) stage.variables = { ...stage.variables, ...variables };
571
+ if (deploymentId !== undefined) stage.deploymentId = deploymentId;
572
+ if (tracingEnabled !== undefined) stage.tracingEnabled = tracingEnabled;
573
+
574
+ stage.lastUpdatedDate = new Date().toISOString();
575
+ this.persistStages(restApiId);
576
+
577
+ return {
578
+ stageName: stage.stageName,
579
+ description: stage.description,
580
+ lastUpdatedDate: stage.lastUpdatedDate
581
+ };
582
+ }
583
+
584
+ deleteStage(params) {
585
+ const { restApiId, stageName } = params;
586
+ const api = this.apis.get(restApiId);
587
+
588
+ if (!api) {
589
+ throw new Error(`API ${restApiId} not found`);
590
+ }
591
+
592
+ api.stages.delete(stageName);
593
+ this.persistStages(restApiId);
594
+
595
+ return {};
596
+ }
597
+
598
+ // ============ API Key Operations ============
599
+
600
+ createApiKey(params) {
601
+ const { name, description, enabled, stageKeys, customerId } = params;
602
+
603
+ const apiKeyId = uuidv4();
604
+ const apiKey = {
605
+ id: apiKeyId,
606
+ name: name || '',
607
+ description: description || '',
608
+ enabled: enabled !== false,
609
+ value: crypto.randomBytes(20).toString('hex'),
610
+ stageKeys: stageKeys || [],
611
+ customerId: customerId || null,
612
+ createdDate: new Date().toISOString(),
613
+ lastUpdatedDate: new Date().toISOString()
614
+ };
615
+
616
+ this.apiKeys.set(apiKeyId, apiKey);
617
+ this.persistApiKeys();
618
+
619
+ logger.debug(`🔑 API Key criada: ${name || apiKeyId}`);
620
+
621
+ return {
622
+ id: apiKey.id,
623
+ name: apiKey.name,
624
+ value: apiKey.value,
625
+ enabled: apiKey.enabled
626
+ };
627
+ }
628
+
629
+ getApiKeys(params) {
630
+ const items = Array.from(this.apiKeys.values()).map(key => ({
631
+ id: key.id,
632
+ name: key.name,
633
+ description: key.description,
634
+ enabled: key.enabled,
635
+ createdDate: key.createdDate,
636
+ lastUpdatedDate: key.lastUpdatedDate
637
+ }));
638
+
639
+ return { items };
640
+ }
641
+
642
+ // ============ Usage Plan Operations ============
643
+
644
+ createUsagePlan(params) {
645
+ const { name, description, apiStages, throttle, quota } = params;
646
+
647
+ const usagePlanId = uuidv4();
648
+ const usagePlan = {
649
+ id: usagePlanId,
650
+ name: name,
651
+ description: description || '',
652
+ apiStages: apiStages || [],
653
+ throttle: throttle || {
654
+ burstLimit: 100,
655
+ rateLimit: 10
656
+ },
657
+ quota: quota || {
658
+ limit: 10000,
659
+ period: 'DAY',
660
+ offset: 0
661
+ },
662
+ createdDate: new Date().toISOString(),
663
+ lastUpdatedDate: new Date().toISOString()
664
+ };
665
+
666
+ this.usagePlans.set(usagePlanId, usagePlan);
667
+ this.persistUsagePlans();
668
+
669
+ logger.debug(`📊 Usage Plan criado: ${name}`);
670
+
671
+ return {
672
+ id: usagePlan.id,
673
+ name: usagePlan.name,
674
+ createdDate: usagePlan.createdDate
675
+ };
676
+ }
677
+
678
+ // ============ Request Execution ============
679
+
680
+ async executeRequest(apiId, stageName, method, path, headers, body, queryString) {
681
+ const api = this.apis.get(apiId);
682
+ if (!api) {
683
+ return this.createResponse(404, 'API not found');
684
+ }
685
+
686
+ const stage = api.stages.get(stageName);
687
+ if (!stage) {
688
+ return this.createResponse(404, 'Stage not found');
689
+ }
690
+
691
+ // Encontra o recurso e método correspondente
692
+ const { resource, methodDef } = this.findMatchingResource(api, method, path);
693
+
694
+ if (!resource || !methodDef) {
695
+ return this.createResponse(404, 'Resource or method not found');
696
+ }
697
+
698
+ // Verifica API Key
699
+ if (methodDef.apiKeyRequired) {
700
+ const apiKey = this.extractApiKey(headers);
701
+ if (!this.validateApiKey(apiKey)) {
702
+ return this.createResponse(403, 'Forbidden: Invalid API Key');
703
+ }
704
+ }
705
+
706
+ // Verifica throttling
707
+ const usagePlan = this.getUsagePlanForApi(apiId, stageName);
708
+ if (usagePlan && !this.checkThrottle(usagePlan)) {
709
+ return this.createResponse(429, 'Too Many Requests');
710
+ }
711
+
712
+ // Processa integração
713
+ const integration = methodDef.integration;
714
+ if (!integration) {
715
+ return this.createResponse(500, 'Integration not configured');
716
+ }
717
+
718
+ // Executa a integração baseada no tipo
719
+ const response = await this.executeIntegration(integration, {
720
+ method,
721
+ path,
722
+ headers,
723
+ body,
724
+ queryString,
725
+ resource,
726
+ stage
727
+ });
728
+
729
+ // Aplica resposta da integração
730
+ const integrationResponse = this.getMatchingIntegrationResponse(
731
+ integration.integrationResponses,
732
+ response.statusCode
733
+ );
734
+
735
+ if (integrationResponse) {
736
+ response.headers = { ...response.headers, ...integrationResponse.responseParameters };
737
+ response.body = this.applyResponseTemplate(integrationResponse, response.body);
738
+ }
739
+
740
+ this.audit.record({
741
+ eventName: 'Invoke',
742
+ readOnly: method === 'GET' || method === 'HEAD',
743
+ isDataEvent: true,
744
+ resources: [{ ARN: `arn:aws:execute-api:local:000000000000:${apiId}/${stageName}/${method}${path}`, type: 'AWS::APIGateway::Stage' }],
745
+ requestParameters: { apiId, stageName, method, path },
746
+ });
747
+
748
+ return response;
749
+ }
750
+
751
+ findMatchingResource(api, method, path) {
752
+ // Busca recurso que corresponda ao path
753
+ const resources = Array.from(api.resources.values());
754
+
755
+ // Ordena por path mais específico primeiro
756
+ resources.sort((a, b) => b.path.length - a.path.length);
757
+
758
+ for (const resource of resources) {
759
+ if (this.matchPath(resource.path, path)) {
760
+ const methodDef = resource.resourceMethods.get(method);
761
+ if (methodDef) {
762
+ return { resource, methodDef };
763
+ }
764
+ }
765
+ }
766
+
767
+ return { resource: null, methodDef: null };
768
+ }
769
+
770
+ matchPath(pattern, path) {
771
+ // Converte path com parâmetros para regex
772
+ // Ex: /users/{userId}/posts/{postId}
773
+ const regexPattern = pattern
774
+ .replace(/\{([^}]+)\}/g, '([^/]+)')
775
+ .replace(/\//g, '\\/');
776
+
777
+ const regex = new RegExp(`^${regexPattern}$`);
778
+ return regex.test(path);
779
+ }
780
+
781
+ extractPathParams(pattern, path) {
782
+ const paramNames = [];
783
+ const regexPattern = pattern
784
+ .replace(/\{([^}]+)\}/g, (match, paramName) => {
785
+ paramNames.push(paramName);
786
+ return '([^/]+)';
787
+ })
788
+ .replace(/\//g, '\\/');
789
+
790
+ const regex = new RegExp(`^${regexPattern}$`);
791
+ const match = path.match(regex);
792
+
793
+ if (match) {
794
+ const params = {};
795
+ paramNames.forEach((name, index) => {
796
+ params[name] = match[index + 1];
797
+ });
798
+ return params;
799
+ }
800
+
801
+ return {};
802
+ }
803
+
804
+ async executeIntegration(integration, context) {
805
+ const { type, uri, integrationHttpMethod, requestTemplates, requestParameters } = integration;
806
+
807
+ // Prepara o request
808
+ let requestBody = context.body;
809
+ let requestHeaders = { ...context.headers };
810
+
811
+ // Aplica templates de request
812
+ if (requestTemplates && requestTemplates['application/json']) {
813
+ const template = requestTemplates['application/json'];
814
+ requestBody = this.applyRequestTemplate(template, context);
815
+ }
816
+
817
+ // Aplica mapeamento de parâmetros
818
+ if (requestParameters) {
819
+ for (const [key, value] of Object.entries(requestParameters)) {
820
+ requestHeaders[key] = this.resolveParameter(value, context);
821
+ }
822
+ }
823
+
824
+ switch(type) {
825
+ case 'AWS':
826
+ case 'AWS_PROXY':
827
+ return this.executeAWSIntegration(integration, context, requestBody, requestHeaders);
828
+ case 'HTTP':
829
+ case 'HTTP_PROXY':
830
+ return this.executeHTTPIntegration(integration, context, requestBody, requestHeaders);
831
+ case 'MOCK':
832
+ return this.executeMockIntegration(integration);
833
+ default:
834
+ return this.createResponse(501, `Integration type ${type} not supported`);
835
+ }
836
+ }
837
+
838
+ async executeAWSIntegration(integration, context, body, headers) {
839
+ // Simula integração com Lambda
840
+ const uriParts = integration.uri.split(':');
841
+ const functionName = uriParts[uriParts.length - 1];
842
+
843
+ // Aqui poderia chamar o simulador Lambda
844
+ logger.debug(`Invoking Lambda: ${functionName}`);
845
+
846
+ return this.createResponse(200, {
847
+ message: `Lambda ${functionName} invoked`,
848
+ event: context
849
+ });
850
+ }
851
+
852
+ async executeHTTPIntegration(integration, context, body, headers) {
853
+ const { uri, integrationHttpMethod } = integration;
854
+
855
+ logger.debug(`HTTP ${integrationHttpMethod} to ${uri}`);
856
+
857
+ // Simula chamada HTTP
858
+ try {
859
+ const response = await this.makeHttpRequest(uri, integrationHttpMethod, headers, body);
860
+ return {
861
+ statusCode: response.status,
862
+ headers: response.headers,
863
+ body: response.data
864
+ };
865
+ } catch (error) {
866
+ return this.createResponse(502, 'Bad Gateway');
867
+ }
868
+ }
869
+
870
+ async makeHttpRequest(url, method, headers, body) {
871
+ // Simulação - em implementação real, usaria axios ou fetch
872
+ return {
873
+ status: 200,
874
+ headers: {},
875
+ data: { message: 'Mock HTTP response' }
876
+ };
877
+ }
878
+
879
+ executeMockIntegration(integration) {
880
+ // Retorna resposta mock
881
+ const mockResponse = integration.requestTemplates?.mock || {};
882
+ return this.createResponse(200, mockResponse);
883
+ }
884
+
885
+ getMatchingIntegrationResponse(responses, statusCode) {
886
+ // Busca response que corresponda ao status code
887
+ if (responses.has(statusCode.toString())) {
888
+ return responses.get(statusCode.toString());
889
+ }
890
+
891
+ // Busca default
892
+ if (responses.has('default')) {
893
+ return responses.get('default');
894
+ }
895
+
896
+ return null;
897
+ }
898
+
899
+ applyRequestTemplate(template, context) {
900
+ // Implementação simples de template
901
+ try {
902
+ return JSON.stringify(context);
903
+ } catch (error) {
904
+ return template;
905
+ }
906
+ }
907
+
908
+ applyResponseTemplate(response, body) {
909
+ const templates = response.responseTemplates;
910
+ if (templates && templates['application/json']) {
911
+ // Aplica template de resposta
912
+ try {
913
+ return JSON.parse(templates['application/json'].replace(/\$\{([^}]+)\}/g, (match, path) => {
914
+ return this.getNestedValue(body, path);
915
+ }));
916
+ } catch (error) {
917
+ return body;
918
+ }
919
+ }
920
+ return body;
921
+ }
922
+
923
+ resolveParameter(value, context) {
924
+ // Resolve parâmetros como method.request.header.X-Header
925
+ if (value.includes('method.request.')) {
926
+ const parts = value.split('.');
927
+ const type = parts[2]; // header, querystring, path
928
+ const name = parts[3];
929
+
930
+ switch(type) {
931
+ case 'header':
932
+ return context.headers[name];
933
+ case 'querystring':
934
+ return context.queryString[name];
935
+ case 'path':
936
+ return context.pathParams[name];
937
+ default:
938
+ return null;
939
+ }
940
+ }
941
+ return value;
942
+ }
943
+
944
+ getNestedValue(obj, path) {
945
+ return path.split('.').reduce((current, key) => current?.[key], obj);
946
+ }
947
+
948
+ extractApiKey(headers) {
949
+ return headers['x-api-key'] || headers['X-Api-Key'];
950
+ }
951
+
952
+ validateApiKey(apiKeyValue) {
953
+ if (!apiKeyValue) return false;
954
+
955
+ for (const key of this.apiKeys.values()) {
956
+ if (key.value === apiKeyValue && key.enabled) {
957
+ return true;
958
+ }
959
+ }
960
+ return false;
961
+ }
962
+
963
+ getUsagePlanForApi(apiId, stageName) {
964
+ for (const plan of this.usagePlans.values()) {
965
+ const apiStage = plan.apiStages.find(
966
+ as => as.apiId === apiId && as.stage === stageName
967
+ );
968
+ if (apiStage) {
969
+ return plan;
970
+ }
971
+ }
972
+ return null;
973
+ }
974
+
975
+ checkThrottle(usagePlan) {
976
+ // Implementação simplificada de throttling
977
+ const { rateLimit, burstLimit } = usagePlan.throttle;
978
+ // Aqui poderia implementar contagem de requests
979
+ return true;
980
+ }
981
+
982
+ createResponse(statusCode, body) {
983
+ return {
984
+ statusCode: statusCode,
985
+ headers: {
986
+ 'Content-Type': 'application/json',
987
+ 'Access-Control-Allow-Origin': '*'
988
+ },
989
+ body: typeof body === 'string' ? body : JSON.stringify(body)
990
+ };
991
+ }
992
+
993
+ // ============ HTTP API Operations ============
994
+
995
+ createHttpApi(params) {
996
+ const { name, description, protocolType, routeSelectionExpression, corsConfiguration } = params;
997
+
998
+ const apiId = `http_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
999
+ const api = {
1000
+ id: apiId,
1001
+ name: name,
1002
+ description: description || '',
1003
+ protocolType: protocolType || 'HTTP',
1004
+ routeSelectionExpression: routeSelectionExpression || '$request.method $request.path',
1005
+ corsConfiguration: corsConfiguration || {
1006
+ allowOrigins: ['*'],
1007
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
1008
+ allowHeaders: ['*'],
1009
+ maxAge: 300
1010
+ },
1011
+ routes: new Map(),
1012
+ integrations: new Map(),
1013
+ stages: new Map(),
1014
+ createdDate: new Date().toISOString()
1015
+ };
1016
+
1017
+ this.apis.set(apiId, api);
1018
+ this.persistAPIs();
1019
+
1020
+ logger.debug(`✅ HTTP API criada: ${name} (${apiId})`);
1021
+
1022
+ return {
1023
+ ApiId: apiId,
1024
+ Name: api.name,
1025
+ ProtocolType: api.protocolType
1026
+ };
1027
+ }
1028
+
1029
+ createRoute(params) {
1030
+ const { apiId, routeKey, authorizationType, target } = params;
1031
+ const api = this.apis.get(apiId);
1032
+
1033
+ if (!api) {
1034
+ throw new Error(`API ${apiId} not found`);
1035
+ }
1036
+
1037
+ const route = {
1038
+ routeKey: routeKey,
1039
+ authorizationType: authorizationType || 'NONE',
1040
+ target: target,
1041
+ createdAt: new Date().toISOString()
1042
+ };
1043
+
1044
+ api.routes.set(routeKey, route);
1045
+ this.persistAPIs();
1046
+
1047
+ return { route };
1048
+ }
1049
+
1050
+ // ============ Persistence ============
1051
+
1052
+ loadAPIs() {
1053
+ const saved = this.store.read('__apis__');
1054
+ if (saved) {
1055
+ for (const [id, data] of Object.entries(saved)) {
1056
+ // Reconstitui Maps
1057
+ data.resources = new Map(Object.entries(data.resources || {}));
1058
+ data.stages = new Map(Object.entries(data.stages || {}));
1059
+ data.deployments = new Map(Object.entries(data.deployments || {}));
1060
+ data.models = new Map(Object.entries(data.models || {}));
1061
+ data.authorizers = new Map(Object.entries(data.authorizers || {}));
1062
+ this.apis.set(id, data);
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
+ loadWebSocketAPIs() {
1068
+ const saved = this.store.read('__websocket_apis__');
1069
+ if (saved) {
1070
+ for (const [id, data] of Object.entries(saved)) {
1071
+ this.websocketApis.set(id, data);
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ loadDeployments() {
1077
+ const saved = this.store.read('__deployments__');
1078
+ if (saved) {
1079
+ for (const [id, data] of Object.entries(saved)) {
1080
+ this.deployments.set(id, data);
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+ loadStages() {
1086
+ const saved = this.store.read('__stages__');
1087
+ if (saved) {
1088
+ for (const [id, data] of Object.entries(saved)) {
1089
+ this.stages.set(id, data);
1090
+ }
1091
+ }
1092
+ }
1093
+
1094
+ loadResources() {
1095
+ const saved = this.store.read('__resources__');
1096
+ if (saved) {
1097
+ for (const [id, data] of Object.entries(saved)) {
1098
+ this.resources.set(id, data);
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ loadMethods() {
1104
+ const saved = this.store.read('__methods__');
1105
+ if (saved) {
1106
+ for (const [id, data] of Object.entries(saved)) {
1107
+ this.methods.set(id, data);
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ loadIntegrations() {
1113
+ const saved = this.store.read('__integrations__');
1114
+ if (saved) {
1115
+ for (const [id, data] of Object.entries(saved)) {
1116
+ this.integrations.set(id, data);
1117
+ }
1118
+ }
1119
+ }
1120
+
1121
+ loadModels() {
1122
+ const saved = this.store.read('__models__');
1123
+ if (saved) {
1124
+ for (const [id, data] of Object.entries(saved)) {
1125
+ this.models.set(id, data);
1126
+ }
1127
+ }
1128
+ }
1129
+
1130
+ loadUsagePlans() {
1131
+ const saved = this.store.read('__usage_plans__');
1132
+ if (saved) {
1133
+ for (const [id, data] of Object.entries(saved)) {
1134
+ this.usagePlans.set(id, data);
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ loadApiKeys() {
1140
+ const saved = this.store.read('__api_keys__');
1141
+ if (saved) {
1142
+ for (const [id, data] of Object.entries(saved)) {
1143
+ this.apiKeys.set(id, data);
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ loadDomainNames() {
1149
+ const saved = this.store.read('__domain_names__');
1150
+ if (saved) {
1151
+ for (const [id, data] of Object.entries(saved)) {
1152
+ this.domainNames.set(id, data);
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ persistAPIs() {
1158
+ const apisObj = {};
1159
+ for (const [id, api] of this.apis.entries()) {
1160
+ apisObj[id] = {
1161
+ ...api,
1162
+ resources: Object.fromEntries(api.resources),
1163
+ stages: Object.fromEntries(api.stages),
1164
+ deployments: Object.fromEntries(api.deployments),
1165
+ models: Object.fromEntries(api.models),
1166
+ authorizers: Object.fromEntries(api.authorizers)
1167
+ };
1168
+ }
1169
+ this.store.write('__apis__', apisObj);
1170
+ }
1171
+
1172
+ persistResources(apiId) {
1173
+ const api = this.apis.get(apiId);
1174
+ if (api) {
1175
+ this.persistAPIs();
1176
+ }
1177
+ }
1178
+
1179
+ persistMethods(apiId, resourceId) {
1180
+ this.persistResources(apiId);
1181
+ }
1182
+
1183
+ persistIntegrations(apiId, resourceId) {
1184
+ this.persistMethods(apiId, resourceId);
1185
+ }
1186
+
1187
+ persistStages(apiId) {
1188
+ this.persistAPIs();
1189
+ }
1190
+
1191
+ persistDeployments(apiId) {
1192
+ this.persistAPIs();
1193
+ }
1194
+
1195
+ persistApiKeys() {
1196
+ const keysObj = {};
1197
+ for (const [id, key] of this.apiKeys.entries()) {
1198
+ keysObj[id] = key;
1199
+ }
1200
+ this.store.write('__api_keys__', keysObj);
1201
+ }
1202
+
1203
+ persistUsagePlans() {
1204
+ const plansObj = {};
1205
+ for (const [id, plan] of this.usagePlans.entries()) {
1206
+ plansObj[id] = plan;
1207
+ }
1208
+ this.store.write('__usage_plans__', plansObj);
1209
+ }
1210
+
1211
+ async reset() {
1212
+ this.apis.clear();
1213
+ this.websocketApis.clear();
1214
+ this.deployments.clear();
1215
+ this.stages.clear();
1216
+ this.resources.clear();
1217
+ this.methods.clear();
1218
+ this.integrations.clear();
1219
+ this.models.clear();
1220
+ this.usagePlans.clear();
1221
+ this.apiKeys.clear();
1222
+ this.domainNames.clear();
1223
+
1224
+ this.persistAPIs();
1225
+ this.persistApiKeys();
1226
+ this.persistUsagePlans();
1227
+
1228
+ logger.debug('API Gateway: Todos os dados resetados');
1229
+ }
1230
+
1231
+ // ============ Stats ============
1232
+
1233
+ getAPIsCount() {
1234
+ return this.apis.size;
1235
+ }
1236
+
1237
+ getDeploymentsCount() {
1238
+ let count = 0;
1239
+ for (const api of this.apis.values()) {
1240
+ count += api.deployments.size;
1241
+ }
1242
+ return count;
1243
+ }
1244
+
1245
+ getStagesCount() {
1246
+ let count = 0;
1247
+ for (const api of this.apis.values()) {
1248
+ count += api.stages.size;
1249
+ }
1250
+ return count;
1251
+ }
1252
+
1253
+ getResourcesCount() {
1254
+ let count = 0;
1255
+ for (const api of this.apis.values()) {
1256
+ count += api.resources.size;
1257
+ }
1258
+ return count;
1259
+ }
1260
+ }
1261
+
1262
1262
  module.exports = APIGatewaySimulator;