@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39

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 (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. package/aws-config +0 -154
@@ -0,0 +1,262 @@
1
+ /**
2
+ * ALB Simulator - Application Load Balancer com persistência
3
+ */
4
+
5
+ const path = require('path');
6
+ const LoadBalancer = require('./models/loadBalancer');
7
+ const { TargetGroup } = require('./models/targetGroup');
8
+ const LocalStore = require('../../utils/local-store');
9
+ const logger = require('../../utils/logger');
10
+
11
+ class ALBSimulator {
12
+ constructor(config = {}) {
13
+ this.config = config;
14
+ this.loadBalancers = new Map();
15
+ this.targetGroups = new Map();
16
+
17
+ // Inicializar persistência
18
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
19
+ this.dataDir = path.join(dataDir, 'alb');
20
+ this.store = new LocalStore(this.dataDir);
21
+ }
22
+
23
+ async initialize() {
24
+ // 1. Carregar dados persistidos
25
+ await this.loadFromPersistence();
26
+
27
+ // 2. Pré-provisionar ALBs se configurado e não existirem
28
+ if (this.loadBalancers.size === 0 && this.config.loadBalancers && Array.isArray(this.config.loadBalancers)) {
29
+ logger.info('📚 Carregando ALBs da configuração inicial...');
30
+ for (const albConfig of this.config.loadBalancers) {
31
+ this.createLoadBalancer(albConfig.Name, 'internet-facing', albConfig);
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Carrega dados persistidos do storage
38
+ */
39
+ async loadFromPersistence() {
40
+ try {
41
+ // Carregar Load Balancers
42
+ const albsData = this.store.read('load-balancers');
43
+ if (Array.isArray(albsData)) {
44
+ logger.info(`📂 Carregando ${albsData.length} ALBs do storage...`);
45
+ for (const albData of albsData) {
46
+ const alb = new LoadBalancer(albData.LoadBalancerName, albData.Scheme, albData);
47
+ alb.LoadBalancerArn = albData.LoadBalancerArn;
48
+ alb.State = albData.State;
49
+ alb.Listeners = albData.Listeners || [];
50
+ this.loadBalancers.set(alb.LoadBalancerArn, alb);
51
+ }
52
+ }
53
+
54
+ // Carregar Target Groups
55
+ const tgsData = this.store.read('target-groups');
56
+ if (Array.isArray(tgsData)) {
57
+ logger.info(`📂 Carregando ${tgsData.length} Target Groups do storage...`);
58
+ for (const tgData of tgsData) {
59
+ const tg = new TargetGroup(tgData.TargetGroupName, tgData.Protocol, tgData.Port, tgData.VpcId);
60
+ tg.TargetGroupArn = tgData.TargetGroupArn;
61
+ tg.Targets = tgData.Targets || [];
62
+ tg.HealthCheckConfig = tgData.HealthCheckConfig || {};
63
+ this.targetGroups.set(tg.TargetGroupArn, tg);
64
+ }
65
+ }
66
+ } catch (error) {
67
+ logger.warn(`⚠️ Erro ao carregar dados persistidos ALB: ${error.message}`);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Persiste estado atual em storage
73
+ */
74
+ persistData() {
75
+ try {
76
+ this.store.write('load-balancers', Array.from(this.loadBalancers.values()).map(a => a.toJSON ? a.toJSON() : a));
77
+ this.store.write('target-groups', Array.from(this.targetGroups.values()).map(tg => tg.toJSON ? tg.toJSON() : tg));
78
+ } catch (error) {
79
+ logger.error(`Erro ao persistir dados ALB: ${error.message}`);
80
+ }
81
+ }
82
+
83
+ // ========== Load Balancer Operations ==========
84
+
85
+ createLoadBalancer(name, scheme = 'internet-facing', config = {}) {
86
+ const alb = new LoadBalancer(name, scheme, config);
87
+ this.loadBalancers.set(alb.LoadBalancerArn, alb);
88
+ logger.info(`✅ ALB criado: ${name} (${alb.LoadBalancerArn})`);
89
+ // ✅ Persistir mudanças
90
+ this.persistData();
91
+ return alb;
92
+ }
93
+
94
+ describeLoadBalancers(filters = {}) {
95
+ let albs = Array.from(this.loadBalancers.values());
96
+
97
+ if (filters.names && Array.isArray(filters.names)) {
98
+ albs = albs.filter(a => filters.names.includes(a.LoadBalancerName));
99
+ }
100
+
101
+ if (filters.arns && Array.isArray(filters.arns)) {
102
+ albs = albs.filter(a => filters.arns.includes(a.LoadBalancerArn));
103
+ }
104
+
105
+ return albs;
106
+ }
107
+
108
+ getLoadBalancer(albArn) {
109
+ const alb = this.loadBalancers.get(albArn);
110
+ if (!alb) {
111
+ throw new Error(`Load Balancer ${albArn} not found`);
112
+ }
113
+ return alb;
114
+ }
115
+
116
+ deleteLoadBalancer(albArn) {
117
+ // Deletar target groups associados
118
+ const alb = this.getLoadBalancer(albArn);
119
+ alb.TargetGroups.forEach(tg => {
120
+ this.targetGroups.delete(tg.TargetGroupArn);
121
+ });
122
+
123
+ this.loadBalancers.delete(albArn);
124
+ logger.info(`🗑️ ALB deletado: ${albArn}`);
125
+ // ✅ Persistir mudanças
126
+ this.persistData();
127
+ return true;
128
+ }
129
+
130
+ // ========== Target Group Operations ==========
131
+
132
+ createTargetGroup(name, protocol = 'HTTP', port = 80, vpcId = null, config = {}) {
133
+ const tg = new TargetGroup(name, protocol, port, vpcId, config);
134
+ this.targetGroups.set(tg.TargetGroupArn, tg);
135
+ logger.info(`✅ Target Group criado: ${name} (${tg.TargetGroupArn})`);
136
+ // ✅ Persistir mudanças
137
+ this.persistData();
138
+ return tg;
139
+ }
140
+
141
+ describeTargetGroups(filters = {}) {
142
+ let tgs = Array.from(this.targetGroups.values());
143
+
144
+ if (filters.names && Array.isArray(filters.names)) {
145
+ tgs = tgs.filter(t => filters.names.includes(t.TargetGroupName));
146
+ }
147
+
148
+ if (filters.arns && Array.isArray(filters.arns)) {
149
+ tgs = tgs.filter(t => filters.arns.includes(t.TargetGroupArn));
150
+ }
151
+
152
+ return tgs;
153
+ }
154
+
155
+ getTargetGroup(tgArn) {
156
+ const tg = this.targetGroups.get(tgArn);
157
+ if (!tg) {
158
+ throw new Error(`Target Group ${tgArn} not found`);
159
+ }
160
+ return tg;
161
+ }
162
+
163
+ deleteTargetGroup(tgArn) {
164
+ // Verificar se está em uso por ALB
165
+ const inUse = Array.from(this.loadBalancers.values()).some(alb =>
166
+ alb.TargetGroups.some(tg => tg.TargetGroupArn === tgArn)
167
+ );
168
+
169
+ if (inUse) {
170
+ throw new Error(`Target Group ${tgArn} still in use by load balancer`);
171
+ }
172
+
173
+ this.targetGroups.delete(tgArn);
174
+ logger.info(`🗑️ Target Group deletado: ${tgArn}`);
175
+ // ✅ Persistir mudanças
176
+ this.persistData();
177
+ return true;
178
+ }
179
+
180
+ // ========== Target Operations ==========
181
+
182
+ registerTarget(tgArn, targetId, port = null) {
183
+ const tg = this.getTargetGroup(tgArn);
184
+ const target = tg.registerTarget(targetId, port);
185
+ logger.info(`✅ Target registrado: ${targetId} em ${tg.TargetGroupName}`);
186
+ // ✅ Persistir mudanças
187
+ this.persistData();
188
+ return target;
189
+ }
190
+
191
+ deregisterTarget(tgArn, targetId, port = null) {
192
+ const tg = this.getTargetGroup(tgArn);
193
+ const target = tg.deregisterTarget(targetId, port);
194
+ logger.info(`🗑️ Target removido: ${targetId} de ${tg.TargetGroupName}`);
195
+ // ✅ Persistir mudanças
196
+ this.persistData();
197
+ return target;
198
+ }
199
+
200
+ describeTargetHealth(tgArn) {
201
+ const tg = this.getTargetGroup(tgArn);
202
+ return {
203
+ TargetGroupArn: tgArn,
204
+ HealthDescriptions: tg.Targets.map(t => ({
205
+ TargetId: t.TargetId,
206
+ Port: t.Port,
207
+ HealthState: t.HealthState,
208
+ HealthReason: t.HealthReason
209
+ }))
210
+ };
211
+ }
212
+
213
+ // ========== Listener Operations ==========
214
+
215
+ addListener(albArn, protocol = 'HTTP', port = 80, tgArn = null) {
216
+ const alb = this.getLoadBalancer(albArn);
217
+
218
+ if (tgArn) {
219
+ this.getTargetGroup(tgArn); // Validar que o TG existe
220
+ }
221
+
222
+ const listener = alb.addListener(protocol, port, tgArn);
223
+ logger.info(`✅ Listener adicionado: ${protocol}:${port} → ${tgArn}`);
224
+ // ✅ Persistir mudanças
225
+ this.persistData();
226
+ return listener;
227
+ }
228
+
229
+ // ========== Load Balancing Logic ==========
230
+
231
+ routeRequest(albArn, path = '/') {
232
+ const alb = this.getLoadBalancer(albArn);
233
+
234
+ // Se não há target groups, retornar erro
235
+ if (alb.TargetGroups.length === 0) {
236
+ return null;
237
+ }
238
+
239
+ // Usar o primeiro target group por padrão
240
+ const tg = alb.TargetGroups[0];
241
+ const nextTarget = tg.getNextTarget();
242
+
243
+ return nextTarget
244
+ ? {
245
+ TargetId: nextTarget.TargetId,
246
+ Port: nextTarget.Port,
247
+ TargetGroupArn: tg.TargetGroupArn
248
+ }
249
+ : null;
250
+ }
251
+
252
+ // ========== Utility ==========
253
+
254
+ getAllResources() {
255
+ return {
256
+ loadBalancers: Array.from(this.loadBalancers.values()),
257
+ targetGroups: Array.from(this.targetGroups.values())
258
+ };
259
+ }
260
+ }
261
+
262
+ module.exports = ALBSimulator;
@@ -38,6 +38,28 @@ class APIGatewayService {
38
38
  injectDependencies(server) {
39
39
  const ct = server.getService('cloudtrail');
40
40
  if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
41
+
42
+ const lambda = server.getService('lambda');
43
+ if (lambda) {
44
+ this.lambdaService = lambda;
45
+ if (this.simulator) this.simulator.setLambdaService(lambda);
46
+ if (this.server) this.server.lambdaService = lambda;
47
+ }
48
+ const sqs = server.getService('sqs');
49
+ if (sqs) {
50
+ if (this.simulator) this.simulator.setSqsService(sqs);
51
+ if (this.server) this.server.sqsService = sqs;
52
+ }
53
+ const sns = server.getService('sns');
54
+ if (sns) {
55
+ if (this.simulator) this.simulator.setSnsService(sns);
56
+ if (this.server) this.server.snsService = sns;
57
+ }
58
+ const eb = server.getService('eventbridge');
59
+ if (eb) {
60
+ if (this.simulator) this.simulator.setEventbridgeService(eb);
61
+ if (this.server) this.server.eventbridgeService = eb;
62
+ }
41
63
  }
42
64
 
43
65
  async start() {
@@ -1,3 +1,4 @@
1
+ const { setupServiceMiddleware } = require('../../middleware');
1
2
  /**
2
3
  * API Gateway Server - Servidor HTTP para API Gateway
3
4
  */
@@ -8,6 +9,8 @@ const logger = require('../../utils/logger');
8
9
 
9
10
  class APIGatewayServer {
10
11
  constructor(port, config) {
12
+ this.organizationsService = null;
13
+ this.stsService = null;
11
14
  this.port = port;
12
15
  this.config = config;
13
16
  this.app = express();
@@ -68,50 +71,159 @@ class APIGatewayServer {
68
71
  const cognitoAuthorizer = this._buildCognitoAuthorizer(api.authorizer);
69
72
 
70
73
  for (const endpoint of (api.endpoints || [])) {
71
- const { path, method, lambdaName, integrationType, authorizerRequired } = endpoint;
74
+ const { path, method, lambdaName, integrationType, authorizerRequired, queueArn, queueUrl, eventBusName, topicArn } = endpoint;
72
75
  if (!path || !method) continue;
73
76
 
74
77
  const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
75
78
  const httpMethod = method.toLowerCase();
76
79
 
77
- logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
80
+ const intType = (integrationType || '').toLowerCase();
81
+ logger.debug(`📡 Registrando rota: ${method} ${path} -> ${intType || 'lambda'} (${lambdaName || queueArn || topicArn || eventBusName || 'n/a'})`);
78
82
 
79
- const handler = async (req, res) => {
80
- try {
81
- const lambdaService = this.lambdaService;
82
- if (!lambdaService) {
83
- return res.status(500).json({ error: 'Lambda service not available' });
83
+ let handler;
84
+ if (intType === 'sqs' || queueArn || queueUrl) {
85
+ handler = async (req, res) => {
86
+ try {
87
+ const sqsService = this.sqsService;
88
+ if (!sqsService) {
89
+ return res.status(500).json({ error: 'SQS service not available' });
90
+ }
91
+ const sqsSim = sqsService.simulator;
92
+ let resolvedQueueUrl = queueUrl;
93
+ if (!resolvedQueueUrl && queueArn) {
94
+ const nameMatch = String(queueArn).match(/:([^:]+)$/);
95
+ if (nameMatch) {
96
+ const q = sqsSim.queues.get(nameMatch[1]);
97
+ resolvedQueueUrl = q && q.url;
98
+ }
99
+ }
100
+ const bodyContent = req.body ? (typeof req.body === 'string' ? req.body : JSON.stringify(req.body)) : '';
101
+ const messageBody = req.body?.MessageBody || bodyContent;
102
+ if (!resolvedQueueUrl) {
103
+ return res.status(400).json({ __type: 'MissingParameter', message: 'QueueUrl or queueArn must be configured' });
104
+ }
105
+ const result = sqsSim.sendMessage({
106
+ QueueUrl: resolvedQueueUrl,
107
+ MessageBody: messageBody,
108
+ DelaySeconds: req.body?.DelaySeconds,
109
+ MessageAttributes: req.body?.MessageAttributes
110
+ });
111
+ if (result.error) return res.status(result.status || 400).json(result.error);
112
+ res.status(200).json({
113
+ SendMessageResponse: {
114
+ SendMessageResult: { MessageId: result.messageId, MD5OfMessageBody: result.md5 },
115
+ ResponseMetadata: { RequestId: Math.random().toString(36).slice(2) }
116
+ }
117
+ });
118
+ } catch (err) {
119
+ logger.error('SQS send error:', err);
120
+ res.status(500).json({ error: err.message });
121
+ }
122
+ };
123
+ } else if (intType === 'eventbridge' || intType === 'events' || eventBusName) {
124
+ handler = async (req, res) => {
125
+ try {
126
+ const ebService = this.eventbridgeService;
127
+ if (!ebService) {
128
+ return res.status(500).json({ error: 'EventBridge service not available' });
129
+ }
130
+ const ebSim = ebService.simulator;
131
+ const entries = req.body?.Entries || [{
132
+ Source: req.body?.Source || 'aws.apigateway',
133
+ DetailType: req.body?.DetailType || 'API Gateway Request',
134
+ Detail: JSON.stringify(req.body?.Detail || req.body || {}),
135
+ EventBusName: eventBusName || req.body?.EventBusName || 'default',
136
+ Time: new Date().toISOString(),
137
+ Resources: []
138
+ }];
139
+ const norm = entries.map(e => ({
140
+ Time: e.Time || new Date().toISOString(),
141
+ Source: e.Source || 'aws.apigateway',
142
+ Resources: e.Resources || [],
143
+ DetailType: e.DetailType || 'API Gateway Request',
144
+ Detail: typeof e.Detail === 'string' ? e.Detail : JSON.stringify(e.Detail || {}),
145
+ EventBusName: eventBusName || e.EventBusName || 'default',
146
+ TraceHeader: e.TraceHeader
147
+ }));
148
+ const result = await ebSim.putEvents({ Entries: norm });
149
+ res.status(200).json({
150
+ Entries: (result.Entries || []).map(e => ({
151
+ EventId: e.EventId || Math.random().toString(36).slice(2),
152
+ ErrorCode: e.ErrorCode,
153
+ ErrorMessage: e.ErrorMessage
154
+ })),
155
+ FailedEntryCount: result.FailedEntryCount || 0
156
+ });
157
+ } catch (err) {
158
+ logger.error('EventBridge putEvents error:', err);
159
+ res.status(500).json({ error: err.message });
84
160
  }
161
+ };
162
+ } else if (intType === 'sns' || topicArn) {
163
+ handler = async (req, res) => {
164
+ try {
165
+ const snsService = this.snsService;
166
+ if (!snsService) {
167
+ return res.status(500).json({ error: 'SNS service not available' });
168
+ }
169
+ const snsSim = snsService.simulator;
170
+ const message = req.body?.Message || (req.body ? (typeof req.body === 'string' ? req.body : JSON.stringify(req.body)) : '');
171
+ const result = await snsSim.publish({
172
+ TopicArn: topicArn || req.body?.TopicArn,
173
+ TargetArn: req.body?.TargetArn,
174
+ Message: message,
175
+ Subject: req.body?.Subject,
176
+ MessageAttributes: req.body?.MessageAttributes
177
+ });
178
+ res.status(200).json({
179
+ PublishResponse: {
180
+ PublishResult: { MessageId: result.messageId || Math.random().toString(36).slice(2) },
181
+ ResponseMetadata: { RequestId: Math.random().toString(36).slice(2) }
182
+ }
183
+ });
184
+ } catch (err) {
185
+ logger.error('SNS publish error:', err);
186
+ res.status(500).json({ error: err.message });
187
+ }
188
+ };
189
+ } else {
190
+ handler = async (req, res) => {
191
+ try {
192
+ const lambdaService = this.lambdaService;
193
+ if (!lambdaService) {
194
+ return res.status(500).json({ error: 'Lambda service not available' });
195
+ }
85
196
 
86
- const event = {
87
- httpMethod: req.method,
88
- path: req.path,
89
- headers: req.headers,
90
- queryStringParameters: Object.keys(req.query).length ? req.query : null,
91
- pathParameters: Object.keys(req.params).length ? req.params : null,
92
- body: req.body ? JSON.stringify(req.body) : null,
93
- isBase64Encoded: false,
94
- requestContext: {
197
+ const event = {
198
+ httpMethod: req.method,
95
199
  path: req.path,
96
- stage: 'local',
97
- requestId: Math.random().toString(36).substring(7),
98
- identity: { sourceIp: req.ip },
99
- authorizer: req.cognitoAuthorizer || null
100
- }
101
- };
102
-
103
- const result = await lambdaService.simulator.invoke(lambdaName, event);
104
- const payload = result.Payload || {};
105
- const statusCode = payload.statusCode || 200;
106
- const headers = payload.headers || { 'Content-Type': 'application/json' };
107
- const body = payload.body;
108
-
109
- res.status(statusCode).set(headers).send(body);
110
- } catch (err) {
111
- logger.error(`Lambda invoke error (${lambdaName}):`, err);
112
- res.status(500).json({ error: err.message });
113
- }
114
- };
200
+ headers: req.headers,
201
+ queryStringParameters: Object.keys(req.query).length ? req.query : null,
202
+ pathParameters: Object.keys(req.params).length ? req.params : null,
203
+ body: req.body ? JSON.stringify(req.body) : null,
204
+ isBase64Encoded: false,
205
+ requestContext: {
206
+ path: req.path,
207
+ stage: 'local',
208
+ requestId: Math.random().toString(36).substring(7),
209
+ identity: { sourceIp: req.ip },
210
+ authorizer: req.cognitoAuthorizer || null
211
+ }
212
+ };
213
+
214
+ const result = await lambdaService.simulator.invoke(lambdaName, event);
215
+ const payload = result.Payload || {};
216
+ const statusCode = payload.statusCode || 200;
217
+ const headers = payload.headers || { 'Content-Type': 'application/json' };
218
+ const body = payload.body;
219
+
220
+ res.status(statusCode).set(headers).send(body);
221
+ } catch (err) {
222
+ logger.error(`Lambda invoke error (${lambdaName}):`, err);
223
+ res.status(500).json({ error: err.message });
224
+ }
225
+ };
226
+ }
115
227
 
116
228
  const middlewares = [];
117
229
  if (authorizerRequired && cognitoAuthorizer) {
@@ -434,6 +546,106 @@ class APIGatewayServer {
434
546
  }
435
547
  });
436
548
 
549
+ // Models
550
+ this.app.post('/restapis/:apiId/models', (req, res) => {
551
+ try {
552
+ const result = this.simulator.createModel({ ...req.body, restApiId: req.params.apiId });
553
+ res.json(result);
554
+ } catch (error) {
555
+ res.status(400).json({ error: error.message });
556
+ }
557
+ });
558
+
559
+ this.app.get('/restapis/:apiId/models', (req, res) => {
560
+ try {
561
+ const result = this.simulator.getModels({ restApiId: req.params.apiId });
562
+ res.json(result);
563
+ } catch (error) {
564
+ res.status(404).json({ error: error.message });
565
+ }
566
+ });
567
+
568
+ this.app.get('/restapis/:apiId/models/:modelName', (req, res) => {
569
+ try {
570
+ const result = this.simulator.getModel({
571
+ restApiId: req.params.apiId,
572
+ modelName: req.params.modelName,
573
+ flatten: req.query.flatten
574
+ });
575
+ res.json(result);
576
+ } catch (error) {
577
+ res.status(404).json({ error: error.message });
578
+ }
579
+ });
580
+
581
+ this.app.delete('/restapis/:apiId/models/:modelName', (req, res) => {
582
+ try {
583
+ const result = this.simulator.deleteModel({
584
+ restApiId: req.params.apiId,
585
+ modelName: req.params.modelName
586
+ });
587
+ res.json(result);
588
+ } catch (error) {
589
+ res.status(404).json({ error: error.message });
590
+ }
591
+ });
592
+
593
+ // Request Validators
594
+ this.app.post('/restapis/:apiId/requestvalidators', (req, res) => {
595
+ try {
596
+ const result = this.simulator.createRequestValidator({ ...req.body, restApiId: req.params.apiId });
597
+ res.json(result);
598
+ } catch (error) {
599
+ res.status(400).json({ error: error.message });
600
+ }
601
+ });
602
+
603
+ this.app.get('/restapis/:apiId/requestvalidators', (req, res) => {
604
+ try {
605
+ const result = this.simulator.getRequestValidators({ restApiId: req.params.apiId });
606
+ res.json(result);
607
+ } catch (error) {
608
+ res.status(404).json({ error: error.message });
609
+ }
610
+ });
611
+
612
+ this.app.get('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
613
+ try {
614
+ const result = this.simulator.getRequestValidator({
615
+ restApiId: req.params.apiId,
616
+ requestValidatorId: req.params.requestValidatorId
617
+ });
618
+ res.json(result);
619
+ } catch (error) {
620
+ res.status(404).json({ error: error.message });
621
+ }
622
+ });
623
+
624
+ this.app.patch('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
625
+ try {
626
+ const result = this.simulator.updateRequestValidator({
627
+ ...req.body,
628
+ restApiId: req.params.apiId,
629
+ requestValidatorId: req.params.requestValidatorId
630
+ });
631
+ res.json(result);
632
+ } catch (error) {
633
+ res.status(400).json({ error: error.message });
634
+ }
635
+ });
636
+
637
+ this.app.delete('/restapis/:apiId/requestvalidators/:requestValidatorId', (req, res) => {
638
+ try {
639
+ const result = this.simulator.deleteRequestValidator({
640
+ restApiId: req.params.apiId,
641
+ requestValidatorId: req.params.requestValidatorId
642
+ });
643
+ res.json(result);
644
+ } catch (error) {
645
+ res.status(404).json({ error: error.message });
646
+ }
647
+ });
648
+
437
649
  // HTTP APIs
438
650
  this.app.post('/httpapis', (req, res) => {
439
651
  try {