@centeredgesoft/runtime-mock-routes 2.9.1 → 2.10.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/.devcontainer/devcontainer.json +31 -16
- package/build/index.js +22 -8
- package/build/run.js +87 -2
- package/build/src/app.d.ts +7 -7
- package/build/src/app.js +24 -24
- package/build/src/appV2.d.ts +8 -7
- package/build/src/appV2.js +52 -23
- package/package.json +15 -12
- package/run.ts +82 -3
- package/src/app.test.ts +1 -1
- package/src/app.ts +6 -6
- package/src/appV2.test.ts +31 -1
- package/src/appV2.ts +45 -11
- package/.devcontainer/Dockerfile +0 -16
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
|
2
2
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/typescript-node
|
|
3
3
|
{
|
|
4
|
-
"name": "
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"args": {
|
|
11
|
-
"VARIANT": "16-bullseye"
|
|
4
|
+
"name": "Aspire",
|
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/dotnet:dev-10.0-noble",
|
|
7
|
+
"features": {
|
|
8
|
+
"ghcr.io/devcontainers/features/node:1": {
|
|
9
|
+
"version": 24
|
|
12
10
|
}
|
|
13
11
|
},
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
"hostRequirements": {
|
|
14
|
+
"cpus": 4,
|
|
15
|
+
"memory": "16gb",
|
|
16
|
+
"storage": "32gb"
|
|
17
|
+
},
|
|
17
18
|
|
|
19
|
+
"onCreateCommand": "curl -sSL https://aspire.dev/install.sh | bash",
|
|
20
|
+
"postStartCommand": "dotnet dev-certs https --trust",
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
"customizations": {
|
|
23
|
+
"vscode": {
|
|
24
|
+
// Set *default* container specific settings.json values on container create.
|
|
25
|
+
"settings": {},
|
|
26
|
+
// Add the IDs of extensions you want installed when the container is created.
|
|
27
|
+
"extensions": [
|
|
28
|
+
"ms-dotnettools.csdevkit",
|
|
29
|
+
"ms-vscode.vscode-typescript-next",
|
|
30
|
+
"dbaeumer.vscode-eslint",
|
|
31
|
+
"GitHub.copilot-chat",
|
|
32
|
+
"GitHub.copilot",
|
|
33
|
+
"github.vscode-github-actions",
|
|
34
|
+
"github.vscode-pull-request-github"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
23
38
|
|
|
24
39
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
25
40
|
// "forwardPorts": [],
|
|
26
41
|
|
|
27
42
|
// Use 'postCreateCommand' to run commands after the container is created.
|
|
28
|
-
|
|
43
|
+
"postCreateCommand": "npm install"
|
|
29
44
|
|
|
30
45
|
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
|
31
|
-
"remoteUser": "node"
|
|
46
|
+
// "remoteUser": "node"
|
|
32
47
|
}
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -11,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
11
15
|
}) : function(o, v) {
|
|
12
16
|
o["default"] = v;
|
|
13
17
|
});
|
|
14
|
-
var __importStar = (this && this.__importStar) || function (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
21
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
36
|
exports.appV2 = exports.appV1 = void 0;
|
|
23
37
|
exports.appV1 = __importStar(require("./src/app"));
|
package/build/run.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
3
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const http_1 = __importDefault(require("http"));
|
|
18
|
+
const https_1 = __importDefault(require("https"));
|
|
4
19
|
const commander_1 = require("commander");
|
|
5
20
|
const path_1 = require("path");
|
|
6
21
|
const app_1 = require("./src/app");
|
|
7
22
|
const appV2_1 = require("./src/appV2");
|
|
23
|
+
const sdk_node_1 = require("@opentelemetry/sdk-node");
|
|
24
|
+
const instrumentation_express_1 = require("@opentelemetry/instrumentation-express");
|
|
25
|
+
const instrumentation_http_1 = require("@opentelemetry/instrumentation-http");
|
|
8
26
|
var AppVersion;
|
|
9
27
|
(function (AppVersion) {
|
|
10
28
|
AppVersion["v1"] = "v1";
|
|
@@ -12,12 +30,28 @@ var AppVersion;
|
|
|
12
30
|
})(AppVersion || (AppVersion = {}));
|
|
13
31
|
commander_1.program.version("2.1.0")
|
|
14
32
|
.option("-p, --port <port>", "Port to run the server one", process.env.RUNTIME_MOCK_ROUTES_PORT || "8080")
|
|
33
|
+
.option("--https-port <httpsPort>", "Enable TLS for the server", process.env.RUNTIME_MOCK_ROUTES_HTTPS_PORT || "8443")
|
|
34
|
+
.option("--tlsCert <tlsCertPath>", "Path to the TLS certificate file", process.env.HTTPS_CERT_FILE)
|
|
35
|
+
.option("--tlsKey <tlsKeyPath>", "Path to the TLS key file", process.env.HTTPS_CERT_KEY_FILE)
|
|
15
36
|
.option("-s, --seed <filePath>", "File path to seed the application", process.env.RUNTIME_MOCK_ROUTES_FILE_PATH)
|
|
16
37
|
.option("-a, --appVersion <appVersion>", "App Version to use", AppVersion.v2);
|
|
17
38
|
commander_1.program.parse(process.argv);
|
|
39
|
+
let logRecordProcessors = undefined;
|
|
40
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
41
|
+
// OTLP isn't configured, fallback to console logging
|
|
42
|
+
logRecordProcessors = [new sdk_node_1.logs.SimpleLogRecordProcessor(new sdk_node_1.logs.ConsoleLogRecordExporter())];
|
|
43
|
+
}
|
|
44
|
+
const sdk = new sdk_node_1.NodeSDK({
|
|
45
|
+
logRecordProcessors: logRecordProcessors,
|
|
46
|
+
instrumentations: [
|
|
47
|
+
new instrumentation_http_1.HttpInstrumentation(),
|
|
48
|
+
new instrumentation_express_1.ExpressInstrumentation()
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
sdk.start();
|
|
18
52
|
let initialRequests = {};
|
|
19
53
|
if (commander_1.program.seed) {
|
|
20
|
-
const filePath = path_1.resolve(commander_1.program.seed);
|
|
54
|
+
const filePath = (0, path_1.resolve)(commander_1.program.seed);
|
|
21
55
|
const funcOrJson = require(filePath);
|
|
22
56
|
initialRequests = typeof funcOrJson === 'function' ? funcOrJson() : funcOrJson;
|
|
23
57
|
}
|
|
@@ -27,6 +61,57 @@ const appVersions = {
|
|
|
27
61
|
};
|
|
28
62
|
const appFactory = appVersions[commander_1.program.appVersion] || appV2_1.appFactory;
|
|
29
63
|
const app = appFactory(initialRequests);
|
|
30
|
-
|
|
64
|
+
// Setup HTTPS options
|
|
65
|
+
const httpsOptions = commander_1.program.tlsCert && commander_1.program.tlsKey && fs_1.default.existsSync(commander_1.program.tlsCert) && fs_1.default.existsSync(commander_1.program.tlsKey)
|
|
66
|
+
? {
|
|
67
|
+
cert: fs_1.default.readFileSync(commander_1.program.tlsCert),
|
|
68
|
+
key: fs_1.default.readFileSync(commander_1.program.tlsKey),
|
|
69
|
+
enabled: true
|
|
70
|
+
}
|
|
71
|
+
: { enabled: false };
|
|
72
|
+
const httpServer = http_1.default.createServer(app);
|
|
73
|
+
const httpsServer = httpsOptions.enabled ? https_1.default.createServer(httpsOptions, app) : null;
|
|
74
|
+
httpServer.listen(commander_1.program.port, () => {
|
|
31
75
|
console.log(`⚡️[server]: Server is running at http://localhost:${commander_1.program.port}`);
|
|
32
76
|
});
|
|
77
|
+
if (httpsServer) {
|
|
78
|
+
httpsServer.listen(commander_1.program.httpsPort, () => {
|
|
79
|
+
console.log(`⚡️[server]: Server is running at https://localhost:${commander_1.program.httpsPort}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Register signal handlers
|
|
83
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
84
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
85
|
+
// Graceful shutdown handler
|
|
86
|
+
let isShuttingDown = false;
|
|
87
|
+
function gracefulShutdown(signal) {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
if (isShuttingDown)
|
|
90
|
+
return;
|
|
91
|
+
isShuttingDown = true;
|
|
92
|
+
// Close servers
|
|
93
|
+
const closePromises = [];
|
|
94
|
+
closePromises.push(closeServer(httpServer));
|
|
95
|
+
closePromises.push(closeServer(httpsServer));
|
|
96
|
+
yield Promise.all(closePromises);
|
|
97
|
+
try {
|
|
98
|
+
yield sdk.shutdown();
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.log("Error shutting down OTEL SDK", err);
|
|
102
|
+
}
|
|
103
|
+
console.log('Shutdown complete, exiting process');
|
|
104
|
+
process.exit(0);
|
|
105
|
+
function closeServer(httpServer) {
|
|
106
|
+
if (!httpServer)
|
|
107
|
+
return Promise.resolve();
|
|
108
|
+
const serverType = httpServer instanceof https_1.default.Server ? 'HTTPS' : 'HTTP';
|
|
109
|
+
return new Promise(resolve => {
|
|
110
|
+
httpServer.close(() => {
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
httpServer.closeAllConnections();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
package/build/src/app.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type SupportedMethodsType = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
2
2
|
export declare const SupportedMethodsColection: SupportedMethodsType;
|
|
3
|
-
export
|
|
4
|
-
export declare const isSupportedMethod: (obj: any) => obj is
|
|
3
|
+
export type SupportedMethod = SupportedMethodsType[number];
|
|
4
|
+
export declare const isSupportedMethod: (obj: any) => obj is SupportedMethod;
|
|
5
5
|
export interface RuntimeRequestBody {
|
|
6
6
|
path: string;
|
|
7
7
|
body: any;
|
|
@@ -13,8 +13,8 @@ export interface RuntimeRequestCollection {
|
|
|
13
13
|
[path: string]: RuntimeRequestBody;
|
|
14
14
|
}
|
|
15
15
|
export declare const isRuntimeRequestCollection: (obj: any) => obj is RuntimeRequestCollection;
|
|
16
|
-
export
|
|
17
|
-
export declare const isMethodBasedRuntimeRequestCollection: (obj: any) => obj is
|
|
16
|
+
export type MethodBasedRuntimeRequestCollection = Partial<Record<SupportedMethod, RuntimeRequestCollection>>;
|
|
17
|
+
export declare const isMethodBasedRuntimeRequestCollection: (obj: any) => obj is MethodBasedRuntimeRequestCollection;
|
|
18
18
|
interface CleanRuntimeRequestCollectionParams {
|
|
19
19
|
collection: RuntimeRequestCollection;
|
|
20
20
|
method: SupportedMethod;
|
|
@@ -22,6 +22,6 @@ interface CleanRuntimeRequestCollectionParams {
|
|
|
22
22
|
export declare const cleanRuntimeRequestCollection: (params: CleanRuntimeRequestCollectionParams) => RuntimeRequestCollection;
|
|
23
23
|
export declare const cleanMethodBasedRuntimeRequestCollection: (obj: MethodBasedRuntimeRequestCollection) => MethodBasedRuntimeRequestCollection;
|
|
24
24
|
export declare const singleQueryParam: (obj: any) => string;
|
|
25
|
-
export declare const appFactory: (runtimeCollection?: MethodBasedRuntimeRequestCollection) =>
|
|
26
|
-
declare const app:
|
|
25
|
+
export declare const appFactory: (runtimeCollection?: MethodBasedRuntimeRequestCollection) => any;
|
|
26
|
+
declare const app: any;
|
|
27
27
|
export default app;
|
package/build/src/app.js
CHANGED
|
@@ -27,7 +27,7 @@ const sort_keys_1 = __importDefault(require("sort-keys"));
|
|
|
27
27
|
const chance = new chance_1.default();
|
|
28
28
|
handlebars_1.default.registerHelper('faker', (funcName, ...rest) => {
|
|
29
29
|
try {
|
|
30
|
-
const func = lodash_1.get(faker_1.default, funcName, () => '');
|
|
30
|
+
const func = (0, lodash_1.get)(faker_1.default, funcName, () => '');
|
|
31
31
|
return func(...rest);
|
|
32
32
|
}
|
|
33
33
|
catch (err) {
|
|
@@ -36,7 +36,7 @@ handlebars_1.default.registerHelper('faker', (funcName, ...rest) => {
|
|
|
36
36
|
});
|
|
37
37
|
handlebars_1.default.registerHelper('chance', (funcName, ...rest) => {
|
|
38
38
|
try {
|
|
39
|
-
const func = lodash_1.get(chance, funcName, () => '');
|
|
39
|
+
const func = (0, lodash_1.get)(chance, funcName, () => '');
|
|
40
40
|
return func.call(chance, ...rest);
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
@@ -51,7 +51,7 @@ const isRuntimeRequestBody = (obj) => {
|
|
|
51
51
|
return typeof obj.path === 'string'
|
|
52
52
|
&& obj.hasOwnProperty('body')
|
|
53
53
|
&& (obj.status === undefined || !isNaN(Number(obj.status)))
|
|
54
|
-
&& exports.isSupportedMethod(obj.method);
|
|
54
|
+
&& (0, exports.isSupportedMethod)(obj.method);
|
|
55
55
|
}
|
|
56
56
|
catch (_) {
|
|
57
57
|
return false;
|
|
@@ -60,7 +60,7 @@ const isRuntimeRequestBody = (obj) => {
|
|
|
60
60
|
exports.isRuntimeRequestBody = isRuntimeRequestBody;
|
|
61
61
|
const isRuntimeRequestCollection = (obj) => {
|
|
62
62
|
try {
|
|
63
|
-
return !Object.keys(obj).some(k => !exports.isRuntimeRequestBody(obj[k]));
|
|
63
|
+
return !Object.keys(obj).some(k => !(0, exports.isRuntimeRequestBody)(obj[k]));
|
|
64
64
|
}
|
|
65
65
|
catch (_) {
|
|
66
66
|
return false;
|
|
@@ -69,7 +69,7 @@ const isRuntimeRequestCollection = (obj) => {
|
|
|
69
69
|
exports.isRuntimeRequestCollection = isRuntimeRequestCollection;
|
|
70
70
|
const isMethodBasedRuntimeRequestCollection = (obj) => {
|
|
71
71
|
try {
|
|
72
|
-
return !Object.keys(obj).some(k => !(exports.isSupportedMethod(k) && exports.isRuntimeRequestCollection(obj[k])));
|
|
72
|
+
return !Object.keys(obj).some(k => !((0, exports.isSupportedMethod)(k) && (0, exports.isRuntimeRequestCollection)(obj[k])));
|
|
73
73
|
}
|
|
74
74
|
catch (_) {
|
|
75
75
|
return false;
|
|
@@ -88,7 +88,7 @@ const cleanMethodBasedRuntimeRequestCollection = (obj) => {
|
|
|
88
88
|
return Object.keys(obj).reduce((acc, curr) => {
|
|
89
89
|
const method = curr;
|
|
90
90
|
const collection = obj[curr];
|
|
91
|
-
return Object.assign(Object.assign({}, acc), { [curr]: exports.cleanRuntimeRequestCollection({ method, collection }) });
|
|
91
|
+
return Object.assign(Object.assign({}, acc), { [curr]: (0, exports.cleanRuntimeRequestCollection)({ method, collection }) });
|
|
92
92
|
}, {});
|
|
93
93
|
};
|
|
94
94
|
exports.cleanMethodBasedRuntimeRequestCollection = cleanMethodBasedRuntimeRequestCollection;
|
|
@@ -96,17 +96,17 @@ const singleQueryParam = (obj) => Array.isArray(obj) ? `${obj[0]}` : `${obj}`;
|
|
|
96
96
|
exports.singleQueryParam = singleQueryParam;
|
|
97
97
|
const appFactory = (runtimeCollection) => {
|
|
98
98
|
let runtimeRequestCollection = runtimeCollection || {};
|
|
99
|
-
if (!exports.isMethodBasedRuntimeRequestCollection(runtimeRequestCollection)) {
|
|
99
|
+
if (!(0, exports.isMethodBasedRuntimeRequestCollection)(runtimeRequestCollection)) {
|
|
100
100
|
throw new Error('Intial requests JSON is invalid');
|
|
101
101
|
}
|
|
102
|
-
runtimeRequestCollection = exports.cleanMethodBasedRuntimeRequestCollection(runtimeRequestCollection);
|
|
103
|
-
const app = express_1.default();
|
|
104
|
-
app.use(cors_1.default());
|
|
102
|
+
runtimeRequestCollection = (0, exports.cleanMethodBasedRuntimeRequestCollection)(runtimeRequestCollection);
|
|
103
|
+
const app = (0, express_1.default)();
|
|
104
|
+
app.use((0, cors_1.default)());
|
|
105
105
|
app.use(body_parser_1.default.json());
|
|
106
|
-
app.get('/', (_req, res) => res.send(sort_keys_1.default(runtimeRequestCollection, { deep: true })));
|
|
106
|
+
app.get('/', (_req, res) => res.send((0, sort_keys_1.default)(runtimeRequestCollection, { deep: true })));
|
|
107
107
|
app.post('/', (req, res) => {
|
|
108
108
|
const { body } = req;
|
|
109
|
-
if (exports.isRuntimeRequestBody(body)) {
|
|
109
|
+
if ((0, exports.isRuntimeRequestBody)(body)) {
|
|
110
110
|
const fixedPath = `/${body.path}`.replace(/\/\//g, '/');
|
|
111
111
|
body.path = fixedPath;
|
|
112
112
|
const method = body.method;
|
|
@@ -120,9 +120,9 @@ const appFactory = (runtimeCollection) => {
|
|
|
120
120
|
if (!path || !method) {
|
|
121
121
|
return res.status(400).send();
|
|
122
122
|
}
|
|
123
|
-
const cleanPath = exports.singleQueryParam(path);
|
|
124
|
-
const cleanMethod = exports.singleQueryParam(method);
|
|
125
|
-
if (!exports.isSupportedMethod(cleanMethod)) {
|
|
123
|
+
const cleanPath = (0, exports.singleQueryParam)(path);
|
|
124
|
+
const cleanMethod = (0, exports.singleQueryParam)(method);
|
|
125
|
+
if (!(0, exports.isSupportedMethod)(cleanMethod)) {
|
|
126
126
|
return res.status(400).send();
|
|
127
127
|
}
|
|
128
128
|
if (!Object.keys(runtimeRequestCollection).includes(cleanMethod)) {
|
|
@@ -135,14 +135,14 @@ const appFactory = (runtimeCollection) => {
|
|
|
135
135
|
});
|
|
136
136
|
const catchAllHandler = (req, res) => {
|
|
137
137
|
const method = req.method;
|
|
138
|
-
if (!exports.isSupportedMethod(method)) {
|
|
138
|
+
if (!(0, exports.isSupportedMethod)(method)) {
|
|
139
139
|
return res.status(400).send();
|
|
140
140
|
}
|
|
141
141
|
const collection = runtimeRequestCollection[method];
|
|
142
142
|
if (!collection) {
|
|
143
143
|
return res.status(404).send();
|
|
144
144
|
}
|
|
145
|
-
const matcher = feather_route_matcher_1.default(collection);
|
|
145
|
+
const matcher = (0, feather_route_matcher_1.default)(collection);
|
|
146
146
|
const routeMatch = matcher(req.path);
|
|
147
147
|
if (routeMatch) {
|
|
148
148
|
const reqInfo = routeMatch.value;
|
|
@@ -171,18 +171,18 @@ const appFactory = (runtimeCollection) => {
|
|
|
171
171
|
}
|
|
172
172
|
return res.status(404).send();
|
|
173
173
|
};
|
|
174
|
-
app.get(
|
|
175
|
-
app.post(
|
|
176
|
-
app.put(
|
|
177
|
-
app.patch(
|
|
178
|
-
app.delete(
|
|
174
|
+
app.get(/\/.*/, catchAllHandler);
|
|
175
|
+
app.post(/\/.*/, catchAllHandler);
|
|
176
|
+
app.put(/\/.*/, catchAllHandler);
|
|
177
|
+
app.patch(/\/.*/, catchAllHandler);
|
|
178
|
+
app.delete(/\/.*/, catchAllHandler);
|
|
179
179
|
Object.defineProperty(app, 'runtimeRequestCollection', {
|
|
180
180
|
get: () => {
|
|
181
|
-
return lodash_1.cloneDeep(runtimeRequestCollection);
|
|
181
|
+
return (0, lodash_1.cloneDeep)(runtimeRequestCollection);
|
|
182
182
|
}
|
|
183
183
|
});
|
|
184
184
|
return app;
|
|
185
185
|
};
|
|
186
186
|
exports.appFactory = appFactory;
|
|
187
|
-
const app = exports.appFactory();
|
|
187
|
+
const app = (0, exports.appFactory)();
|
|
188
188
|
exports.default = app;
|
package/build/src/appV2.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
type SupportedMethodsType = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
2
2
|
export declare const SupportedMethodsColection: SupportedMethodsType;
|
|
3
|
-
export
|
|
4
|
-
export declare const isSupportedMethod: (obj: any) => obj is
|
|
3
|
+
export type SupportedMethod = SupportedMethodsType[number];
|
|
4
|
+
export declare const isSupportedMethod: (obj: any) => obj is SupportedMethod;
|
|
5
5
|
export interface RuntimeRequestMethodBody {
|
|
6
6
|
body: any;
|
|
7
7
|
status?: any;
|
|
8
8
|
headers?: any;
|
|
9
|
+
raw?: boolean;
|
|
9
10
|
}
|
|
10
11
|
export interface RequestParameters {
|
|
11
12
|
query: any;
|
|
@@ -14,8 +15,8 @@ export interface RequestParameters {
|
|
|
14
15
|
headers: any;
|
|
15
16
|
}
|
|
16
17
|
export declare const isRuntimeRequestMethodBody: (obj: any) => obj is RuntimeRequestMethodBody;
|
|
17
|
-
export
|
|
18
|
-
export declare const isRuntimeRequestMethodBodyCollection: (obj: any) => obj is
|
|
18
|
+
export type RuntimeRequestMethodBodyCollection = Partial<Record<SupportedMethod, RuntimeRequestMethodBody>>;
|
|
19
|
+
export declare const isRuntimeRequestMethodBodyCollection: (obj: any) => obj is RuntimeRequestMethodBodyCollection;
|
|
19
20
|
export interface RuntimeRequestBody {
|
|
20
21
|
path: string;
|
|
21
22
|
methods: RuntimeRequestMethodBodyCollection;
|
|
@@ -25,6 +26,6 @@ export interface RuntimeRequestCollection {
|
|
|
25
26
|
[path: string]: RuntimeRequestBody;
|
|
26
27
|
}
|
|
27
28
|
export declare const isRuntimeRequestCollection: (obj: any) => obj is RuntimeRequestCollection;
|
|
28
|
-
export declare const appFactory: (runtimeCollection?: RuntimeRequestCollection) =>
|
|
29
|
-
declare const app:
|
|
29
|
+
export declare const appFactory: (runtimeCollection?: RuntimeRequestCollection) => any;
|
|
30
|
+
declare const app: any;
|
|
30
31
|
export default app;
|
package/build/src/appV2.js
CHANGED
|
@@ -23,10 +23,11 @@ const feather_route_matcher_1 = __importDefault(require("feather-route-matcher")
|
|
|
23
23
|
const handlebars_1 = __importDefault(require("handlebars"));
|
|
24
24
|
const lodash_1 = require("lodash");
|
|
25
25
|
const sort_keys_1 = __importDefault(require("sort-keys"));
|
|
26
|
+
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
26
27
|
const chance = new chance_1.default();
|
|
27
28
|
handlebars_1.default.registerHelper('faker', (funcName, ...rest) => {
|
|
28
29
|
try {
|
|
29
|
-
const func = lodash_1.get(faker_1.default, funcName, () => '');
|
|
30
|
+
const func = (0, lodash_1.get)(faker_1.default, funcName, () => '');
|
|
30
31
|
return func(...rest);
|
|
31
32
|
}
|
|
32
33
|
catch (err) {
|
|
@@ -35,7 +36,7 @@ handlebars_1.default.registerHelper('faker', (funcName, ...rest) => {
|
|
|
35
36
|
});
|
|
36
37
|
handlebars_1.default.registerHelper('chance', (funcName, ...rest) => {
|
|
37
38
|
try {
|
|
38
|
-
const func = lodash_1.get(chance, funcName, () => '');
|
|
39
|
+
const func = (0, lodash_1.get)(chance, funcName, () => '');
|
|
39
40
|
return func.call(chance, ...rest);
|
|
40
41
|
}
|
|
41
42
|
catch (err) {
|
|
@@ -56,7 +57,7 @@ const isRuntimeRequestMethodBody = (obj) => {
|
|
|
56
57
|
exports.isRuntimeRequestMethodBody = isRuntimeRequestMethodBody;
|
|
57
58
|
const isRuntimeRequestMethodBodyCollection = (obj) => {
|
|
58
59
|
try {
|
|
59
|
-
return !Object.keys(obj).some(k => !(exports.isSupportedMethod(k) && exports.isRuntimeRequestMethodBody(obj[k])));
|
|
60
|
+
return !Object.keys(obj).some(k => !((0, exports.isSupportedMethod)(k) && (0, exports.isRuntimeRequestMethodBody)(obj[k])));
|
|
60
61
|
}
|
|
61
62
|
catch (_) {
|
|
62
63
|
return false;
|
|
@@ -66,7 +67,7 @@ exports.isRuntimeRequestMethodBodyCollection = isRuntimeRequestMethodBodyCollect
|
|
|
66
67
|
const isRuntimeRequestBody = (obj) => {
|
|
67
68
|
try {
|
|
68
69
|
return typeof obj.path === 'string'
|
|
69
|
-
&& exports.isRuntimeRequestMethodBodyCollection(obj.methods);
|
|
70
|
+
&& (0, exports.isRuntimeRequestMethodBodyCollection)(obj.methods);
|
|
70
71
|
}
|
|
71
72
|
catch (_) {
|
|
72
73
|
return false;
|
|
@@ -75,7 +76,7 @@ const isRuntimeRequestBody = (obj) => {
|
|
|
75
76
|
exports.isRuntimeRequestBody = isRuntimeRequestBody;
|
|
76
77
|
const isRuntimeRequestCollection = (obj) => {
|
|
77
78
|
try {
|
|
78
|
-
return !Object.keys(obj).some(k => !exports.isRuntimeRequestBody(obj[k]));
|
|
79
|
+
return !Object.keys(obj).some(k => !(0, exports.isRuntimeRequestBody)(obj[k]));
|
|
79
80
|
}
|
|
80
81
|
catch (_) {
|
|
81
82
|
return false;
|
|
@@ -84,7 +85,7 @@ const isRuntimeRequestCollection = (obj) => {
|
|
|
84
85
|
exports.isRuntimeRequestCollection = isRuntimeRequestCollection;
|
|
85
86
|
const appFactory = (runtimeCollection) => {
|
|
86
87
|
let runtimeRequestCollection = runtimeCollection || {};
|
|
87
|
-
if (!exports.isRuntimeRequestCollection(runtimeRequestCollection)) {
|
|
88
|
+
if (!(0, exports.isRuntimeRequestCollection)(runtimeRequestCollection)) {
|
|
88
89
|
throw new Error('Intial requests JSON is invalid');
|
|
89
90
|
}
|
|
90
91
|
runtimeRequestCollection = Object.keys(runtimeRequestCollection).reduce((acc, curr) => {
|
|
@@ -92,31 +93,56 @@ const appFactory = (runtimeCollection) => {
|
|
|
92
93
|
const body = Object.assign(Object.assign({}, runtimeCollection[curr]), { path: fixedPath });
|
|
93
94
|
return Object.assign(Object.assign({}, acc), { [fixedPath]: body });
|
|
94
95
|
}, {});
|
|
95
|
-
const app = express_1.default();
|
|
96
|
-
app.use(cors_1.default());
|
|
96
|
+
const app = (0, express_1.default)();
|
|
97
|
+
app.use((0, cors_1.default)());
|
|
97
98
|
app.use(express_1.default.json({
|
|
98
99
|
strict: false,
|
|
99
100
|
type: ['application/json', 'application/*+json', 'text/json']
|
|
100
101
|
}));
|
|
101
102
|
// log the request
|
|
102
103
|
if (process.env.LOG_MOCK_REQUESTS === 'true') {
|
|
104
|
+
const logger = api_logs_1.logs.getLogger('mock-requests-logger');
|
|
103
105
|
app.use((req, res, next) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// log the request
|
|
107
|
+
logger.emit({
|
|
108
|
+
severityNumber: api_logs_1.SeverityNumber.INFO,
|
|
109
|
+
severityText: 'INFO',
|
|
110
|
+
body: `Request ${req.path}`,
|
|
111
|
+
attributes: {
|
|
112
|
+
'http.headers': req.headers,
|
|
113
|
+
'http.method': req.method,
|
|
114
|
+
'http.url': req.url,
|
|
115
|
+
'http.request_body': req.body,
|
|
116
|
+
}
|
|
117
|
+
});
|
|
107
118
|
// log the response
|
|
108
119
|
const originalSend = res.send;
|
|
120
|
+
let firstSend = true;
|
|
109
121
|
res.send = function (body) {
|
|
110
|
-
|
|
122
|
+
if (firstSend) {
|
|
123
|
+
// For some reason send gets called twice per request, this ensures we only log once
|
|
124
|
+
firstSend = false;
|
|
125
|
+
logger.emit({
|
|
126
|
+
severityNumber: api_logs_1.SeverityNumber.INFO,
|
|
127
|
+
severityText: 'INFO',
|
|
128
|
+
body: `Response ${req.path}`,
|
|
129
|
+
attributes: {
|
|
130
|
+
'http.status_code': res.statusCode,
|
|
131
|
+
'http.method': req.method,
|
|
132
|
+
'http.url': req.url,
|
|
133
|
+
'http.response_body': body,
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
111
137
|
return originalSend.apply(this, arguments);
|
|
112
138
|
};
|
|
113
139
|
next();
|
|
114
140
|
});
|
|
115
141
|
}
|
|
116
|
-
app.get('/', (_req, res) => res.send(sort_keys_1.default(runtimeRequestCollection, { deep: true })));
|
|
142
|
+
app.get('/', (_req, res) => res.send((0, sort_keys_1.default)(runtimeRequestCollection, { deep: true })));
|
|
117
143
|
app.post('/', (req, res) => {
|
|
118
144
|
const { body } = req;
|
|
119
|
-
if (exports.isRuntimeRequestBody(body)) {
|
|
145
|
+
if ((0, exports.isRuntimeRequestBody)(body)) {
|
|
120
146
|
const fixedPath = `/${body.path}`.replace(/\/\//g, '/');
|
|
121
147
|
body.path = fixedPath;
|
|
122
148
|
runtimeRequestCollection = Object.assign(Object.assign({}, runtimeRequestCollection), { [fixedPath]: body });
|
|
@@ -126,7 +152,7 @@ const appFactory = (runtimeCollection) => {
|
|
|
126
152
|
});
|
|
127
153
|
app.put('/', (req, res) => {
|
|
128
154
|
const { body } = req;
|
|
129
|
-
if (!exports.isRuntimeRequestCollection(body)) {
|
|
155
|
+
if (!(0, exports.isRuntimeRequestCollection)(body)) {
|
|
130
156
|
return res.status(400).send('BAD_REQUEST_BODY');
|
|
131
157
|
}
|
|
132
158
|
const cleanBody = Object.keys(body).reduce((acc, curr) => {
|
|
@@ -150,7 +176,7 @@ const appFactory = (runtimeCollection) => {
|
|
|
150
176
|
return res.status(204).send();
|
|
151
177
|
});
|
|
152
178
|
const catchAllHandler = (req, res) => {
|
|
153
|
-
const matcher = feather_route_matcher_1.default(runtimeRequestCollection);
|
|
179
|
+
const matcher = (0, feather_route_matcher_1.default)(runtimeRequestCollection);
|
|
154
180
|
const routeMatch = matcher(req.path);
|
|
155
181
|
if (routeMatch) {
|
|
156
182
|
const reqInfo = routeMatch.value;
|
|
@@ -191,6 +217,9 @@ const appFactory = (runtimeCollection) => {
|
|
|
191
217
|
const template = handlebars_1.default.compile(body);
|
|
192
218
|
const result = template(tokenParams);
|
|
193
219
|
try {
|
|
220
|
+
if (method.raw) {
|
|
221
|
+
return res.send(result);
|
|
222
|
+
}
|
|
194
223
|
const jsonResult = JSON.parse(result);
|
|
195
224
|
return res.send(jsonResult);
|
|
196
225
|
}
|
|
@@ -200,18 +229,18 @@ const appFactory = (runtimeCollection) => {
|
|
|
200
229
|
}
|
|
201
230
|
return res.status(404).send();
|
|
202
231
|
};
|
|
203
|
-
app.get(
|
|
204
|
-
app.post(
|
|
205
|
-
app.put(
|
|
206
|
-
app.patch(
|
|
207
|
-
app.delete(
|
|
232
|
+
app.get(/\/.*/, catchAllHandler);
|
|
233
|
+
app.post(/\/.*/, catchAllHandler);
|
|
234
|
+
app.put(/\/.*/, catchAllHandler);
|
|
235
|
+
app.patch(/\/.*/, catchAllHandler);
|
|
236
|
+
app.delete(/\/.*/, catchAllHandler);
|
|
208
237
|
Object.defineProperty(app, 'runtimeRequestCollection', {
|
|
209
238
|
get: () => {
|
|
210
|
-
return lodash_1.cloneDeep(runtimeRequestCollection);
|
|
239
|
+
return (0, lodash_1.cloneDeep)(runtimeRequestCollection);
|
|
211
240
|
}
|
|
212
241
|
});
|
|
213
242
|
return app;
|
|
214
243
|
};
|
|
215
244
|
exports.appFactory = appFactory;
|
|
216
|
-
const app = exports.appFactory();
|
|
245
|
+
const app = (0, exports.appFactory)();
|
|
217
246
|
exports.default = app;
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centeredgesoft/runtime-mock-routes",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc --project ./",
|
|
8
8
|
"start": "nodemon -r dotenv/config index.ts",
|
|
9
|
-
"test": "jest"
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"serve": "ts-node run.ts -s ./example/seed.js"
|
|
10
11
|
},
|
|
11
12
|
"bin": {
|
|
12
13
|
"runtime-mock-routes": "./build/run.js"
|
|
@@ -23,10 +24,13 @@
|
|
|
23
24
|
},
|
|
24
25
|
"homepage": "https://github.com/CenterEdge/runtime-mock-routes#readme",
|
|
25
26
|
"dependencies": {
|
|
27
|
+
"@opentelemetry/instrumentation-express": "^0.57.0",
|
|
28
|
+
"@opentelemetry/instrumentation-http": "^0.208.0",
|
|
29
|
+
"@opentelemetry/sdk-node": "^0.208.0",
|
|
26
30
|
"chance": "^1.1.7",
|
|
27
31
|
"commander": "^6.2.1",
|
|
28
32
|
"cors": "^2.8.5",
|
|
29
|
-
"express": "^
|
|
33
|
+
"express": "^5.2.1",
|
|
30
34
|
"faker": "^5.1.0",
|
|
31
35
|
"feather-route-matcher": "^4.0.0",
|
|
32
36
|
"handlebars": "^4.7.6",
|
|
@@ -36,18 +40,17 @@
|
|
|
36
40
|
"devDependencies": {
|
|
37
41
|
"@types/chance": "^1.1.1",
|
|
38
42
|
"@types/cors": "^2.8.9",
|
|
39
|
-
"@types/express": "^4.17.11",
|
|
40
43
|
"@types/faker": "^5.1.5",
|
|
41
|
-
"@types/jest": "^
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
42
45
|
"@types/lodash": "^4.14.167",
|
|
43
|
-
"@types/node": "^
|
|
46
|
+
"@types/node": "^22.9.0",
|
|
44
47
|
"@types/supertest": "^2.0.10",
|
|
45
|
-
"dotenv": "^
|
|
46
|
-
"jest": "^
|
|
47
|
-
"nodemon": "^
|
|
48
|
+
"dotenv": "^17.2.3",
|
|
49
|
+
"jest": "^30.2.0",
|
|
50
|
+
"nodemon": "^3.1.11",
|
|
48
51
|
"supertest": "^6.0.1",
|
|
49
|
-
"ts-jest": "^
|
|
50
|
-
"ts-node": "^9.
|
|
51
|
-
"typescript": "^
|
|
52
|
+
"ts-jest": "^29.4.6",
|
|
53
|
+
"ts-node": "^10.9.2",
|
|
54
|
+
"typescript": "^5.9.3"
|
|
52
55
|
}
|
|
53
56
|
}
|
package/run.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import https from 'https';
|
|
2
5
|
import { program } from 'commander';
|
|
3
6
|
import { resolve } from 'path';
|
|
4
7
|
import { appFactory as appFactoryV1 } from './src/app';
|
|
5
8
|
import { appFactory as appFactoryV2 } from './src/appV2';
|
|
6
9
|
import { Express } from 'express'
|
|
10
|
+
import { NodeSDK, logs } from '@opentelemetry/sdk-node';
|
|
11
|
+
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
|
|
12
|
+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
7
13
|
|
|
8
14
|
enum AppVersion {
|
|
9
15
|
v1 = 'v1',
|
|
@@ -12,10 +18,30 @@ enum AppVersion {
|
|
|
12
18
|
|
|
13
19
|
program.version("2.1.0")
|
|
14
20
|
.option("-p, --port <port>", "Port to run the server one", process.env.RUNTIME_MOCK_ROUTES_PORT || "8080")
|
|
21
|
+
.option("--https-port <httpsPort>", "Enable TLS for the server", process.env.RUNTIME_MOCK_ROUTES_HTTPS_PORT || "8443")
|
|
22
|
+
.option("--tlsCert <tlsCertPath>", "Path to the TLS certificate file", process.env.HTTPS_CERT_FILE)
|
|
23
|
+
.option("--tlsKey <tlsKeyPath>", "Path to the TLS key file", process.env.HTTPS_CERT_KEY_FILE)
|
|
15
24
|
.option("-s, --seed <filePath>", "File path to seed the application", process.env.RUNTIME_MOCK_ROUTES_FILE_PATH)
|
|
16
25
|
.option("-a, --appVersion <appVersion>", "App Version to use", AppVersion.v2)
|
|
17
26
|
|
|
18
27
|
program.parse(process.argv);
|
|
28
|
+
|
|
29
|
+
let logRecordProcessors: logs.LogRecordProcessor[] | undefined = undefined;
|
|
30
|
+
if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
31
|
+
// OTLP isn't configured, fallback to console logging
|
|
32
|
+
logRecordProcessors = [new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter())];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sdk = new NodeSDK({
|
|
36
|
+
logRecordProcessors: logRecordProcessors,
|
|
37
|
+
instrumentations: [
|
|
38
|
+
new HttpInstrumentation(),
|
|
39
|
+
new ExpressInstrumentation()
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
sdk.start()
|
|
44
|
+
|
|
19
45
|
let initialRequests: any = {};
|
|
20
46
|
if (program.seed) {
|
|
21
47
|
const filePath = resolve(program.seed);
|
|
@@ -31,9 +57,62 @@ const appVersions: Record<AppVersion, AppFactory> = {
|
|
|
31
57
|
}
|
|
32
58
|
const appFactory = appVersions[program.appVersion] || appFactoryV2;
|
|
33
59
|
|
|
34
|
-
|
|
35
60
|
const app = appFactory(initialRequests);
|
|
36
61
|
|
|
37
|
-
|
|
62
|
+
// Setup HTTPS options
|
|
63
|
+
const httpsOptions = program.tlsCert && program.tlsKey && fs.existsSync(program.tlsCert) && fs.existsSync(program.tlsKey)
|
|
64
|
+
? {
|
|
65
|
+
cert: fs.readFileSync(program.tlsCert),
|
|
66
|
+
key: fs.readFileSync(program.tlsKey),
|
|
67
|
+
enabled: true
|
|
68
|
+
}
|
|
69
|
+
: { enabled: false };
|
|
70
|
+
|
|
71
|
+
const httpServer = http.createServer(app);
|
|
72
|
+
const httpsServer = httpsOptions.enabled ? https.createServer(httpsOptions, app) : null;
|
|
73
|
+
|
|
74
|
+
httpServer.listen(program.port, () => {
|
|
38
75
|
console.log(`⚡️[server]: Server is running at http://localhost:${program.port}`);
|
|
39
|
-
});
|
|
76
|
+
});
|
|
77
|
+
if (httpsServer) {
|
|
78
|
+
httpsServer.listen(program.httpsPort, () => {
|
|
79
|
+
console.log(`⚡️[server]: Server is running at https://localhost:${program.httpsPort}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Register signal handlers
|
|
84
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
85
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
86
|
+
|
|
87
|
+
// Graceful shutdown handler
|
|
88
|
+
let isShuttingDown = false;
|
|
89
|
+
async function gracefulShutdown(signal) {
|
|
90
|
+
if (isShuttingDown) return;
|
|
91
|
+
isShuttingDown = true;
|
|
92
|
+
|
|
93
|
+
// Close servers
|
|
94
|
+
const closePromises = [];
|
|
95
|
+
closePromises.push(closeServer(httpServer));
|
|
96
|
+
closePromises.push(closeServer(httpsServer));
|
|
97
|
+
await Promise.all(closePromises);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await sdk.shutdown();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.log("Error shutting down OTEL SDK", err);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('Shutdown complete, exiting process');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
|
|
108
|
+
function closeServer(httpServer) {
|
|
109
|
+
if (!httpServer) return Promise.resolve();
|
|
110
|
+
const serverType = httpServer instanceof https.Server ? 'HTTPS' : 'HTTP'
|
|
111
|
+
return new Promise<void>(resolve => {
|
|
112
|
+
httpServer.close(() => {
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
httpServer.closeAllConnections();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/app.test.ts
CHANGED
package/src/app.ts
CHANGED
|
@@ -216,12 +216,12 @@ export const appFactory = (runtimeCollection?: MethodBasedRuntimeRequestCollecti
|
|
|
216
216
|
return res.status(404).send();
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
app.get(
|
|
220
|
-
app.post(
|
|
221
|
-
app.put(
|
|
222
|
-
app.patch(
|
|
223
|
-
app.delete(
|
|
224
|
-
|
|
219
|
+
app.get(/\/.*/, catchAllHandler);
|
|
220
|
+
app.post(/\/.*/, catchAllHandler);
|
|
221
|
+
app.put(/\/.*/, catchAllHandler);
|
|
222
|
+
app.patch(/\/.*/, catchAllHandler);
|
|
223
|
+
app.delete(/\/.*/, catchAllHandler);
|
|
224
|
+
|
|
225
225
|
Object.defineProperty(app, 'runtimeRequestCollection', {
|
|
226
226
|
get: () => {
|
|
227
227
|
return cloneDeep(runtimeRequestCollection);
|
package/src/appV2.test.ts
CHANGED
|
@@ -64,7 +64,7 @@ describe('appFactory', () => {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
expect(() => appFactory(seed as any)).
|
|
67
|
+
expect(() => appFactory(seed as any)).toThrow('Intial requests JSON is invalid')
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
@@ -275,6 +275,36 @@ describe('Body as Function', () => {
|
|
|
275
275
|
})
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
+
describe('Body returns quoted JSON when sending raw', () => {
|
|
279
|
+
test('Return JSON stringified string with quotes', async () => {
|
|
280
|
+
const seed: RuntimeRequestCollection = {
|
|
281
|
+
"/test": {
|
|
282
|
+
path: "/test",
|
|
283
|
+
methods: {
|
|
284
|
+
GET: {
|
|
285
|
+
body: function(rp: RequestParameters) { return JSON.stringify(`"<p>Test</p>"`) },
|
|
286
|
+
status: 200,
|
|
287
|
+
headers: () => { return { 'Content-Type': 'application/json; charset=utf-8' } },
|
|
288
|
+
raw: true
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const app = appFactory(seed);
|
|
295
|
+
expect(app).toBeDefined();
|
|
296
|
+
|
|
297
|
+
var response = await request(app).get('/test');
|
|
298
|
+
|
|
299
|
+
expect(response.body).toEqual(`"<p>Test</p>"`);
|
|
300
|
+
expect(response.body).toHaveLength(13)
|
|
301
|
+
expect(response.body[0]).toBe(`"`);
|
|
302
|
+
expect(response.body[1]).toBe(`<`);
|
|
303
|
+
expect(response.body[11]).toBe(`>`);
|
|
304
|
+
expect(response.body[12]).toBe(`"`);
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
|
|
278
308
|
describe('Request Body is Simple Type', () => {
|
|
279
309
|
test('Send in string as JSON', async () => {
|
|
280
310
|
const seed: RuntimeRequestCollection = {
|
package/src/appV2.ts
CHANGED
|
@@ -6,6 +6,7 @@ import createMatcher from 'feather-route-matcher';
|
|
|
6
6
|
import Handlebars from 'handlebars';
|
|
7
7
|
import { cloneDeep, get } from 'lodash';
|
|
8
8
|
import sortKeys from 'sort-keys';
|
|
9
|
+
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
|
|
9
10
|
|
|
10
11
|
const chance = new Chance();
|
|
11
12
|
|
|
@@ -37,6 +38,7 @@ export interface RuntimeRequestMethodBody {
|
|
|
37
38
|
body: any;
|
|
38
39
|
status?: any;
|
|
39
40
|
headers?: any;
|
|
41
|
+
raw?: boolean;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export interface RequestParameters {
|
|
@@ -116,15 +118,43 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
|
|
|
116
118
|
|
|
117
119
|
// log the request
|
|
118
120
|
if (process.env.LOG_MOCK_REQUESTS === 'true') {
|
|
121
|
+
const logger = logs.getLogger('mock-requests-logger');
|
|
122
|
+
|
|
119
123
|
app.use((req, res, next) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
// log the request
|
|
125
|
+
logger.emit({
|
|
126
|
+
severityNumber: SeverityNumber.INFO,
|
|
127
|
+
severityText: 'INFO',
|
|
128
|
+
body: `Request ${req.path}`,
|
|
129
|
+
attributes: {
|
|
130
|
+
'http.headers': req.headers,
|
|
131
|
+
'http.method': req.method,
|
|
132
|
+
'http.url': req.url,
|
|
133
|
+
'http.request_body': req.body,
|
|
134
|
+
}
|
|
135
|
+
});
|
|
123
136
|
|
|
124
137
|
// log the response
|
|
125
138
|
const originalSend = res.send;
|
|
139
|
+
let firstSend = true;
|
|
126
140
|
res.send = function (body) {
|
|
127
|
-
|
|
141
|
+
if (firstSend) {
|
|
142
|
+
// For some reason send gets called twice per request, this ensures we only log once
|
|
143
|
+
firstSend = false;
|
|
144
|
+
|
|
145
|
+
logger.emit({
|
|
146
|
+
severityNumber: SeverityNumber.INFO,
|
|
147
|
+
severityText: 'INFO',
|
|
148
|
+
body: `Response ${req.path}`,
|
|
149
|
+
attributes: {
|
|
150
|
+
'http.status_code': res.statusCode,
|
|
151
|
+
'http.method': req.method,
|
|
152
|
+
'http.url': req.url,
|
|
153
|
+
'http.response_body': body,
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
128
158
|
return originalSend.apply(this, arguments);
|
|
129
159
|
};
|
|
130
160
|
|
|
@@ -218,7 +248,7 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
|
|
|
218
248
|
const status = typeof method.status === 'function'
|
|
219
249
|
? method.status(tokenParams)
|
|
220
250
|
: method.status
|
|
221
|
-
|
|
251
|
+
|
|
222
252
|
res.status(status);
|
|
223
253
|
}
|
|
224
254
|
|
|
@@ -252,6 +282,10 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
|
|
|
252
282
|
const result = template(tokenParams)
|
|
253
283
|
|
|
254
284
|
try {
|
|
285
|
+
if (method.raw) {
|
|
286
|
+
return res.send(result);
|
|
287
|
+
}
|
|
288
|
+
|
|
255
289
|
const jsonResult = JSON.parse(result);
|
|
256
290
|
return res.send(jsonResult);
|
|
257
291
|
} catch (_) {
|
|
@@ -262,12 +296,12 @@ export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
|
|
|
262
296
|
return res.status(404).send();
|
|
263
297
|
}
|
|
264
298
|
|
|
265
|
-
app.get(
|
|
266
|
-
app.post(
|
|
267
|
-
app.put(
|
|
268
|
-
app.patch(
|
|
269
|
-
app.delete(
|
|
270
|
-
|
|
299
|
+
app.get(/\/.*/, catchAllHandler);
|
|
300
|
+
app.post(/\/.*/, catchAllHandler);
|
|
301
|
+
app.put(/\/.*/, catchAllHandler);
|
|
302
|
+
app.patch(/\/.*/, catchAllHandler);
|
|
303
|
+
app.delete(/\/.*/, catchAllHandler);
|
|
304
|
+
|
|
271
305
|
Object.defineProperty(app, 'runtimeRequestCollection', {
|
|
272
306
|
get: () => {
|
|
273
307
|
return cloneDeep(runtimeRequestCollection);
|
package/.devcontainer/Dockerfile
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/typescript-node/.devcontainer/base.Dockerfile
|
|
2
|
-
|
|
3
|
-
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster
|
|
4
|
-
ARG VARIANT="16-bullseye"
|
|
5
|
-
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
|
6
|
-
|
|
7
|
-
# [Optional] Uncomment this section to install additional OS packages.
|
|
8
|
-
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
9
|
-
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
|
10
|
-
|
|
11
|
-
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
|
12
|
-
# ARG EXTRA_NODE_VERSION=10
|
|
13
|
-
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
|
14
|
-
|
|
15
|
-
# [Optional] Uncomment if you want to install more global node packages
|
|
16
|
-
# RUN su node -c "npm install -g <your-package-list -here>"
|