@centeredgesoft/runtime-mock-routes 2.1.0 → 2.5.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/Dockerfile +16 -0
- package/.devcontainer/devcontainer.json +32 -0
- package/LICENSE +20 -20
- package/README.md +100 -93
- package/build/index.d.ts +2 -2
- package/build/index.js +24 -24
- package/build/run.d.ts +2 -2
- package/build/run.js +32 -32
- package/build/src/app.d.ts +27 -27
- package/build/src/app.js +188 -180
- package/build/src/appV2.d.ts +30 -23
- package/build/src/appV2.js +196 -177
- package/index.ts +1 -1
- package/jest.config.js +3 -3
- package/package.json +53 -55
- package/run.ts +38 -38
- package/src/app.test.ts +212 -212
- package/src/app.ts +234 -234
- package/src/appV2.test.ts +320 -261
- package/src/appV2.ts +257 -227
- package/tsconfig.json +15 -15
- package/.env +0 -0
package/src/appV2.ts
CHANGED
|
@@ -1,228 +1,258 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
app
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
1
|
+
import Chance from 'chance';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import express, { Request, Response } from 'express';
|
|
4
|
+
import faker from 'faker';
|
|
5
|
+
import createMatcher from 'feather-route-matcher';
|
|
6
|
+
import Handlebars from 'handlebars';
|
|
7
|
+
import { cloneDeep, get } from 'lodash';
|
|
8
|
+
import sortKeys from 'sort-keys';
|
|
9
|
+
|
|
10
|
+
const chance = new Chance();
|
|
11
|
+
|
|
12
|
+
Handlebars.registerHelper('faker', (funcName: string, ...rest: any[]) => {
|
|
13
|
+
try {
|
|
14
|
+
const func = get(faker, funcName, () => '');
|
|
15
|
+
return func(...rest);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return err;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
Handlebars.registerHelper('chance', (funcName: string, ...rest: any[]) => {
|
|
22
|
+
try {
|
|
23
|
+
const func = get(chance, funcName, () => '');
|
|
24
|
+
return func.call(chance, ...rest);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return err
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
type SupportedMethodsType = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
31
|
+
export const SupportedMethodsColection: SupportedMethodsType = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
32
|
+
export type SupportedMethod = SupportedMethodsType[number];
|
|
33
|
+
|
|
34
|
+
export const isSupportedMethod = (obj: any): obj is SupportedMethod => SupportedMethodsColection.includes(obj);
|
|
35
|
+
|
|
36
|
+
export interface RuntimeRequestMethodBody {
|
|
37
|
+
body: any;
|
|
38
|
+
status?: number | ((...args: any[])=>number);
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RequestParameters {
|
|
43
|
+
query: any,
|
|
44
|
+
params: any,
|
|
45
|
+
body: any,
|
|
46
|
+
headers: any
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const isRuntimeRequestMethodBody = (obj: any): obj is RuntimeRequestMethodBody => {
|
|
50
|
+
try {
|
|
51
|
+
return !!obj.body && (!obj.status || !isNaN(Number(obj.status)))
|
|
52
|
+
} catch (_) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type RuntimeRequestMethodBodyCollection = Partial<Record<SupportedMethod, RuntimeRequestMethodBody>>;
|
|
58
|
+
|
|
59
|
+
export const isRuntimeRequestMethodBodyCollection = (obj: any): obj is RuntimeRequestMethodBodyCollection => {
|
|
60
|
+
try {
|
|
61
|
+
return !Object.keys(obj).some(k => !(isSupportedMethod(k) && isRuntimeRequestMethodBody(obj[k])))
|
|
62
|
+
} catch (_) { return false; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface RuntimeRequestBody {
|
|
66
|
+
path: string;
|
|
67
|
+
methods: RuntimeRequestMethodBodyCollection;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const isRuntimeRequestBody = (obj: any): obj is RuntimeRequestBody => {
|
|
71
|
+
try {
|
|
72
|
+
return typeof obj.path === 'string'
|
|
73
|
+
&& isRuntimeRequestMethodBodyCollection(obj.methods)
|
|
74
|
+
} catch (_) { return false; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface RuntimeRequestCollection {
|
|
78
|
+
[path: string]: RuntimeRequestBody
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const isRuntimeRequestCollection = (obj: any): obj is RuntimeRequestCollection => {
|
|
82
|
+
try { return !Object.keys(obj).some(k => !isRuntimeRequestBody(obj[k])) }
|
|
83
|
+
catch (_) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const appFactory = (runtimeCollection?: RuntimeRequestCollection) => {
|
|
89
|
+
let runtimeRequestCollection: RuntimeRequestCollection = runtimeCollection || {};
|
|
90
|
+
if (!isRuntimeRequestCollection(runtimeRequestCollection)) {
|
|
91
|
+
throw new Error('Intial requests JSON is invalid')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
runtimeRequestCollection = Object.keys(runtimeRequestCollection).reduce(
|
|
95
|
+
(acc, curr) => {
|
|
96
|
+
const fixedPath = `/${curr}`.replace(/\/\//g, '/');
|
|
97
|
+
const body = {
|
|
98
|
+
...runtimeCollection[curr],
|
|
99
|
+
path: fixedPath
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
...acc,
|
|
103
|
+
[fixedPath]: body
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
{} as RuntimeRequestCollection
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const app = express();
|
|
110
|
+
|
|
111
|
+
app.use(cors());
|
|
112
|
+
app.use(express.json({
|
|
113
|
+
strict: false
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
app.get('/', (_req, res) => res.send(sortKeys(runtimeRequestCollection, { deep: true })));
|
|
117
|
+
|
|
118
|
+
app.post('/', (req, res) => {
|
|
119
|
+
const { body } = req;
|
|
120
|
+
if (isRuntimeRequestBody(body)) {
|
|
121
|
+
const fixedPath = `/${body.path}`.replace(/\/\//g, '/');
|
|
122
|
+
body.path = fixedPath;
|
|
123
|
+
|
|
124
|
+
runtimeRequestCollection = {
|
|
125
|
+
...runtimeRequestCollection,
|
|
126
|
+
[fixedPath]: body
|
|
127
|
+
}
|
|
128
|
+
return res.status(204).send();
|
|
129
|
+
}
|
|
130
|
+
return res.status(400).send();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
app.put('/', (req, res) => {
|
|
134
|
+
const { body } = req;
|
|
135
|
+
if (!isRuntimeRequestCollection(body)) {
|
|
136
|
+
return res.status(400).send('BAD_REQUEST_BODY')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const cleanBody = Object.keys(body).reduce(
|
|
140
|
+
(acc, curr) => {
|
|
141
|
+
const fixedPath = `/${curr}`.replace(/\/\//g, '/');
|
|
142
|
+
const newBody = {
|
|
143
|
+
...body[curr],
|
|
144
|
+
path: fixedPath
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
...acc,
|
|
148
|
+
[fixedPath]: newBody
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
{} as RuntimeRequestCollection
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
runtimeRequestCollection = cleanBody;
|
|
155
|
+
return res.status(204).send();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
app.delete('/', (req, res) => {
|
|
159
|
+
let { path } = req.query;
|
|
160
|
+
|
|
161
|
+
if (!path) {
|
|
162
|
+
return res.status(400).send();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const cleanPath = Array.isArray(path) ? (path as any[]).map(p => `${p}`) : [`${path}`]
|
|
166
|
+
|
|
167
|
+
runtimeRequestCollection = cleanPath.reduce(
|
|
168
|
+
(acc, curr) => {
|
|
169
|
+
const { [curr]: _, ...rest } = acc;
|
|
170
|
+
return rest;
|
|
171
|
+
},
|
|
172
|
+
runtimeRequestCollection
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return res.status(204).send();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const catchAllHandler = (req: Request, res: Response) => {
|
|
179
|
+
const matcher = createMatcher(runtimeRequestCollection);
|
|
180
|
+
const routeMatch = matcher(req.path);
|
|
181
|
+
|
|
182
|
+
if (routeMatch) {
|
|
183
|
+
const reqInfo: RuntimeRequestBody = routeMatch.value;
|
|
184
|
+
const params = routeMatch.params;
|
|
185
|
+
|
|
186
|
+
const method: RuntimeRequestMethodBody = reqInfo.methods[req.method];
|
|
187
|
+
if (!method) {
|
|
188
|
+
return res.status(404).send()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const tokenParams = {
|
|
192
|
+
query: req.query,
|
|
193
|
+
params,
|
|
194
|
+
body: req.body,
|
|
195
|
+
headers: req.headers
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (method.status) {
|
|
199
|
+
const status = typeof method.status === 'function'
|
|
200
|
+
? method.status(tokenParams)
|
|
201
|
+
: method.status
|
|
202
|
+
|
|
203
|
+
res.status(status);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (method.headers) {
|
|
207
|
+
const processedHeaders = Object.keys(method.headers).reduce(
|
|
208
|
+
(acc, curr) => ({
|
|
209
|
+
...acc,
|
|
210
|
+
[curr]: Handlebars.compile(method.headers[curr])(tokenParams)
|
|
211
|
+
}),
|
|
212
|
+
{} as Record<string, string>
|
|
213
|
+
);
|
|
214
|
+
res.set(processedHeaders);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let { body } = method;
|
|
218
|
+
|
|
219
|
+
// if body is a function, execute and then process the response
|
|
220
|
+
if (typeof body === 'function') {
|
|
221
|
+
body = body(tokenParams);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (typeof body !== 'string') {
|
|
225
|
+
return res.send(body);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const template = Handlebars.compile(body);
|
|
229
|
+
const result = template(tokenParams)
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const jsonResult = JSON.parse(result);
|
|
233
|
+
return res.send(jsonResult);
|
|
234
|
+
} catch (_) {
|
|
235
|
+
return res.send(result)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return res.status(404).send();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
app.get('/*', catchAllHandler);
|
|
243
|
+
app.post('/*', catchAllHandler);
|
|
244
|
+
app.put('/*', catchAllHandler);
|
|
245
|
+
app.patch('/*', catchAllHandler);
|
|
246
|
+
app.delete('/*', catchAllHandler);
|
|
247
|
+
|
|
248
|
+
Object.defineProperty(app, 'runtimeRequestCollection', {
|
|
249
|
+
get: () => {
|
|
250
|
+
return cloneDeep(runtimeRequestCollection);
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
return app;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const app = appFactory();
|
|
228
258
|
export default app;
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es6",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"rootDir": "./",
|
|
6
|
-
"outDir": "./build",
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"strict": false,
|
|
10
|
-
},
|
|
11
|
-
"exclude": [
|
|
12
|
-
"**/*.spec.ts",
|
|
13
|
-
"**/*.test.ts",
|
|
14
|
-
"./build/**/*"
|
|
15
|
-
]
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es6",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"rootDir": "./",
|
|
6
|
+
"outDir": "./build",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"strict": false,
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"**/*.spec.ts",
|
|
13
|
+
"**/*.test.ts",
|
|
14
|
+
"./build/**/*"
|
|
15
|
+
]
|
|
16
16
|
}
|
package/.env
DELETED
|
File without changes
|