mockapi-msi 2.0.0 → 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/LICENSE +164 -164
- package/README.md +319 -116
- package/apiHandlers/myCustomHandler.js +26 -9
- package/main.js +87 -53
- package/modules/HttpException.js +8 -30
- package/modules/banner.js +22 -0
- package/modules/cli.js +106 -106
- package/modules/configWatcher.js +60 -0
- package/modules/configurationParser.js +43 -43
- package/modules/constants.js +95 -95
- package/modules/core.js +288 -107
- package/modules/csv.js +121 -77
- package/modules/log.js +42 -38
- package/modules/moduleProxy.js +51 -54
- package/modules/readers.js +42 -42
- package/modules/urlParser.js +53 -22
- package/package.json +38 -20
- package/testdata/data.csv +5 -5
- package/.dockerignore +0 -24
- package/.mockapi-config +0 -47
- package/Dockerfile +0 -8
- package/docker-compose.debug.yml +0 -14
- package/docker-compose.yml +0 -12
package/modules/log.js
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
const constants = require("./constants");
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
const constants = require("./constants");
|
|
2
|
+
|
|
3
|
+
class Log {
|
|
4
|
+
|
|
5
|
+
constructor(logLevel) {
|
|
6
|
+
this._logLevel = logLevel || "verbose";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_write(message) {
|
|
10
|
+
const date = (new Date()).toISOString();
|
|
11
|
+
console.log(`${date} ${message}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
debug(message) {
|
|
15
|
+
if (this._logLevel === constants.LOG_LEVELS.DEBUG || this._logLevel === constants.LOG_LEVELS.VERBOSE) {
|
|
16
|
+
this._write(`${constants.COLOR.fgYellow}[DEBUG]${constants.COLOR.reset} - ${message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
error(message) {
|
|
21
|
+
if (this._logLevel === constants.LOG_LEVELS.ERROR || this._logLevel === constants.LOG_LEVELS.VERBOSE) {
|
|
22
|
+
this._write(`${constants.COLOR.fgRed}[ERROR]${constants.COLOR.reset} - ${message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
info(message) {
|
|
27
|
+
if (this._logLevel === constants.LOG_LEVELS.VERBOSE) {
|
|
28
|
+
this._write(`${constants.COLOR.fgYellow}[INFO]${constants.COLOR.reset} - ${message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
detail(message) {
|
|
33
|
+
if (this._logLevel === constants.LOG_LEVELS.VERBOSE) {
|
|
34
|
+
this._write(`${message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
message(message) {
|
|
39
|
+
console.log(message);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
module.exports = Log;
|
package/modules/moduleProxy.js
CHANGED
|
@@ -1,55 +1,52 @@
|
|
|
1
|
-
const constants = require("./constants");
|
|
2
|
-
const HttpException = require('./HttpException');
|
|
3
|
-
|
|
4
|
-
class Proxy {
|
|
5
|
-
|
|
6
|
-
_externalModuleList = {};
|
|
7
|
-
|
|
8
|
-
constructor(externalModulePath, logger) {
|
|
9
|
-
this._externalModuleList = {};
|
|
10
|
-
this._logger = logger;
|
|
11
|
-
this._externalModulePath = externalModulePath;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async load(modules) {
|
|
15
|
-
for (const moduleName in modules) {
|
|
16
|
-
if (Object.hasOwnProperty.call(modules, moduleName)) {
|
|
17
|
-
const moduleFileName = modules[moduleName];
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const modulePath = `${this._externalModulePath}${moduleFileName}.js`;
|
|
21
|
-
|
|
22
|
-
this._logger.info(`Attempting to load module ${moduleName} from ${modulePath}`);
|
|
23
|
-
|
|
24
|
-
const module = await import(modulePath);
|
|
25
|
-
|
|
26
|
-
this._externalModuleList[moduleName] = module;
|
|
27
|
-
|
|
28
|
-
this._logger.info(`Module '${moduleName}' was loaded`);
|
|
29
|
-
} catch (error) {
|
|
30
|
-
this._logger.error(`Module '${moduleName}' failed during loading ${error}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
execute(name, requestInformation, data) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
1
|
+
const constants = require("./constants");
|
|
2
|
+
const HttpException = require('./HttpException');
|
|
3
|
+
|
|
4
|
+
class Proxy {
|
|
5
|
+
|
|
6
|
+
_externalModuleList = {};
|
|
7
|
+
|
|
8
|
+
constructor(externalModulePath, logger) {
|
|
9
|
+
this._externalModuleList = {};
|
|
10
|
+
this._logger = logger;
|
|
11
|
+
this._externalModulePath = externalModulePath;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async load(modules) {
|
|
15
|
+
for (const moduleName in modules) {
|
|
16
|
+
if (Object.hasOwnProperty.call(modules, moduleName)) {
|
|
17
|
+
const moduleFileName = modules[moduleName];
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const modulePath = `${this._externalModulePath}${moduleFileName}.js`;
|
|
21
|
+
|
|
22
|
+
this._logger.info(`Attempting to load module ${moduleName} from ${modulePath}`);
|
|
23
|
+
|
|
24
|
+
const module = await import(modulePath);
|
|
25
|
+
|
|
26
|
+
this._externalModuleList[moduleName] = module;
|
|
27
|
+
|
|
28
|
+
this._logger.info(`Module '${moduleName}' was loaded`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this._logger.error(`Module '${moduleName}' failed during loading ${error}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
execute(name, requestInformation, data) {
|
|
37
|
+
const module = this._externalModuleList[name];
|
|
38
|
+
|
|
39
|
+
if (!module) {
|
|
40
|
+
throw new HttpException(constants.HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, `Module ${name} doesn't exist`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return module.process(requestInformation, data);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new HttpException(constants.HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR, `Module ${name} failed. ${error}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
55
52
|
module.exports = Proxy;
|
package/modules/readers.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const constants = require("./constants");
|
|
4
|
-
const CSV = require('./csv');
|
|
5
|
-
const HttpException = require('./HttpException');
|
|
6
|
-
|
|
7
|
-
const text_reader = (file) => {
|
|
8
|
-
return () => {
|
|
9
|
-
if (!fs.existsSync(file)) {
|
|
10
|
-
throw new HttpException(constants.HTTP_STATUS_CODES.NOT_FOUND, `File ${file} doesn't exists`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return fs.readFileSync(file, 'utf8');
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const file_exists = (file) => fs.existsSync(file);
|
|
18
|
-
|
|
19
|
-
const folder_reader = (folder) => {
|
|
20
|
-
return (urlInformation) => {
|
|
21
|
-
|
|
22
|
-
if (!urlInformation.hasFile) {
|
|
23
|
-
throw new HttpException(constants.HTTP_STATUS_CODES.NOT_ACCEPTABLE, "File not provided");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const file = path.join(folder, urlInformation.file);
|
|
27
|
-
return text_reader(file)()
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const csv_reader = function (file, parameters) {
|
|
32
|
-
const csvContent = text_reader(file, parameters)();
|
|
33
|
-
const csvReader = new CSV(csvContent, parameters);
|
|
34
|
-
|
|
35
|
-
return csvReader.read.bind(csvReader);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
text_reader: text_reader,
|
|
40
|
-
folder_reader: folder_reader,
|
|
41
|
-
csv_reader: csv_reader,
|
|
42
|
-
file_exists: file_exists
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const constants = require("./constants");
|
|
4
|
+
const CSV = require('./csv');
|
|
5
|
+
const HttpException = require('./HttpException');
|
|
6
|
+
|
|
7
|
+
const text_reader = (file) => {
|
|
8
|
+
return () => {
|
|
9
|
+
if (!fs.existsSync(file)) {
|
|
10
|
+
throw new HttpException(constants.HTTP_STATUS_CODES.NOT_FOUND, `File ${file} doesn't exists`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return fs.readFileSync(file, 'utf8');
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const file_exists = (file) => fs.existsSync(file);
|
|
18
|
+
|
|
19
|
+
const folder_reader = (folder) => {
|
|
20
|
+
return (urlInformation) => {
|
|
21
|
+
|
|
22
|
+
if (!urlInformation.hasFile) {
|
|
23
|
+
throw new HttpException(constants.HTTP_STATUS_CODES.NOT_ACCEPTABLE, "File not provided");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const file = path.join(folder, urlInformation.file);
|
|
27
|
+
return text_reader(file)()
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const csv_reader = function (file, parameters) {
|
|
32
|
+
const csvContent = text_reader(file, parameters)();
|
|
33
|
+
const csvReader = new CSV(csvContent, parameters);
|
|
34
|
+
|
|
35
|
+
return csvReader.read.bind(csvReader);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
text_reader: text_reader,
|
|
40
|
+
folder_reader: folder_reader,
|
|
41
|
+
csv_reader: csv_reader,
|
|
42
|
+
file_exists: file_exists
|
|
43
43
|
};
|
package/modules/urlParser.js
CHANGED
|
@@ -1,23 +1,54 @@
|
|
|
1
|
-
const constants = require("./constants");
|
|
2
|
-
|
|
3
|
-
const parser = (url) => {
|
|
4
|
-
|
|
5
|
-
const parsedUrl = new URL(url, constants.BASE_URL);
|
|
6
|
-
const urlSections = parsedUrl.pathname.split("/").filter((e) => e !== "");
|
|
7
|
-
|
|
8
|
-
const baseUrl = "/" + urlSections.filter((e) => e.indexOf(".") < 0).join("/");
|
|
9
|
-
const file = urlSections.filter((e) => e.indexOf(".") >= 0);
|
|
10
|
-
const hasFile = file.length > 0;
|
|
11
|
-
|
|
12
|
-
return {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
hasFile:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
const constants = require("./constants");
|
|
2
|
+
|
|
3
|
+
const parser = (url) => {
|
|
4
|
+
|
|
5
|
+
const parsedUrl = new URL(url, constants.BASE_URL);
|
|
6
|
+
const urlSections = parsedUrl.pathname.split("/").filter((e) => e !== "");
|
|
7
|
+
|
|
8
|
+
const baseUrl = "/" + urlSections.filter((e) => e.indexOf(".") < 0).join("/");
|
|
9
|
+
const file = urlSections.filter((e) => e.indexOf(".") >= 0);
|
|
10
|
+
const hasFile = file.length > 0;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
pathname: parsedUrl.pathname,
|
|
14
|
+
base: baseUrl,
|
|
15
|
+
file: hasFile ? file[0] : "",
|
|
16
|
+
hasFile: hasFile,
|
|
17
|
+
search: parsedUrl.searchParams
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Matches a request path against an endpoint pattern that may contain
|
|
24
|
+
* path parameters (e.g., /users/:id/orders/:orderId).
|
|
25
|
+
*
|
|
26
|
+
* @param {string} pattern - The endpoint pattern from the configuration.
|
|
27
|
+
* @param {string} requestPath - The actual incoming request base path.
|
|
28
|
+
* @returns {{ match: boolean, params: Object }} Whether it matched and extracted path parameters.
|
|
29
|
+
*/
|
|
30
|
+
const matchPath = (pattern, requestPath) => {
|
|
31
|
+
const patternParts = pattern.split("/").filter((e) => e !== "");
|
|
32
|
+
const requestParts = requestPath.split("/").filter((e) => e !== "");
|
|
33
|
+
|
|
34
|
+
if (patternParts.length !== requestParts.length) {
|
|
35
|
+
return { match: false, params: {} };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const params = {};
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
41
|
+
if (patternParts[i].startsWith(":")) {
|
|
42
|
+
params[patternParts[i].substring(1)] = requestParts[i];
|
|
43
|
+
} else if (patternParts[i] !== requestParts[i]) {
|
|
44
|
+
return { match: false, params: {} };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { match: true, params };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
parse: parser,
|
|
53
|
+
matchPath: matchPath
|
|
23
54
|
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mockapi-msi",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Mock API is a lightweight configurable HTTP API for testing and prototyping.",
|
|
5
|
-
"main": "main.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "node ./main.js",
|
|
8
|
-
"test": "
|
|
9
|
-
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "mockapi-msi",
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Mock API is a lightweight configurable HTTP API for testing and prototyping.",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node ./main.js",
|
|
8
|
+
"test": "node --test tests/*.test.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mock",
|
|
12
|
+
"api",
|
|
13
|
+
"testing",
|
|
14
|
+
"prototyping",
|
|
15
|
+
"http",
|
|
16
|
+
"rest",
|
|
17
|
+
"fake-api",
|
|
18
|
+
"stub"
|
|
19
|
+
],
|
|
20
|
+
"author": "Matias Iacono",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"bin": {
|
|
23
|
+
"mockapi": "main.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"main.js",
|
|
27
|
+
"modules/",
|
|
28
|
+
"apiHandlers/",
|
|
29
|
+
"testdata/",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"yaml": "2.2.2"
|
|
38
|
+
}
|
|
21
39
|
}
|
package/testdata/data.csv
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
id,name,money
|
|
2
|
-
1,"test 1",100
|
|
3
|
-
2,"test 2",100
|
|
4
|
-
3,"test 3",100
|
|
5
|
-
4,"test, 4",100
|
|
1
|
+
id,name,money
|
|
2
|
+
1,"test 1",100
|
|
3
|
+
2,"test 2",100
|
|
4
|
+
3,"test 3",100
|
|
5
|
+
4,"test, 4",100
|
|
6
6
|
5,"test 5",100
|
package/.dockerignore
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
**/.classpath
|
|
2
|
-
**/.dockerignore
|
|
3
|
-
**/.env
|
|
4
|
-
**/.git
|
|
5
|
-
**/.gitignore
|
|
6
|
-
**/.project
|
|
7
|
-
**/.settings
|
|
8
|
-
**/.toolstarget
|
|
9
|
-
**/.vs
|
|
10
|
-
**/.vscode
|
|
11
|
-
**/*.*proj.user
|
|
12
|
-
**/*.dbmdl
|
|
13
|
-
**/*.jfm
|
|
14
|
-
**/azds.yaml
|
|
15
|
-
**/charts
|
|
16
|
-
**/docker-compose*
|
|
17
|
-
**/compose*
|
|
18
|
-
**/Dockerfile*
|
|
19
|
-
**/node_modules
|
|
20
|
-
**/npm-debug.log
|
|
21
|
-
**/obj
|
|
22
|
-
**/secrets.dev.yaml
|
|
23
|
-
**/values.dev.yaml
|
|
24
|
-
README.md
|
package/.mockapi-config
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# basic Mock API configuration
|
|
3
|
-
port: 8001
|
|
4
|
-
enableCors: true
|
|
5
|
-
#externalModulesPath: "../apiHandlers/"
|
|
6
|
-
|
|
7
|
-
# dummy example configurations
|
|
8
|
-
# PATH must point to a existing CSV file
|
|
9
|
-
# 3 native readers are supported: text | csv | folder
|
|
10
|
-
data:
|
|
11
|
-
myRows:
|
|
12
|
-
path: "./testdata/data.csv"
|
|
13
|
-
reader: csv
|
|
14
|
-
properties:
|
|
15
|
-
- json
|
|
16
|
-
- seq
|
|
17
|
-
- 0
|
|
18
|
-
#options
|
|
19
|
-
#json || text
|
|
20
|
-
#seq || rand
|
|
21
|
-
#-1 = return every record on every request
|
|
22
|
-
#0...n starting index (always reset to 0 when reach EOR)
|
|
23
|
-
|
|
24
|
-
# custom http response handlers section
|
|
25
|
-
#customHandlers:
|
|
26
|
-
# "custom":
|
|
27
|
-
# "myCustomHandler"
|
|
28
|
-
|
|
29
|
-
# Mock API available endpoints, return types and
|
|
30
|
-
# general configuration
|
|
31
|
-
# verb: any | get | post | delete | ...
|
|
32
|
-
# data: refers to previously configured data and readers
|
|
33
|
-
# responseStatus: 200 | 404 | 500 | ...
|
|
34
|
-
# responseContentType: any MIME available type
|
|
35
|
-
# handler: refers to any previously configured custom handler
|
|
36
|
-
endpoints:
|
|
37
|
-
"/data":
|
|
38
|
-
verb: get
|
|
39
|
-
data: myRows
|
|
40
|
-
responseStatus: 200
|
|
41
|
-
responseContentType: "application/json"
|
|
42
|
-
|
|
43
|
-
log: verbose
|
|
44
|
-
#debug
|
|
45
|
-
#error
|
|
46
|
-
#verbose
|
|
47
|
-
#none
|
package/Dockerfile
DELETED
package/docker-compose.debug.yml
DELETED