mockapi-msi 2.0.1 → 2.4.0

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/modules/core.js CHANGED
@@ -1,111 +1,289 @@
1
- const parser = require('./urlParser');
2
- const http = require('http');
3
- const constants = require('./constants');
4
- const handlerLoader = require('./configurationParser');
5
-
6
- class Core {
7
-
8
- _logger = null;
9
- _port = 0;
10
- _enableCors = false;
11
- _endpointList = [];
12
- _logLevel = ""
13
- _modulesProxy = null;
14
- _configurations = null;
15
- _data = null;
16
-
17
- constructor(logger, configurations, modulesProxy) {
18
- this._configurations = configurations;
19
- this._logger = logger;
20
- this._port = configurations.port;
21
- this._enableCors = configurations.enableCors;
22
- this._endpointList = configurations.endpoints;
23
- this._modulesProxy = modulesProxy;
24
-
25
- this._data = configurations.data || { };
26
- handlerLoader.loadHandlersFromConfiguration(this._data);
27
- }
28
-
29
- run() {
30
- const self = this;
31
-
32
- http.createServer((request, response) => {
33
- let bodyPayload = [];
34
-
35
- request.on('data', (chunk) => {
36
- bodyPayload.push(chunk);
37
- }).on('end', () => {
38
- bodyPayload = Buffer.concat(bodyPayload).toString();
39
-
40
- const urlInformation = parser.parse(request.url);
41
-
42
- self._logger.info(`Requesting: ${urlInformation.base} - Verb: ${request.method}`);
43
-
44
- if (bodyPayload !== '') {
45
- self._logger.info(`Incoming body: ${bodyPayload}`);
46
- }
47
-
48
- let actionFound = false;
49
- let responseBody = null;
50
- let responseStatus = null;
51
- let contentType = null;
52
-
53
- for (const endpointUrl in self._endpointList) {
54
- if (Object.hasOwnProperty.call(self._endpointList, endpointUrl)) {
55
- const endpoint = self._endpointList[endpointUrl];
56
- const requestMethod = request.method.toLowerCase();
57
-
58
- if (
59
- endpointUrl === urlInformation.base &&
60
- (endpoint.verb === "any" || endpoint.verb.toLowerCase() === requestMethod)
61
- ) {
62
-
63
- if (endpoint.data !== undefined && self._data[endpoint.data] === undefined) {
64
- self._logger.error("No matching data variable for this request");
65
- break;
66
- }
67
-
68
- actionFound = true;
69
- contentType = endpoint.responseContentType;
70
-
71
- try {
72
- const processData = endpoint.data === undefined ?
73
- "" :
74
- self._data[endpoint.data].dataHandler(urlInformation);
75
-
76
- responseBody = endpoint.handler !== undefined ?
77
- self._modulesProxy.execute(endpoint.handler, {
78
- method: requestMethod,
79
- url: endpointUrl,
80
- body: bodyPayload
81
- }, processData) : processData;
82
-
83
- responseStatus = endpoint.responseStatus;
84
- } catch(ex) {
85
- self._logger.error(`${ex.message}`);
86
-
87
- responseStatus = ex.httpStatusCode;
88
- responseBody = ex.message;
89
- }
90
-
91
- break;
92
- }
93
- }
94
- }
95
-
96
- response.statusCode = responseStatus ||
97
- (!actionFound ?
98
- constants.HTTP_STATUS_CODES.NOT_FOUND :
99
- constants.HTTP_STATUS_CODES.OK);
100
-
101
- response.setHeader('Content-Type', contentType || constants.DEFAULT_CONTENT_TYPE);
102
-
103
- self._enableCors && response.setHeader('Access-Control-Allow-Origin', '*');
104
-
105
- response.end(responseBody);
106
- });
107
- }).listen(self._port);
108
- }
109
- }
110
-
1
+ const parser = require('./urlParser');
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const constants = require('./constants');
7
+ const handlerLoader = require('./configurationParser');
8
+
9
+ const MIME_TYPES = {
10
+ '.html': 'text/html',
11
+ '.css': 'text/css',
12
+ '.js': 'application/javascript',
13
+ '.json': 'application/json',
14
+ '.png': 'image/png',
15
+ '.jpg': 'image/jpeg',
16
+ '.jpeg': 'image/jpeg',
17
+ '.gif': 'image/gif',
18
+ '.svg': 'image/svg+xml',
19
+ '.ico': 'image/x-icon',
20
+ '.txt': 'text/plain',
21
+ '.xml': 'application/xml',
22
+ '.pdf': 'application/pdf'
23
+ };
24
+
25
+ class Core {
26
+
27
+ _logger = null;
28
+ _port = 0;
29
+ _cors = null;
30
+ _endpointList = [];
31
+ _logLevel = ""
32
+ _modulesProxy = null;
33
+ _configurations = null;
34
+ _data = null;
35
+ _staticPath = null;
36
+ _connections = new Set();
37
+ _tls = null;
38
+
39
+ constructor(logger, configurations, modulesProxy) {
40
+ this._configurations = configurations;
41
+ this._logger = logger;
42
+ this._port = configurations.port;
43
+ this._cors = this._parseCors(configurations.enableCors);
44
+ this._endpointList = configurations.endpoints;
45
+ this._modulesProxy = modulesProxy;
46
+ this._staticPath = configurations.staticPath || null;
47
+ this._tls = this._parseTls(configurations.tls);
48
+
49
+ this._data = configurations.data || { };
50
+ handlerLoader.loadHandlersFromConfiguration(this._data);
51
+ }
52
+
53
+ _parseTls(tlsConfig) {
54
+ if (!tlsConfig || !tlsConfig.cert || !tlsConfig.key) return null;
55
+
56
+ try {
57
+ return {
58
+ cert: fs.readFileSync(tlsConfig.cert),
59
+ key: fs.readFileSync(tlsConfig.key)
60
+ };
61
+ } catch (error) {
62
+ this._logger.error(`Failed to load TLS certificates: ${error.message}`);
63
+ return null;
64
+ }
65
+ }
66
+
67
+ _parseCors(corsConfig) {
68
+ if (!corsConfig) return null;
69
+
70
+ if (corsConfig === true) {
71
+ return { origins: '*', methods: '*', headers: '*' };
72
+ }
73
+
74
+ return {
75
+ origins: corsConfig.origins || '*',
76
+ methods: corsConfig.methods || '*',
77
+ headers: corsConfig.headers || '*'
78
+ };
79
+ }
80
+
81
+ _applyCorsHeaders(request, response) {
82
+ if (!this._cors) return false;
83
+
84
+ const origin = request.headers['origin'] || '*';
85
+ const allowedOrigin = this._cors.origins === '*'
86
+ ? '*'
87
+ : (this._cors.origins.includes(origin) ? origin : null);
88
+
89
+ if (!allowedOrigin) return false;
90
+
91
+ response.setHeader('Access-Control-Allow-Origin', allowedOrigin);
92
+
93
+ const methods = this._cors.methods === '*'
94
+ ? 'GET, POST, PUT, DELETE, PATCH, OPTIONS'
95
+ : (Array.isArray(this._cors.methods) ? this._cors.methods.join(', ') : this._cors.methods);
96
+ response.setHeader('Access-Control-Allow-Methods', methods);
97
+
98
+ const headers = this._cors.headers === '*'
99
+ ? 'Content-Type, Authorization, X-Requested-With'
100
+ : (Array.isArray(this._cors.headers) ? this._cors.headers.join(', ') : this._cors.headers);
101
+ response.setHeader('Access-Control-Allow-Headers', headers);
102
+
103
+ return true;
104
+ }
105
+
106
+ _serveStaticFile(request, response, urlPath) {
107
+ const safePath = path.normalize(urlPath).replace(/^(\.\.[/\\])+/, '');
108
+ const filePath = path.join(this._staticPath, safePath);
109
+ const resolvedPath = path.resolve(filePath);
110
+ const resolvedStatic = path.resolve(this._staticPath);
111
+
112
+ if (!resolvedPath.startsWith(resolvedStatic)) {
113
+ response.statusCode = constants.HTTP_STATUS_CODES.FORBIDDEN;
114
+ response.end('Forbidden');
115
+ return;
116
+ }
117
+
118
+ if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isFile()) {
119
+ return false;
120
+ }
121
+
122
+ const ext = path.extname(resolvedPath).toLowerCase();
123
+ const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
124
+
125
+ this._logger.info(`Serving static file: ${resolvedPath}`);
126
+
127
+ response.statusCode = constants.HTTP_STATUS_CODES.OK;
128
+ response.setHeader('Content-Type', mimeType);
129
+ this._applyCorsHeaders(request, response);
130
+
131
+ const stream = fs.createReadStream(resolvedPath);
132
+ stream.pipe(response);
133
+
134
+ return true;
135
+ }
136
+
137
+ run() {
138
+ const self = this;
139
+
140
+ const requestHandler = (request, response) => {
141
+
142
+ // Handle CORS preflight requests
143
+ if (request.method === 'OPTIONS' && self._cors) {
144
+ self._applyCorsHeaders(request, response);
145
+ response.statusCode = constants.HTTP_STATUS_CODES.NO_CONTENT;
146
+ response.end();
147
+ return;
148
+ }
149
+
150
+ let bodyPayload = [];
151
+
152
+ request.on('data', (chunk) => {
153
+ bodyPayload.push(chunk);
154
+ }).on('end', () => {
155
+ bodyPayload = Buffer.concat(bodyPayload).toString();
156
+
157
+ const urlInformation = parser.parse(request.url);
158
+
159
+ self._logger.info(`Requesting: ${urlInformation.base} - Verb: ${request.method}`);
160
+
161
+ if (bodyPayload !== '') {
162
+ self._logger.info(`Incoming body: ${bodyPayload}`);
163
+ }
164
+
165
+ let actionFound = false;
166
+ let responseBody = null;
167
+ let responseStatus = null;
168
+ let contentType = null;
169
+ let delay = 0;
170
+
171
+ for (const endpointUrl in self._endpointList) {
172
+ if (Object.hasOwnProperty.call(self._endpointList, endpointUrl)) {
173
+ const endpoint = self._endpointList[endpointUrl];
174
+ const requestMethod = request.method.toLowerCase();
175
+
176
+ const pathResult = parser.matchPath(endpointUrl, urlInformation.base);
177
+
178
+ if (
179
+ pathResult.match &&
180
+ (endpoint.verb === "any" || endpoint.verb.toLowerCase() === requestMethod)
181
+ ) {
182
+
183
+ if (endpoint.data !== undefined && self._data[endpoint.data] === undefined) {
184
+ self._logger.error("No matching data variable for this request");
185
+ break;
186
+ }
187
+
188
+ // Merge path params and query params into urlInformation
189
+ urlInformation.params = pathResult.params;
190
+ urlInformation.query = Object.fromEntries(urlInformation.search);
191
+
192
+ actionFound = true;
193
+ contentType = endpoint.responseContentType;
194
+ delay = endpoint.delay || 0;
195
+
196
+ try {
197
+ const processData = endpoint.data === undefined ?
198
+ "" :
199
+ self._data[endpoint.data].dataHandler(urlInformation);
200
+
201
+ responseBody = endpoint.handler !== undefined ?
202
+ self._modulesProxy.execute(endpoint.handler, {
203
+ method: requestMethod,
204
+ url: endpointUrl,
205
+ body: bodyPayload,
206
+ params: urlInformation.params,
207
+ query: urlInformation.query
208
+ }, processData) : processData;
209
+
210
+ responseStatus = endpoint.responseStatus;
211
+ } catch(ex) {
212
+ self._logger.error(`${ex.message}`);
213
+
214
+ responseStatus = ex.httpStatusCode;
215
+ responseBody = ex.message;
216
+ }
217
+
218
+ break;
219
+ }
220
+ }
221
+ }
222
+
223
+ const sendResponse = () => {
224
+ // Try static file serving before returning 404
225
+ if (!actionFound && self._staticPath) {
226
+ const served = self._serveStaticFile(request, response, urlInformation.pathname);
227
+ if (served !== false) return;
228
+ }
229
+
230
+ response.statusCode = responseStatus ||
231
+ (!actionFound ?
232
+ constants.HTTP_STATUS_CODES.NOT_FOUND :
233
+ constants.HTTP_STATUS_CODES.OK);
234
+
235
+ response.setHeader('Content-Type', contentType || constants.DEFAULT_CONTENT_TYPE);
236
+
237
+ self._applyCorsHeaders(request, response);
238
+
239
+ response.end(responseBody);
240
+ };
241
+
242
+ if (delay > 0) {
243
+ self._logger.info(`Delaying response by ${delay}ms`);
244
+ setTimeout(sendResponse, delay);
245
+ } else {
246
+ sendResponse();
247
+ }
248
+ });
249
+ };
250
+
251
+ this._server = this._tls
252
+ ? https.createServer(this._tls, requestHandler)
253
+ : http.createServer(requestHandler);
254
+
255
+ this._server.listen(self._port);
256
+
257
+ this._server.on('connection', (socket) => {
258
+ self._connections.add(socket);
259
+ socket.on('close', () => self._connections.delete(socket));
260
+ });
261
+ }
262
+
263
+ reload(configurations) {
264
+ this._configurations = configurations;
265
+ this._port = configurations.port;
266
+ this._cors = this._parseCors(configurations.enableCors);
267
+ this._endpointList = configurations.endpoints;
268
+ this._staticPath = configurations.staticPath || null;
269
+
270
+ this._data = configurations.data || {};
271
+ handlerLoader.loadHandlersFromConfiguration(this._data);
272
+
273
+ this._logger.info('Configuration reloaded');
274
+ }
275
+
276
+ stop(callback) {
277
+ if (this._server) {
278
+ this._server.close(callback);
279
+ for (const socket of this._connections) {
280
+ socket.destroy();
281
+ }
282
+ this._connections.clear();
283
+ } else if (callback) {
284
+ callback();
285
+ }
286
+ }
287
+ }
288
+
111
289
  module.exports = Core;
