@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,105 @@
1
+ /**
2
+ * OpenSearch Service - Ponto de entrada
3
+ */
4
+
5
+ const OpenSearchServer = require('./server');
6
+ const OpenSearchSimulator = require('./simulator');
7
+
8
+ class OpenSearchService {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.name = 'opensearch';
12
+ this.port = config.ports.opensearch || 9200;
13
+ this.server = null;
14
+ this.simulator = null;
15
+ this.isRunning = false;
16
+ }
17
+
18
+ async initialize() {
19
+ const logger = require('../../utils/logger');
20
+ logger.debug(`Inicializando OpenSearch Service na porta ${this.port}...`);
21
+
22
+ this.simulator = new OpenSearchSimulator(this.config);
23
+ await this.simulator.initialize();
24
+
25
+ this.server = new OpenSearchServer(this.port, this.config);
26
+ this.server.simulator = this.simulator;
27
+
28
+ await this.server.initialize();
29
+
30
+ logger.debug('OpenSearch Service inicializado');
31
+ }
32
+
33
+ injectDependencies(server) {
34
+ // Aqui você pode injetar dependências de outros serviços se necessário
35
+ }
36
+
37
+ async start() {
38
+ const logger = require('../../utils/logger');
39
+
40
+ if (this.isRunning) {
41
+ logger.warn('OpenSearch Service já está em execução');
42
+ return;
43
+ }
44
+
45
+ try {
46
+ await this.server.start();
47
+ this.isRunning = true;
48
+ logger.info('OpenSearch Service iniciado com sucesso');
49
+ } catch (error) {
50
+ logger.error(`Erro ao iniciar OpenSearch Service: ${error.message}`);
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ async stop() {
56
+ const logger = require('../../utils/logger');
57
+
58
+ if (!this.isRunning) {
59
+ logger.warn('OpenSearch Service não está em execução');
60
+ return;
61
+ }
62
+
63
+ try {
64
+ await this.server.stop();
65
+ this.isRunning = false;
66
+ logger.info('OpenSearch Service parado com sucesso');
67
+ } catch (error) {
68
+ logger.error(`Erro ao parar OpenSearch Service: ${error.message}`);
69
+ throw error;
70
+ }
71
+ }
72
+
73
+ async reset() {
74
+ const logger = require('../../utils/logger');
75
+ try {
76
+ await this.simulator.reset();
77
+ logger.info('OpenSearch Service resetado');
78
+ } catch (error) {
79
+ logger.error(`Erro ao resetar OpenSearch Service: ${error.message}`);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ getSimulator() {
85
+ return this.simulator;
86
+ }
87
+
88
+ getStatus() {
89
+ const stats = this.simulator?.getClusterStats() || { indices: 0, docs: 0, size_mb: 0 };
90
+
91
+ return {
92
+ name: this.name,
93
+ running: this.isRunning,
94
+ port: this.port,
95
+ endpoint: `http://localhost:${this.port}`,
96
+ resources: {
97
+ indicesCount: stats.indices,
98
+ docsCount: stats.docs,
99
+ sizeMb: stats.size_mb
100
+ }
101
+ };
102
+ }
103
+ }
104
+
105
+ module.exports = OpenSearchService;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * OpenSearch Document Model - Equivalente ao Item no DynamoDB
3
+ */
4
+
5
+ const { v4: uuidv4 } = require('uuid');
6
+
7
+ class Document {
8
+ constructor(data = {}, config = {}) {
9
+ this._id = config._id || uuidv4();
10
+ this._type = config._type || '_doc';
11
+ this._index = config._index || null;
12
+ this._version = config._version || 1;
13
+ this._seq_no = config._seq_no || 0;
14
+ this._primary_term = config._primary_term || 1;
15
+ this._score = config._score || null;
16
+
17
+ // Metadados
18
+ this._createdAt = config._createdAt || new Date().toISOString();
19
+ this._updatedAt = config._updatedAt || new Date().toISOString();
20
+
21
+ // Source - dados do documento
22
+ this._source = data;
23
+
24
+ // Highlights - para busca
25
+ this.highlight = {};
26
+ }
27
+
28
+ /**
29
+ * Atualiza documento
30
+ */
31
+ update(data = {}) {
32
+ this._source = { ...this._source, ...data };
33
+ this._version++;
34
+ this._updatedAt = new Date().toISOString();
35
+ }
36
+
37
+ /**
38
+ * Incrementa version
39
+ */
40
+ incrementVersion() {
41
+ this._version++;
42
+ this._updatedAt = new Date().toISOString();
43
+ }
44
+
45
+ /**
46
+ * Retorna documento em formato Elasticsearch
47
+ */
48
+ toJSON() {
49
+ return {
50
+ _id: this._id,
51
+ _type: this._type,
52
+ _index: this._index,
53
+ _version: this._version,
54
+ _seq_no: this._seq_no,
55
+ _primary_term: this._primary_term,
56
+ _score: this._score,
57
+ _source: this._source,
58
+ highlight: Object.keys(this.highlight).length > 0 ? this.highlight : undefined
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Retorna apenas _source
64
+ */
65
+ getSource() {
66
+ return this._source;
67
+ }
68
+
69
+ /**
70
+ * Retorna tamanho em bytes (aproximado)
71
+ */
72
+ getSize() {
73
+ return JSON.stringify(this._source).length;
74
+ }
75
+
76
+ /**
77
+ * Verifica se documento match em busca simples
78
+ */
79
+ matchesQuery(query = {}) {
80
+ if (Object.keys(query).length === 0) return true;
81
+
82
+ for (const [key, value] of Object.entries(query)) {
83
+ if (key === '_id' && this._id !== value) return false;
84
+ if (key === '_type' && this._type !== value) return false;
85
+ if (!(key in this._source)) return false;
86
+
87
+ const fieldValue = this._source[key];
88
+ if (typeof fieldValue === 'string') {
89
+ if (!fieldValue.toLowerCase().includes(String(value).toLowerCase())) return false;
90
+ } else if (fieldValue !== value) {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * Busca full-text simples
100
+ */
101
+ matchesFullText(text = '') {
102
+ if (!text) return true;
103
+
104
+ const searchText = text.toLowerCase();
105
+ for (const value of Object.values(this._source)) {
106
+ if (typeof value === 'string' && value.toLowerCase().includes(searchText)) {
107
+ return true;
108
+ }
109
+ }
110
+
111
+ return false;
112
+ }
113
+ }
114
+
115
+ module.exports = Document;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * OpenSearch Index Model - Equivalente à Table no DynamoDB
3
+ */
4
+
5
+ const { v4: uuidv4 } = require('uuid');
6
+
7
+ class Index {
8
+ constructor(name, config = {}) {
9
+ this.name = name;
10
+ this.settings = {
11
+ number_of_shards: config.number_of_shards || 1,
12
+ number_of_replicas: config.number_of_replicas || 1,
13
+ analysis: config.analysis || {
14
+ analyzer: {
15
+ default: {
16
+ type: 'standard'
17
+ }
18
+ }
19
+ }
20
+ };
21
+
22
+ // Mappings - Define tipos de campos
23
+ this.mappings = {
24
+ properties: config.properties || {
25
+ _id: { type: 'keyword' },
26
+ _type: { type: 'keyword' },
27
+ _source: { type: 'text' }
28
+ }
29
+ };
30
+
31
+ this.aliases = config.aliases || {};
32
+ this.createdAt = new Date().toISOString();
33
+ this.status = 'open';
34
+ this.docsCount = 0;
35
+ this.storeSize = 0;
36
+ }
37
+
38
+ toJSON() {
39
+ return {
40
+ name: this.name,
41
+ settings: this.settings,
42
+ mappings: this.mappings,
43
+ aliases: this.aliases,
44
+ status: this.status,
45
+ createdAt: this.createdAt,
46
+ docsCount: this.docsCount,
47
+ storeSize: this.storeSize
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Adiciona documento - incrementa contador
53
+ */
54
+ addDocument() {
55
+ this.docsCount++;
56
+ }
57
+
58
+ /**
59
+ * Remove documento - decrementa contador
60
+ */
61
+ removeDocument() {
62
+ if (this.docsCount > 0) this.docsCount--;
63
+ }
64
+
65
+ /**
66
+ * Atualiza tamanho do índice
67
+ */
68
+ updateSize(totalSize) {
69
+ this.storeSize = totalSize;
70
+ }
71
+ }
72
+
73
+ module.exports = Index;
@@ -0,0 +1,310 @@
1
+ /**
2
+ * OpenSearch Server - Servidor HTTP REST para OpenSearch/Elasticsearch
3
+ */
4
+
5
+ const express = require('express');
6
+ const cors = require('cors');
7
+ const logger = require('../../utils/logger');
8
+
9
+ class OpenSearchServer {
10
+ constructor(port, config) {
11
+ this.port = port;
12
+ this.config = config;
13
+ this.app = express();
14
+ this.simulator = null;
15
+ this.server = null;
16
+ this.setupMiddlewares();
17
+ }
18
+
19
+ setupMiddlewares() {
20
+ this.app.use(cors());
21
+ this.app.use(express.json());
22
+
23
+ // Logging
24
+ this.app.use((req, res, next) => {
25
+ logger.debug(`OpenSearch: ${req.method} ${req.path}`);
26
+ next();
27
+ });
28
+ }
29
+
30
+ async initialize() {
31
+ logger.debug(`Inicializando OpenSearch Server na porta ${this.port}`);
32
+ this.setupRoutes();
33
+ }
34
+
35
+ setupRoutes() {
36
+ // ========== Índices ==========
37
+
38
+ // PUT /{index} - Criar índice
39
+ this.app.put('/:index([a-z0-9_-]+)$', (req, res) => {
40
+ try {
41
+ const { index } = req.params;
42
+ const settings = req.body.settings || {};
43
+ const mappings = req.body.mappings || {};
44
+
45
+ const result = this.simulator.createIndex(index, { ...settings, mappings });
46
+ res.status(201).json(result);
47
+ } catch (error) {
48
+ res.status(400).json({ error: { type: 'index_already_exists_exception', reason: error.message } });
49
+ }
50
+ });
51
+
52
+ // DELETE /{index} - Deletar índice
53
+ this.app.delete('/:index([a-z0-9_-]+)$', (req, res) => {
54
+ try {
55
+ const result = this.simulator.deleteIndex(req.params.index);
56
+ res.json(result);
57
+ } catch (error) {
58
+ res.status(404).json({ error: { type: 'index_not_found_exception', reason: error.message } });
59
+ }
60
+ });
61
+
62
+ // GET /{index} - Obter informações do índice
63
+ this.app.get('/:index([a-z0-9_-]+)$', (req, res) => {
64
+ try {
65
+ const result = this.simulator.getIndex(req.params.index);
66
+ res.json(result);
67
+ } catch (error) {
68
+ res.status(404).json({ error: { type: 'index_not_found_exception', reason: error.message } });
69
+ }
70
+ });
71
+
72
+ // POST /{index}/_open - Abrir índice
73
+ this.app.post('/:index([a-z0-9_-]+)/_open', (req, res) => {
74
+ try {
75
+ const result = this.simulator.openIndex(req.params.index);
76
+ res.json(result);
77
+ } catch (error) {
78
+ res.status(404).json({ error: { type: 'index_not_found_exception', reason: error.message } });
79
+ }
80
+ });
81
+
82
+ // POST /{index}/_close - Fechar índice
83
+ this.app.post('/:index([a-z0-9_-]+)/_close', (req, res) => {
84
+ try {
85
+ const result = this.simulator.closeIndex(req.params.index);
86
+ res.json(result);
87
+ } catch (error) {
88
+ res.status(404).json({ error: { type: 'index_not_found_exception', reason: error.message } });
89
+ }
90
+ });
91
+
92
+ // ========== Documentos ==========
93
+
94
+ // POST /{index}/_doc - Indexar documento (auto ID)
95
+ this.app.post('/:index([a-z0-9_-]+)/_doc', (req, res) => {
96
+ try {
97
+ const result = this.simulator.indexDocument(req.params.index, req.body);
98
+ res.status(201).json(result);
99
+ } catch (error) {
100
+ res.status(400).json({ error: error.message });
101
+ }
102
+ });
103
+
104
+ // PUT /{index}/_doc/{id} - Indexar documento (com ID)
105
+ this.app.put('/:index([a-z0-9_-]+)/_doc/:id', (req, res) => {
106
+ try {
107
+ const result = this.simulator.indexDocument(req.params.index, req.body, req.params.id);
108
+ res.status(req.body._created ? 201 : 200).json(result);
109
+ } catch (error) {
110
+ res.status(400).json({ error: error.message });
111
+ }
112
+ });
113
+
114
+ // GET /{index}/_doc/{id} - Obter documento
115
+ this.app.get('/:index([a-z0-9_-]+)/_doc/:id', (req, res) => {
116
+ try {
117
+ const result = this.simulator.getDocument(req.params.index, req.params.id);
118
+ res.json(result);
119
+ } catch (error) {
120
+ res.status(404).json({ error: error.message });
121
+ }
122
+ });
123
+
124
+ // DELETE /{index}/_doc/{id} - Deletar documento
125
+ this.app.delete('/:index([a-z0-9_-]+)/_doc/:id', (req, res) => {
126
+ try {
127
+ const result = this.simulator.deleteDocument(req.params.index, req.params.id);
128
+ res.json(result);
129
+ } catch (error) {
130
+ res.status(404).json({ error: error.message });
131
+ }
132
+ });
133
+
134
+ // ========== Busca ==========
135
+
136
+ // GET /{index}/_search - Buscar documentos
137
+ this.app.get('/:index([a-z0-9_-]+)/_search', (req, res) => {
138
+ try {
139
+ const query = {};
140
+
141
+ if (req.query.q) {
142
+ query.query_string = { query: req.query.q };
143
+ }
144
+
145
+ const from = parseInt(req.query.from || 0);
146
+ const size = parseInt(req.query.size || 10);
147
+
148
+ const result = this.simulator.search(req.params.index, { query, from, size });
149
+ res.json(result);
150
+ } catch (error) {
151
+ res.status(400).json({ error: error.message });
152
+ }
153
+ });
154
+
155
+ // POST /{index}/_search - Buscar com body query
156
+ this.app.post('/:index([a-z0-9_-]+)/_search', (req, res) => {
157
+ try {
158
+ const result = this.simulator.search(req.params.index, req.body);
159
+ res.json(result);
160
+ } catch (error) {
161
+ res.status(400).json({ error: error.message });
162
+ }
163
+ });
164
+
165
+ // ========== Update/Delete ==========
166
+
167
+ // POST /{index}/_update_by_query - Atualizar por query
168
+ this.app.post('/:index([a-z0-9_-]+)/_update_by_query', (req, res) => {
169
+ try {
170
+ const result = this.simulator.updateByQuery(req.params.index, req.body.script, req.body.query);
171
+ res.json(result);
172
+ } catch (error) {
173
+ res.status(400).json({ error: error.message });
174
+ }
175
+ });
176
+
177
+ // POST /{index}/_delete_by_query - Deletar por query
178
+ this.app.post('/:index([a-z0-9_-]+)/_delete_by_query', (req, res) => {
179
+ try {
180
+ const result = this.simulator.deleteByQuery(req.params.index, req.body.query);
181
+ res.json(result);
182
+ } catch (error) {
183
+ res.status(400).json({ error: error.message });
184
+ }
185
+ });
186
+
187
+ // ========== Agregações ==========
188
+
189
+ // POST /{index}/_search?agg - Buscar com agregação
190
+ this.app.post('/:index([a-z0-9_-]+)/_search', (req, res) => {
191
+ try {
192
+ if (req.body.aggs) {
193
+ const result = this.simulator.aggregate(req.params.index, req.body.aggs);
194
+ res.json(result);
195
+ } else {
196
+ const result = this.simulator.search(req.params.index, req.body);
197
+ res.json(result);
198
+ }
199
+ } catch (error) {
200
+ res.status(400).json({ error: error.message });
201
+ }
202
+ });
203
+
204
+ // ========== Admin / Info ==========
205
+
206
+ // GET / - Health check
207
+ this.app.get('/', (req, res) => {
208
+ res.json({
209
+ name: 'opensearch-simulator',
210
+ cluster_name: 'opensearch-cluster',
211
+ cluster_uuid: 'abc123',
212
+ version: {
213
+ number: '2.0.0',
214
+ build_flavor: 'oss',
215
+ build_type: 'tar',
216
+ build_hash: 'abc123',
217
+ build_date: new Date().toISOString(),
218
+ build_snapshot: false,
219
+ lucene_version: '9.0.0',
220
+ minimum_wire_compatibility_version: '7.17.0',
221
+ minimum_index_compatibility_version: '7.0.0'
222
+ },
223
+ tagline: 'The OpenSearch Project'
224
+ });
225
+ });
226
+
227
+ // GET /_cluster/health - Cluster health
228
+ this.app.get('/_cluster/health', (req, res) => {
229
+ const stats = this.simulator.getClusterStats();
230
+ res.json({
231
+ cluster_name: 'opensearch-cluster',
232
+ status: 'green',
233
+ timed_out: false,
234
+ number_of_nodes: 1,
235
+ number_of_data_nodes: 1,
236
+ active_primary_shards: stats.shards,
237
+ active_shards: stats.shards * 2,
238
+ relocating_shards: 0,
239
+ initializing_shards: 0,
240
+ unassigned_shards: 0,
241
+ delayed_unassigned_shards: 0,
242
+ number_of_pending_tasks: 0,
243
+ number_of_in_flight_fetch: 0,
244
+ task_max_waiting_in_queue_millis: 0,
245
+ active_shards_percent_as_number: 100.0
246
+ });
247
+ });
248
+
249
+ // GET /_cat/indices - Listar índices (admin)
250
+ this.app.get('/__admin/opensearch/indices', (req, res) => {
251
+ const indices = this.simulator.listIndices();
252
+ res.json({ indices });
253
+ });
254
+
255
+ // GET /_stats - Estatísticas
256
+ this.app.get('/_stats', (req, res) => {
257
+ const stats = this.simulator.getClusterStats();
258
+ res.json({
259
+ _shards: { total: stats.shards, successful: stats.shards, failed: 0 },
260
+ _all: {
261
+ primaries: {
262
+ docs: { count: stats.docs, deleted: 0 },
263
+ store: { size_in_bytes: stats.size_mb * 1024 * 1024 },
264
+ indexing: { index_total: stats.docs, index_time_in_millis: 0 },
265
+ search: { query_total: 0, query_time_in_millis: 0 }
266
+ },
267
+ total: {
268
+ docs: { count: stats.docs, deleted: 0 },
269
+ store: { size_in_bytes: stats.size_mb * 1024 * 1024 }
270
+ }
271
+ },
272
+ indices: {}
273
+ });
274
+ });
275
+
276
+ // 404
277
+ this.app.use((req, res) => {
278
+ res.status(404).json({
279
+ error: {
280
+ type: 'index_not_found_exception',
281
+ reason: `no such index [${req.path}]`
282
+ }
283
+ });
284
+ });
285
+ }
286
+
287
+ async start() {
288
+ return new Promise((resolve) => {
289
+ this.server = this.app.listen(this.port, () => {
290
+ logger.info(`🔍 OpenSearch rodando em http://localhost:${this.port}`);
291
+ resolve();
292
+ });
293
+ });
294
+ }
295
+
296
+ async stop() {
297
+ return new Promise((resolve) => {
298
+ if (this.server) {
299
+ this.server.close(() => {
300
+ logger.info('OpenSearch servidor parado');
301
+ resolve();
302
+ });
303
+ } else {
304
+ resolve();
305
+ }
306
+ });
307
+ }
308
+ }
309
+
310
+ module.exports = OpenSearchServer;