@gugananuvem/aws-local-simulator 1.0.11 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -72
- package/package.json +12 -2
- package/src/config/config-loader.js +2 -0
- package/src/config/default-config.js +3 -0
- package/src/index.js +18 -2
- package/src/server.js +37 -31
- package/src/services/apigateway/index.js +10 -3
- package/src/services/apigateway/server.js +73 -0
- package/src/services/apigateway/simulator.js +13 -3
- package/src/services/athena/index.js +75 -0
- package/src/services/athena/server.js +101 -0
- package/src/services/athena/simulador.js +998 -0
- package/src/services/athena/simulator.js +346 -0
- package/src/services/cloudformation/index.js +106 -0
- package/src/services/cloudformation/server.js +417 -0
- package/src/services/cloudformation/simulador.js +1045 -0
- package/src/services/cloudtrail/index.js +84 -0
- package/src/services/cloudtrail/server.js +235 -0
- package/src/services/cloudtrail/simulador.js +719 -0
- package/src/services/cloudwatch/index.js +84 -0
- package/src/services/cloudwatch/server.js +366 -0
- package/src/services/cloudwatch/simulador.js +1173 -0
- package/src/services/cognito/index.js +5 -0
- package/src/services/cognito/server.js +54 -3
- package/src/services/cognito/simulator.js +273 -2
- package/src/services/config/index.js +96 -0
- package/src/services/config/server.js +215 -0
- package/src/services/config/simulador.js +1260 -0
- package/src/services/dynamodb/index.js +7 -3
- package/src/services/dynamodb/server.js +4 -2
- package/src/services/dynamodb/simulator.js +39 -29
- package/src/services/eventbridge/index.js +55 -51
- package/src/services/eventbridge/server.js +209 -0
- package/src/services/eventbridge/simulator.js +684 -0
- package/src/services/index.js +30 -4
- package/src/services/kms/index.js +75 -0
- package/src/services/kms/server.js +67 -0
- package/src/services/kms/simulator.js +324 -0
- package/src/services/lambda/handler-loader.js +13 -2
- package/src/services/lambda/index.js +7 -1
- package/src/services/lambda/server.js +32 -39
- package/src/services/lambda/simulator.js +78 -181
- package/src/services/parameter-store/index.js +80 -0
- package/src/services/parameter-store/server.js +50 -0
- package/src/services/parameter-store/simulator.js +201 -0
- package/src/services/s3/index.js +7 -3
- package/src/services/s3/server.js +20 -13
- package/src/services/s3/simulator.js +163 -407
- package/src/services/secret-manager/index.js +80 -0
- package/src/services/secret-manager/server.js +50 -0
- package/src/services/secret-manager/simulator.js +171 -0
- package/src/services/sns/index.js +55 -42
- package/src/services/sns/server.js +580 -0
- package/src/services/sns/simulator.js +1482 -0
- package/src/services/sqs/index.js +2 -4
- package/src/services/sqs/server.js +92 -18
- package/src/services/sqs/simulator.js +79 -298
- package/src/services/sts/index.js +37 -0
- package/src/services/sts/server.js +142 -0
- package/src/services/sts/simulator.js +69 -0
- package/src/services/xray/index.js +83 -0
- package/src/services/xray/server.js +308 -0
- package/src/services/xray/simulador.js +994 -0
- package/src/utils/cloudtrail-audit.js +129 -0
- package/src/utils/local-store.js +18 -2
package/src/server.js
CHANGED
|
@@ -12,11 +12,21 @@ const DynamoDBService = require("./services/dynamodb");
|
|
|
12
12
|
const S3Service = require("./services/s3");
|
|
13
13
|
const SQSService = require("./services/sqs");
|
|
14
14
|
const LambdaService = require("./services/lambda");
|
|
15
|
-
const SNSService = require("./services/sns");
|
|
16
|
-
const EventBridgeService = require("./services/eventbridge");
|
|
15
|
+
const { SNSService } = require("./services/sns");
|
|
16
|
+
const { EventBridgeService } = require("./services/eventbridge");
|
|
17
17
|
const CognitoService = require("./services/cognito");
|
|
18
18
|
const APIGatewayService = require("./services/apigateway");
|
|
19
19
|
const ECSService = require("./services/ecs");
|
|
20
|
+
const STSService = require("./services/sts");
|
|
21
|
+
const { CloudWatchService } = require("./services/cloudwatch");
|
|
22
|
+
const CloudTrailService = require("./services/cloudtrail");
|
|
23
|
+
const { KMSService } = require("./services/kms");
|
|
24
|
+
const CloudFormationService = require("./services/cloudformation");
|
|
25
|
+
const { XRayService } = require("./services/xray");
|
|
26
|
+
const { SecretManagerService } = require("./services/secret-manager");
|
|
27
|
+
const { ParameterStoreService } = require("./services/parameter-store");
|
|
28
|
+
const { ConfigService } = require("./services/config");
|
|
29
|
+
const { AthenaService } = require("./services/athena");
|
|
20
30
|
|
|
21
31
|
class Server {
|
|
22
32
|
constructor(config) {
|
|
@@ -63,23 +73,31 @@ class Server {
|
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
async initializeServices() {
|
|
66
|
-
// Ordem de inicialização importante
|
|
67
76
|
const serviceOrder = [
|
|
68
|
-
{ name: "
|
|
69
|
-
{ name: "
|
|
70
|
-
{ name: "
|
|
71
|
-
{ name: "
|
|
72
|
-
{ name: "
|
|
73
|
-
{ name: "
|
|
74
|
-
{ name: "
|
|
75
|
-
{ name: "
|
|
76
|
-
{ name: "
|
|
77
|
+
{ name: "sts", class: STSService, depends: [] },
|
|
78
|
+
{ name: "lambda", class: LambdaService, depends: [] },
|
|
79
|
+
{ name: "dynamodb", class: DynamoDBService, depends: [] },
|
|
80
|
+
{ name: "s3", class: S3Service, depends: [] },
|
|
81
|
+
{ name: "sqs", class: SQSService, depends: ["lambda"] },
|
|
82
|
+
{ name: "sns", class: SNSService, depends: [] },
|
|
83
|
+
{ name: "eventbridge", class: EventBridgeService, depends: [] },
|
|
84
|
+
{ name: "cognito", class: CognitoService, depends: [] },
|
|
85
|
+
{ name: "ecs", class: ECSService, depends: [] },
|
|
86
|
+
{ name: "apigateway", class: APIGatewayService, depends: ["lambda"] },
|
|
87
|
+
{ name: "kms", class: KMSService, depends: [] },
|
|
88
|
+
{ name: "cloudwatch", class: CloudWatchService, depends: [] },
|
|
89
|
+
{ name: "cloudtrail", class: CloudTrailService, depends: [] },
|
|
90
|
+
{ name: "cloudformation", class: CloudFormationService, depends: [] },
|
|
91
|
+
{ name: "xray", class: XRayService, depends: [] },
|
|
92
|
+
{ name: "secret-manager", class: SecretManagerService, depends: [] },
|
|
93
|
+
{ name: "parameter-store",class: ParameterStoreService, depends: [] },
|
|
94
|
+
{ name: "config", class: ConfigService, depends: [] },
|
|
95
|
+
{ name: "athena", class: AthenaService, depends: [] },
|
|
77
96
|
];
|
|
78
97
|
|
|
79
98
|
for (const serviceDef of serviceOrder) {
|
|
80
99
|
if (this.config.services[serviceDef.name]) {
|
|
81
100
|
try {
|
|
82
|
-
// Resolve dependências
|
|
83
101
|
const dependencies = {};
|
|
84
102
|
for (const dep of serviceDef.depends) {
|
|
85
103
|
dependencies[dep] = this.servicesMap.get(dep);
|
|
@@ -94,29 +112,17 @@ class Server {
|
|
|
94
112
|
logger.success(`✅ ${serviceDef.name.toUpperCase()} Service inicializado`);
|
|
95
113
|
} catch (error) {
|
|
96
114
|
logger.error(`❌ Erro ao inicializar ${serviceDef.name}:`, error);
|
|
97
|
-
if (serviceDef.name === "lambda")
|
|
98
|
-
throw error; // Lambda é essencial
|
|
99
|
-
}
|
|
115
|
+
if (serviceDef.name === "lambda") throw error;
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
}
|
|
103
119
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.services.push(cognitoService);
|
|
110
|
-
this.servicesMap.set("cognito", cognitoService);
|
|
111
|
-
logger.success("✅ Cognito Service inicializado");
|
|
120
|
+
// Injeta dependências cross-service nos serviços que suportam
|
|
121
|
+
for (const service of this.services) {
|
|
122
|
+
if (typeof service.injectDependencies === "function") {
|
|
123
|
+
service.injectDependencies(this);
|
|
124
|
+
}
|
|
112
125
|
}
|
|
113
|
-
if (this.config.services.apigateway) {
|
|
114
|
-
const apigatewayService = new APIGatewayService(this.config);
|
|
115
|
-
await apigatewayService.initialize();
|
|
116
|
-
this.services.push(apigatewayService);
|
|
117
|
-
this.servicesMap.set("apigateway", apigatewayService);
|
|
118
|
-
logger.success("✅ API Gateway Service inicializado");
|
|
119
|
-
}*/
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
async startServices() {
|
|
@@ -7,30 +7,37 @@ const APIGatewayServer = require('./server');
|
|
|
7
7
|
const APIGatewaySimulator = require('./simulator');
|
|
8
8
|
|
|
9
9
|
class APIGatewayService {
|
|
10
|
-
constructor(config) {
|
|
10
|
+
constructor(config, dependencies = {}) {
|
|
11
11
|
this.config = config;
|
|
12
12
|
this.name = 'apigateway';
|
|
13
13
|
this.port = config.ports.apigateway || 4567;
|
|
14
14
|
this.server = null;
|
|
15
15
|
this.simulator = null;
|
|
16
16
|
this.isRunning = false;
|
|
17
|
+
this.lambdaService = dependencies.lambda || null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
async initialize() {
|
|
20
21
|
const logger = require('../../utils/logger');
|
|
21
22
|
logger.debug(`Inicializando API Gateway Service na porta ${this.port}...`);
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
this.simulator = new APIGatewaySimulator(this.config);
|
|
24
25
|
await this.simulator.initialize();
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
this.server = new APIGatewayServer(this.port, this.config);
|
|
27
28
|
this.server.simulator = this.simulator;
|
|
29
|
+
this.server.lambdaService = this.lambdaService;
|
|
28
30
|
|
|
29
31
|
await this.server.initialize();
|
|
30
32
|
|
|
31
33
|
logger.debug('API Gateway Service inicializado');
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
injectDependencies(server) {
|
|
37
|
+
const ct = server.getService('cloudtrail');
|
|
38
|
+
if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
async start() {
|
|
35
42
|
if (this.isRunning) return;
|
|
36
43
|
await this.server.start();
|
|
@@ -19,6 +19,26 @@ class APIGatewayServer {
|
|
|
19
19
|
setupMiddlewares() {
|
|
20
20
|
this.app.use(express.json({ limit: '10mb' }));
|
|
21
21
|
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
22
|
+
// Parse bodies with AWS content types (e.g. application/x-amz-json-1.1)
|
|
23
|
+
this.app.use((req, res, next) => {
|
|
24
|
+
const ct = req.headers['content-type'] || '';
|
|
25
|
+
if ((req.body && JSON.stringify(req.body) == "{}") && ct.includes('application/x-amz-json')) {
|
|
26
|
+
let data = "";
|
|
27
|
+
req.on("data", (chunk) => {
|
|
28
|
+
data += chunk;
|
|
29
|
+
});
|
|
30
|
+
req.on("end", () => {
|
|
31
|
+
try {
|
|
32
|
+
req.body = JSON.parse(data);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
req.body = {};
|
|
35
|
+
}
|
|
36
|
+
next();
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
22
42
|
this.app.use(cors());
|
|
23
43
|
|
|
24
44
|
if (logger.currentLogLevel === 'verboso') {
|
|
@@ -35,10 +55,63 @@ class APIGatewayServer {
|
|
|
35
55
|
|
|
36
56
|
async initialize() {
|
|
37
57
|
this.setupRoutes();
|
|
58
|
+
this.setupConfigRoutes();
|
|
38
59
|
this.setupProxyRoutes();
|
|
39
60
|
logger.debug('API Gateway Server inicializado');
|
|
40
61
|
}
|
|
41
62
|
|
|
63
|
+
setupConfigRoutes() {
|
|
64
|
+
// Register routes from aws-local-simulator.json config directly
|
|
65
|
+
const apis = this.config.apigateway?.apis || [];
|
|
66
|
+
for (const api of apis) {
|
|
67
|
+
for (const endpoint of (api.endpoints || [])) {
|
|
68
|
+
const { path, method, lambdaName, integrationType } = endpoint;
|
|
69
|
+
if (!path || !method) continue;
|
|
70
|
+
|
|
71
|
+
const expressPath = path.replace(/\{([^}]+)\}/g, ':$1');
|
|
72
|
+
const httpMethod = method.toLowerCase();
|
|
73
|
+
|
|
74
|
+
logger.debug(`📡 Registrando rota: ${method} ${path} -> ${lambdaName}`);
|
|
75
|
+
|
|
76
|
+
this.app[httpMethod](expressPath, async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const lambdaService = this.lambdaService;
|
|
79
|
+
if (!lambdaService) {
|
|
80
|
+
return res.status(500).json({ error: 'Lambda service not available' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const event = {
|
|
84
|
+
httpMethod: req.method,
|
|
85
|
+
path: req.path,
|
|
86
|
+
headers: req.headers,
|
|
87
|
+
queryStringParameters: Object.keys(req.query).length ? req.query : null,
|
|
88
|
+
pathParameters: Object.keys(req.params).length ? req.params : null,
|
|
89
|
+
body: req.body ? JSON.stringify(req.body) : null,
|
|
90
|
+
isBase64Encoded: false,
|
|
91
|
+
requestContext: {
|
|
92
|
+
path: req.path,
|
|
93
|
+
stage: 'local',
|
|
94
|
+
requestId: Math.random().toString(36).substring(7),
|
|
95
|
+
identity: { sourceIp: req.ip }
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = await lambdaService.simulator.invoke(lambdaName, event);
|
|
100
|
+
const payload = result.Payload || {};
|
|
101
|
+
const statusCode = payload.statusCode || 200;
|
|
102
|
+
const headers = payload.headers || { 'Content-Type': 'application/json' };
|
|
103
|
+
const body = payload.body;
|
|
104
|
+
|
|
105
|
+
res.status(statusCode).set(headers).send(body);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
logger.error(`Lambda invoke error (${lambdaName}):`, err);
|
|
108
|
+
res.status(500).json({ error: err.message });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
42
115
|
setupRoutes() {
|
|
43
116
|
// Health check
|
|
44
117
|
this.app.get('/health', (req, res) => {
|
|
@@ -9,14 +9,15 @@ const logger = require('../../utils/logger');
|
|
|
9
9
|
const LocalStore = require('../../utils/local-store');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const { URLPattern } = require('urlpattern-polyfill');
|
|
12
|
+
const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
|
|
12
13
|
|
|
13
14
|
class APIGatewaySimulator {
|
|
14
15
|
constructor(config) {
|
|
15
16
|
this.config = config;
|
|
16
17
|
this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'apigateway');
|
|
17
18
|
this.store = new LocalStore(this.dataDir);
|
|
18
|
-
this.apis = new Map();
|
|
19
|
-
this.websocketApis = new Map();
|
|
19
|
+
this.apis = new Map();
|
|
20
|
+
this.websocketApis = new Map();
|
|
20
21
|
this.deployments = new Map();
|
|
21
22
|
this.stages = new Map();
|
|
22
23
|
this.resources = new Map();
|
|
@@ -26,6 +27,7 @@ class APIGatewaySimulator {
|
|
|
26
27
|
this.usagePlans = new Map();
|
|
27
28
|
this.apiKeys = new Map();
|
|
28
29
|
this.domainNames = new Map();
|
|
30
|
+
this.audit = new CloudTrailAudit('execute-api.amazonaws.com');
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
async initialize() {
|
|
@@ -734,7 +736,15 @@ class APIGatewaySimulator {
|
|
|
734
736
|
response.headers = { ...response.headers, ...integrationResponse.responseParameters };
|
|
735
737
|
response.body = this.applyResponseTemplate(integrationResponse, response.body);
|
|
736
738
|
}
|
|
737
|
-
|
|
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
|
+
|
|
738
748
|
return response;
|
|
739
749
|
}
|
|
740
750
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { AthenaSimulator } = require('./simulator');
|
|
6
|
+
const { createAthenaServer } = require('./server');
|
|
7
|
+
const LocalStore = require('../../utils/local-store');
|
|
8
|
+
|
|
9
|
+
class AthenaService {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.logger = require('../../utils/logger');
|
|
13
|
+
this.name = 'athena';
|
|
14
|
+
this.port = config?.ports?.athena || 4599;
|
|
15
|
+
this.store = null;
|
|
16
|
+
this.simulator = null;
|
|
17
|
+
this._server = null;
|
|
18
|
+
this.isRunning = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async initialize() {
|
|
22
|
+
this.logger.debug(`Inicializando Athena Service na porta ${this.port}...`);
|
|
23
|
+
const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR;
|
|
24
|
+
this.store = new LocalStore(path.join(dataDir, 'athena'));
|
|
25
|
+
this.simulator = new AthenaSimulator(this.config, this.store, this.logger);
|
|
26
|
+
await this.simulator.initialize();
|
|
27
|
+
this.app = createAthenaServer(this.simulator, this.logger);
|
|
28
|
+
this.logger.debug('Athena Service inicializado');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
injectDependencies(server) {
|
|
32
|
+
const ct = server.getService('cloudtrail');
|
|
33
|
+
if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async start() {
|
|
37
|
+
if (this.isRunning) return;
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
this._server = http.createServer(this.app);
|
|
40
|
+
this._server.listen(this.port, () => {
|
|
41
|
+
this.isRunning = true;
|
|
42
|
+
this.logger.info(`🔍 Athena rodando em http://localhost:${this.port}`);
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
this._server.on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async stop() {
|
|
50
|
+
if (!this.isRunning || !this._server) return;
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
this._server.close(() => {
|
|
53
|
+
this.isRunning = false;
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async reset() {
|
|
60
|
+
await this.simulator.reset();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getStatus() {
|
|
64
|
+
return {
|
|
65
|
+
running: this.isRunning,
|
|
66
|
+
port: this.port,
|
|
67
|
+
endpoint: `http://localhost:${this.port}`,
|
|
68
|
+
...this.simulator?.getStats(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getSimulator() { return this.simulator; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { AthenaService };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Athena Server - protocolo JSON via x-amz-target
|
|
8
|
+
*/
|
|
9
|
+
function createAthenaServer(simulator, logger) {
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
app.use(cors());
|
|
13
|
+
app.use(express.json({ limit: '10mb', type: () => true }));
|
|
14
|
+
|
|
15
|
+
app.use((req, _res, next) => {
|
|
16
|
+
const target = req.headers['x-amz-target'] || '';
|
|
17
|
+
if (target) logger.debug(`[Athena] ${req.method} ${req.path} target=${target}`);
|
|
18
|
+
next();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// ── Rota principal ───────────────────────────────────────────────
|
|
22
|
+
app.post('/', async (req, res) => {
|
|
23
|
+
const target = (req.headers['x-amz-target'] || '').replace('AmazonAthena.', '');
|
|
24
|
+
const body = req.body || {};
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
let result;
|
|
28
|
+
|
|
29
|
+
switch (target) {
|
|
30
|
+
case 'StartQueryExecution':
|
|
31
|
+
result = await simulator.startQueryExecution(body);
|
|
32
|
+
break;
|
|
33
|
+
case 'GetQueryExecution':
|
|
34
|
+
result = simulator.getQueryExecution(body);
|
|
35
|
+
break;
|
|
36
|
+
case 'GetQueryResults':
|
|
37
|
+
result = simulator.getQueryResults(body);
|
|
38
|
+
break;
|
|
39
|
+
case 'StopQueryExecution':
|
|
40
|
+
result = await simulator.stopQueryExecution(body);
|
|
41
|
+
break;
|
|
42
|
+
case 'ListQueryExecutions':
|
|
43
|
+
result = simulator.listQueryExecutions(body);
|
|
44
|
+
break;
|
|
45
|
+
case 'CreateNamedQuery':
|
|
46
|
+
result = await simulator.createNamedQuery(body);
|
|
47
|
+
break;
|
|
48
|
+
case 'GetNamedQuery':
|
|
49
|
+
result = simulator.getNamedQuery(body);
|
|
50
|
+
break;
|
|
51
|
+
case 'ListNamedQueries':
|
|
52
|
+
result = simulator.listNamedQueries(body);
|
|
53
|
+
break;
|
|
54
|
+
case 'DeleteNamedQuery':
|
|
55
|
+
result = await simulator.deleteNamedQuery(body);
|
|
56
|
+
break;
|
|
57
|
+
case 'CreateWorkGroup':
|
|
58
|
+
result = await simulator.createWorkGroup(body);
|
|
59
|
+
break;
|
|
60
|
+
case 'GetWorkGroup':
|
|
61
|
+
result = simulator.getWorkGroup(body);
|
|
62
|
+
break;
|
|
63
|
+
case 'ListWorkGroups':
|
|
64
|
+
result = simulator.listWorkGroups(body);
|
|
65
|
+
break;
|
|
66
|
+
case 'DeleteWorkGroup':
|
|
67
|
+
result = await simulator.deleteWorkGroup(body);
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
return res.status(400).json({ __type: 'InvalidRequestException', message: `Unknown action: ${target}` });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
res.json(result || {});
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.error(`[Athena] Error in ${target}: ${err.message}`);
|
|
76
|
+
res.status(err.statusCode || 500).json({ __type: err.code || 'InternalServerError', message: err.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── Admin ────────────────────────────────────────────────────────
|
|
81
|
+
app.get('/__admin/health', (_req, res) => {
|
|
82
|
+
res.json({ status: 'healthy', service: 'athena', ...simulator.getStats() });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
app.get('/__admin/executions', (_req, res) => {
|
|
86
|
+
res.json({ executions: Array.from(simulator.queryExecutions.values()).map(({ _results, ...e }) => e) });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.get('/__admin/workgroups', (_req, res) => {
|
|
90
|
+
res.json({ workGroups: Array.from(simulator.workGroups.values()) });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
app.post('/__admin/reset', async (_req, res) => {
|
|
94
|
+
await simulator.reset();
|
|
95
|
+
res.json({ message: 'Athena data reset complete' });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return app;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { createAthenaServer };
|