package/modules/csv.js CHANGED
@@ -1,78 +1,122 @@
1
- let CSV = function (content, properties) {
2
- this._content = content;
3
- this._properties = properties;
4
- this._currentIndex = properties.startIndex;
5
- this._data = this._readData();
6
- this._formattedData = [];
7
-
8
- this["_" + properties.format]();
9
- };
10
-
11
- CSV.prototype._readData = function () {
12
- let data = [];
13
- const lines = this._content.split("\r\n");
14
-
15
- for (let index = 0; index < lines.length; index++) {
16
- const line = lines[index];
17
- const extractedLine = line.match(/(?:,|\n|^)("(?:(?:"")*[^"]*)*"|[^",\n]*|(?:\n|$))/gm);
18
-
19
- let parsedLine = [];
20
-
21
- for (let i = 0; i < extractedLine.length; i++) {
22
- const item = extractedLine[i];
23
- parsedLine.push(item);
24
- }
25
-
26
- data.push(parsedLine);
27
- }
28
-
29
- return data;
30
- };
31
-
32
- CSV.prototype._json = function () {
33
- if (this._data.length === 0) return;
34
-
35
- const propertyNames = this._data[0];
36
-
37
- for (let i = 1; i < this._data.length; i++) {
38
- const record = this._data[i];
39
- let obj = { };
40
-
41
- for (let j = 0; j < record.length; j++) {
42
- const data = record[j];
43
- obj[propertyNames[j]] = data;
44
- }
45
-
46
- this._formattedData.push(obj);
47
- }
48
- };
49
-
50
- CSV.prototype._text = function () {
51
- this._formattedData = this._data.slice(1);
52
- };
53
-
54
- CSV.prototype._seq = function() {
55
- if (this._currentIndex >= this._formattedData.length || this._currentIndex < 0) this._currentIndex = 0;
56
-
57
- const record = this._formattedData[this._currentIndex];
58
- this._currentIndex++;
59
-
60
- return record;
61
- };
62
-
63
- CSV.prototype._rand = function() {
64
- this._currentIndex = parseInt((Math.random() * this._formattedData.length - 1) + 1);
65
- const record = this._formattedData[this._currentIndex];
66
-
67
- return record;
68
- };
69
-
70
- CSV.prototype.read = function () {
71
- if (this._currentIndex === -1) {
72
- return JSON.stringify(this._formattedData);
73
- }
74
-
75
- return JSON.stringify(this["_" + this._properties.direction]());
76
- };
77
-
1
+ class CSV {
2
+
3
+ constructor(content, properties) {
4
+ this._properties = properties;
5
+ this._currentIndex = properties.startIndex;
6
+ this._data = this._parseCSV(content);
7
+ this._formattedData = [];
8
+
9
+ this._applyFormat(properties.format);
10
+ }
11
+
12
+ _parseCSV(content) {
13
+ const rows = [];
14
+ let currentRow = [];
15
+ let currentField = '';
16
+ let insideQuotes = false;
17
+ let i = 0;
18
+
19
+ while (i < content.length) {
20
+ const char = content[i];
21
+
22
+ if (insideQuotes) {
23
+ if (char === '"') {
24
+ if (i + 1 < content.length && content[i + 1] === '"') {
25
+ currentField += '"';
26
+ i += 2;
27
+ } else {
28
+ insideQuotes = false;
29
+ i++;
30
+ }
31
+ } else {
32
+ currentField += char;
33
+ i++;
34
+ }
35
+ } else {
36
+ if (char === '"') {
37
+ insideQuotes = true;
38
+ i++;
39
+ } else if (char === ',') {
40
+ currentRow.push(currentField);
41
+ currentField = '';
42
+ i++;
43
+ } else if (char === '\r' && i + 1 < content.length && content[i + 1] === '\n') {
44
+ currentRow.push(currentField);
45
+ currentField = '';
46
+ rows.push(currentRow);
47
+ currentRow = [];
48
+ i += 2;
49
+ } else if (char === '\n' || char === '\r') {
50
+ currentRow.push(currentField);
51
+ currentField = '';
52
+ rows.push(currentRow);
53
+ currentRow = [];
54
+ i++;
55
+ } else {
56
+ currentField += char;
57
+ i++;
58
+ }
59
+ }
60
+ }
61
+
62
+ if (currentField || currentRow.length > 0) {
63
+ currentRow.push(currentField);
64
+ rows.push(currentRow);
65
+ }
66
+
67
+ return rows;
68
+ }
69
+
70
+ _applyFormat(format) {
71
+ if (typeof this[`_${format}`] === 'function') {
72
+ this[`_${format}`]();
73
+ }
74
+ }
75
+
76
+ _json() {
77
+ if (this._data.length === 0) return;
78
+
79
+ const propertyNames = this._data[0];
80
+
81
+ for (let i = 1; i < this._data.length; i++) {
82
+ const record = this._data[i];
83
+ const obj = {};
84
+
85
+ for (let j = 0; j < record.length; j++) {
86
+ obj[propertyNames[j]] = record[j];
87
+ }
88
+
89
+ this._formattedData.push(obj);
90
+ }
91
+ }
92
+
93
+ _text() {
94
+ this._formattedData = this._data.slice(1);
95
+ }
96
+
97
+ _seq() {
98
+ if (this._currentIndex >= this._formattedData.length || this._currentIndex < 0) {
99
+ this._currentIndex = 0;
100
+ }
101
+
102
+ const record = this._formattedData[this._currentIndex];
103
+ this._currentIndex++;
104
+
105
+ return record;
106
+ }
107
+
108
+ _rand() {
109
+ this._currentIndex = Math.floor(Math.random() * this._formattedData.length);
110
+ return this._formattedData[this._currentIndex];
111
+ }
112
+
113
+ read() {
114
+ if (this._currentIndex === -1) {
115
+ return JSON.stringify(this._formattedData);
116
+ }
117
+
118
+ return JSON.stringify(this[`_${this._properties.direction}`]());
119
+ }
120
+ }
121
+
78
122
  module.exports = CSV;