@aimeloic/monkey-tester 2.0.0 → 2.0.1
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/html.backup.js +317 -0
- package/htmlTemplate.js +1038 -182
- package/index.js +216 -40
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,83 +2,259 @@ import { getHtmlTemplate } from './htmlTemplate.js';
|
|
|
2
2
|
|
|
3
3
|
export function endtesterExpress() {
|
|
4
4
|
return (req, res, next) => {
|
|
5
|
-
if (
|
|
5
|
+
if (
|
|
6
|
+
req.path !== '/api/tester' &&
|
|
7
|
+
req.path !== '/api/tester/'
|
|
8
|
+
) {
|
|
6
9
|
return next();
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
const expressApp = req.app;
|
|
10
13
|
const detectedEndpoints = {};
|
|
11
14
|
|
|
15
|
+
// =========================
|
|
16
|
+
// Detect Input Types
|
|
17
|
+
// =========================
|
|
18
|
+
function detectInputType(field) {
|
|
19
|
+
const lower = field.toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (lower.includes('email')) return 'email';
|
|
22
|
+
|
|
23
|
+
if (lower.includes('password')) return 'password';
|
|
24
|
+
|
|
25
|
+
if (lower.includes('date')) return 'date';
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
lower.includes('age') ||
|
|
29
|
+
lower.includes('price') ||
|
|
30
|
+
lower.includes('salary') ||
|
|
31
|
+
lower.includes('stock') ||
|
|
32
|
+
lower.includes('quantity') ||
|
|
33
|
+
lower.includes('count') ||
|
|
34
|
+
lower.includes('total') ||
|
|
35
|
+
lower.includes('amount') ||
|
|
36
|
+
lower.includes('id')
|
|
37
|
+
) {
|
|
38
|
+
return 'number';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
lower.includes('phone') ||
|
|
43
|
+
lower.includes('tel')
|
|
44
|
+
) {
|
|
45
|
+
return 'tel';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
lower.includes('url') ||
|
|
50
|
+
lower.includes('website')
|
|
51
|
+
) {
|
|
52
|
+
return 'url';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return 'text';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =========================
|
|
59
|
+
// Extract req.body fields
|
|
60
|
+
// =========================
|
|
61
|
+
function extractBodyFields(handler) {
|
|
62
|
+
try {
|
|
63
|
+
const source = handler.toString();
|
|
64
|
+
|
|
65
|
+
const regex =
|
|
66
|
+
/(const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
|
|
67
|
+
|
|
68
|
+
const matches = [...source.matchAll(regex)];
|
|
69
|
+
|
|
70
|
+
const fields = [];
|
|
71
|
+
|
|
72
|
+
matches.forEach((match) => {
|
|
73
|
+
const variables = match[2]
|
|
74
|
+
.split(',')
|
|
75
|
+
.map(v => v.trim())
|
|
76
|
+
.filter(Boolean);
|
|
77
|
+
|
|
78
|
+
variables.forEach((field) => {
|
|
79
|
+
let realField = field;
|
|
80
|
+
|
|
81
|
+
// support aliases
|
|
82
|
+
// name: username
|
|
83
|
+
if (field.includes(':')) {
|
|
84
|
+
realField = field.split(':')[0].trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// remove defaults
|
|
88
|
+
// age = 0
|
|
89
|
+
if (realField.includes('=')) {
|
|
90
|
+
realField = realField.split('=')[0].trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// avoid duplicates
|
|
94
|
+
const alreadyExists = fields.find(
|
|
95
|
+
f => f.name === realField
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!alreadyExists) {
|
|
99
|
+
fields.push({
|
|
100
|
+
name: realField,
|
|
101
|
+
label:
|
|
102
|
+
realField.charAt(0).toUpperCase() +
|
|
103
|
+
realField.slice(1),
|
|
104
|
+
|
|
105
|
+
type: detectInputType(realField),
|
|
106
|
+
|
|
107
|
+
placeholder: `Enter ${realField}`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return fields;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error('Field extraction error:', err);
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// =========================
|
|
121
|
+
// Parse Express Stack
|
|
122
|
+
// =========================
|
|
12
123
|
function parseStack(stack, prefix = '') {
|
|
13
124
|
if (!stack) return;
|
|
14
125
|
|
|
15
126
|
stack.forEach((layer) => {
|
|
127
|
+
// =========================
|
|
128
|
+
// ROUTES
|
|
129
|
+
// =========================
|
|
16
130
|
if (layer.route) {
|
|
17
|
-
const methods = Object.keys(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
131
|
+
const methods = Object.keys(
|
|
132
|
+
layer.route.methods
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const path = (
|
|
136
|
+
prefix + layer.route.path
|
|
137
|
+
).replace(/\/+/g, '/');
|
|
138
|
+
|
|
139
|
+
if (path.includes('/api/tester')) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
21
142
|
|
|
22
143
|
methods.forEach((method) => {
|
|
23
144
|
const httpMethod = method.toUpperCase();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
145
|
+
|
|
146
|
+
const key =
|
|
147
|
+
`${httpMethod.toLowerCase()}-` +
|
|
148
|
+
path.replace(/[^a-zA-Z0-9]/g, '-');
|
|
149
|
+
|
|
150
|
+
// =========================
|
|
151
|
+
// PATH PARAMS
|
|
152
|
+
// =========================
|
|
153
|
+
const pathParams = layer.route.keys
|
|
154
|
+
? layer.route.keys.map((k) => ({
|
|
155
|
+
name: k.name,
|
|
156
|
+
label: k.name.toUpperCase(),
|
|
157
|
+
placeholder: 'value'
|
|
158
|
+
}))
|
|
159
|
+
: [];
|
|
160
|
+
|
|
161
|
+
// =========================
|
|
162
|
+
// BODY FIELDS
|
|
163
|
+
// =========================
|
|
33
164
|
let bodyFields = [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
['POST', 'PUT', 'PATCH'].includes(
|
|
168
|
+
httpMethod
|
|
169
|
+
)
|
|
170
|
+
) {
|
|
171
|
+
layer.route.stack.forEach((stackLayer) => {
|
|
172
|
+
if (
|
|
173
|
+
stackLayer.handle &&
|
|
174
|
+
typeof stackLayer.handle === 'function'
|
|
175
|
+
) {
|
|
176
|
+
const extractedFields =
|
|
177
|
+
extractBodyFields(
|
|
178
|
+
stackLayer.handle
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
bodyFields.push(...extractedFields);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// remove duplicates
|
|
186
|
+
bodyFields = bodyFields.filter(
|
|
187
|
+
(field, index, self) =>
|
|
188
|
+
index ===
|
|
189
|
+
self.findIndex(
|
|
190
|
+
f => f.name === field.name
|
|
191
|
+
)
|
|
192
|
+
);
|
|
52
193
|
}
|
|
53
194
|
|
|
54
195
|
detectedEndpoints[key] = {
|
|
55
196
|
method: httpMethod,
|
|
56
|
-
path
|
|
197
|
+
path,
|
|
57
198
|
title: `${httpMethod} ${path}`,
|
|
58
199
|
desc: `Auto-discovered endpoint: ${path}`,
|
|
59
200
|
params: pathParams,
|
|
60
201
|
fields: bodyFields
|
|
61
202
|
};
|
|
62
203
|
});
|
|
63
|
-
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =========================
|
|
207
|
+
// NESTED ROUTERS
|
|
208
|
+
// =========================
|
|
209
|
+
else if (
|
|
210
|
+
layer.name === 'router' &&
|
|
211
|
+
layer.handle &&
|
|
212
|
+
layer.handle.stack
|
|
213
|
+
) {
|
|
64
214
|
let routerPath = '';
|
|
215
|
+
|
|
65
216
|
if (layer.regexp) {
|
|
66
|
-
const match = layer.regexp
|
|
217
|
+
const match = layer.regexp
|
|
218
|
+
.toString()
|
|
219
|
+
.match(/^\/\^\\(.*?)\\\/\?/);
|
|
220
|
+
|
|
67
221
|
if (match && match[1]) {
|
|
68
|
-
routerPath = match[1].replace(
|
|
222
|
+
routerPath = match[1].replace(
|
|
223
|
+
/\\/g,
|
|
224
|
+
''
|
|
225
|
+
);
|
|
69
226
|
}
|
|
70
227
|
}
|
|
71
|
-
|
|
228
|
+
|
|
229
|
+
parseStack(
|
|
230
|
+
layer.handle.stack,
|
|
231
|
+
prefix + '/' + routerPath
|
|
232
|
+
);
|
|
72
233
|
}
|
|
73
234
|
});
|
|
74
235
|
}
|
|
75
236
|
|
|
76
|
-
|
|
237
|
+
// =========================
|
|
238
|
+
// START PARSING
|
|
239
|
+
// =========================
|
|
240
|
+
if (
|
|
241
|
+
expressApp._router &&
|
|
242
|
+
expressApp._router.stack
|
|
243
|
+
) {
|
|
77
244
|
parseStack(expressApp._router.stack);
|
|
78
245
|
}
|
|
79
246
|
|
|
80
|
-
|
|
81
|
-
|
|
247
|
+
// =========================
|
|
248
|
+
// RENDER HTML
|
|
249
|
+
// =========================
|
|
250
|
+
const fullHtml =
|
|
251
|
+
getHtmlTemplate(detectedEndpoints);
|
|
252
|
+
|
|
253
|
+
res.setHeader(
|
|
254
|
+
'Content-Type',
|
|
255
|
+
'text/html'
|
|
256
|
+
);
|
|
257
|
+
|
|
82
258
|
return res.send(fullHtml);
|
|
83
259
|
};
|
|
84
260
|
}
|