@aimeloic/monkey-tester 5.0.3 → 5.0.4

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.
Files changed (2) hide show
  1. package/monkey.js +117 -49
  2. package/package.json +1 -1
package/monkey.js CHANGED
@@ -1,75 +1,129 @@
1
1
  'use strict';
2
2
 
3
- import { UI } from './htmlTemplate.js';
3
+ import { UI, encodePayload } from './htmlTemplate.js';
4
4
 
5
5
  function inferType(name) {
6
6
  const n = name.toLowerCase();
7
- if (n.includes('email')) return 'email';
8
- if (n.includes('password') || n.includes('pass')) return 'password';
9
- if (n.includes('date')) return 'date';
10
- if (n.includes('age') || n.includes('price') || n.includes('quantity') || n.includes('stock') || n === 'id' || n.endsWith('id')) return 'number';
7
+ if (n.includes('email')) return 'email';
8
+ if (n.includes('password') || n.includes('pass')) return 'password';
9
+ if (n.includes('date') || n.includes('birth')) return 'date';
10
+ if (n.includes('phone') || n.includes('tel')) return 'tel';
11
+ if (n.includes('url') || n.includes('website') || n.includes('link')) return 'url';
12
+ if (
13
+ n.includes('age') || n.includes('price') || n.includes('amount') ||
14
+ n.includes('count') || n.includes('qty') || n.includes('quantity') ||
15
+ n.includes('stock') || n.includes('salary') || n.includes('total') ||
16
+ (n === 'id') || n.endsWith('_id') || n.endsWith('Id')
17
+ ) return 'number';
11
18
  return 'text';
12
19
  }
13
20
 
14
21
  function buildField(name) {
15
- return { name, label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'), type: inferType(name), placeholder: `Enter ${name}` };
22
+ return {
23
+ name,
24
+ label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'),
25
+ type: inferType(name),
26
+ placeholder: 'Enter ' + name
27
+ };
16
28
  }
17
29
 
18
30
  function extractBodyFields(handler) {
19
31
  try {
20
32
  const source = handler.toString();
21
33
  if (!source || source.includes('[native code]')) return [];
34
+
22
35
  const seen = new Map();
23
36
  const destructRe = /(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
24
37
  let m;
25
38
  while ((m = destructRe.exec(source)) !== null) {
26
- m[1].split(',').forEach(part => {
39
+ m[1].split(',').forEach(function(part) {
27
40
  const name = part.split(':')[0].split('=')[0].trim();
28
- if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name)) seen.set(name, buildField(name));
41
+ if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name)) {
42
+ seen.set(name, buildField(name));
43
+ }
29
44
  });
30
45
  }
31
- const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
46
+
47
+ const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)|req\.body\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]]/g;
32
48
  while ((m = accessRe.exec(source)) !== null) {
33
- const name = m[1]; if (name && !seen.has(name)) seen.set(name, buildField(name));
49
+ const name = m[1] || m[2];
50
+ if (name && !seen.has(name)) seen.set(name, buildField(name));
34
51
  }
52
+
35
53
  return Array.from(seen.values());
36
- } catch { return []; }
54
+ } catch {
55
+ return [];
56
+ }
57
+ }
58
+
59
+ function fallbackFields(path) {
60
+ const p = path.toLowerCase();
61
+ if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) return ['email', 'password'].map(buildField);
62
+ if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) return ['username', 'email', 'password'].map(buildField);
63
+ if (p.includes('user')) return ['username', 'email', 'password'].map(buildField);
64
+ if (p.includes('product')) return ['name', 'price', 'stock'].map(buildField);
65
+ if (p.includes('order')) return ['productId', 'quantity', 'address'].map(buildField);
66
+ return [];
37
67
  }
38
68
 
39
69
  function extractRouterPrefix(layer) {
40
70
  if (!layer.regexp) return '';
41
- const m = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/].reduce((acc, re) => acc || re.exec(layer.regexp.source), null);
42
- return m && m[1] ? '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '') : '';
71
+ const src = layer.regexp.source;
72
+ const patterns = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/];
73
+ for (var i = 0; i < patterns.length; i++) {
74
+ const m = patterns[i].exec(src);
75
+ if (m && m[1]) return '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '');
76
+ }
77
+ return '';
43
78
  }
44
79
 
45
- function parseStack(stack, detectedEndpoints, prefix = '') {
80
+ function parseStack(stack, detectedEndpoints, prefix) {
46
81
  if (!Array.isArray(stack)) return;
47
- for (const layer of stack) {
82
+
83
+ for (var i = 0; i < stack.length; i++) {
84
+ var layer = stack[i];
48
85
  if (layer.route) {
49
- const rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
50
- const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
86
+ var rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
87
+ var fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
88
+
51
89
  if (fullPath.startsWith('/api/tester')) continue;
52
90
 
53
- const methods = Object.keys(layer.route.methods || {});
54
- for (const method of methods) {
55
- const httpMethod = method.toUpperCase();
56
- const key = `${httpMethod}::${fullPath}`;
57
- const pathParams = [];
58
- const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
59
- let pm;
60
- while ((pm = paramRe.exec(fullPath)) !== null) {
61
- pathParams.push({ name: pm[1], label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1), placeholder: 'value' });
62
- }
91
+ var methods = Object.keys(layer.route.methods || {});
92
+ var pathParams = [];
93
+ var paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
94
+ var pm;
95
+ while ((pm = paramRe.exec(fullPath)) !== null) {
96
+ pathParams.push({ name: pm[1], label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1), placeholder: 'value' });
97
+ }
63
98
 
64
- let bodyFields = [];
65
- if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
66
- const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
67
- for (const handler of handlers) bodyFields.push(...extractBodyFields(handler));
68
- const seen = new Map();
69
- bodyFields = bodyFields.filter(f => !seen.has(f.name) && seen.set(f.name, true));
99
+ var bodyFields = [];
100
+ if (methods.some(function(m) { return ['POST', 'PUT', 'PATCH'].indexOf(m.toUpperCase()) !== -1; })) {
101
+ var handlers = (layer.route.stack || []).map(function(sl) { return sl.handle; }).filter(Boolean);
102
+ for (var j = 0; j < handlers.length; j++) {
103
+ bodyFields.push.apply(bodyFields, extractBodyFields(handlers[j]));
70
104
  }
105
+ var seen2 = new Map();
106
+ bodyFields = bodyFields.filter(function(f) {
107
+ if (seen2.has(f.name)) return false;
108
+ seen2.set(f.name, true);
109
+ return true;
110
+ });
111
+ if (bodyFields.length === 0 && pathParams.length === 0) {
112
+ bodyFields = fallbackFields(fullPath);
113
+ }
114
+ }
71
115
 
72
- detectedEndpoints[key] = { method: httpMethod, path: fullPath, title: `${httpMethod} ${fullPath}`, desc: `Auto-discovered endpoint - ${fullPath}`, params: pathParams, fields: bodyFields };
116
+ for (var k = 0; k < methods.length; k++) {
117
+ var httpMethod = methods[k].toUpperCase();
118
+ var key = httpMethod + '::' + fullPath;
119
+ detectedEndpoints[key] = {
120
+ method: httpMethod,
121
+ path: fullPath,
122
+ title: httpMethod + ' ' + fullPath,
123
+ desc: 'Auto-discovered endpoint — ' + fullPath,
124
+ params: pathParams,
125
+ fields: bodyFields
126
+ };
73
127
  }
74
128
  } else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
75
129
  parseStack(layer.handle.stack, detectedEndpoints, prefix + extractRouterPrefix(layer));
@@ -79,26 +133,40 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
79
133
 
80
134
  export function endtesterExpress() {
81
135
  return function monkeyTesterMiddleware(req, res, next) {
82
- const route = req.path.toLowerCase().replace(/\/$/, '');
83
-
84
- // 1. Serve Standalone Frontend Templates Interceptions
85
- if (route === '/login') return res.send(UI.login());
86
- if (route === '/signup') return res.send(UI.signup());
87
- if (route === '/dashboard') return res.send(UI.dashboard());
88
-
89
- // 2. Serve the main Tester UI environment
90
- if (route === '/api/tester') {
91
- const app = req.app;
92
- const detectedEndpoints = {};
93
- const rootStack = (app._router && app._router.stack) || (app.router && app.router.stack) || [];
94
- parseStack(rootStack, detectedEndpoints);
95
- const b64 = Buffer.from(JSON.stringify(detectedEndpoints)).toString('base64');
136
+ var normalized = req.path.replace(/\/+$/, '');
137
+
138
+ // Tester sandbox must be checked before static pages so query strings don't collide
139
+ if (normalized.startsWith('/api/tester')) {
140
+ var app = req.app;
141
+ var eps = app.__monkey_endpoints_cache__ || {};
142
+ if (!app.__monkey_endpoints_cache__) {
143
+ eps = {};
144
+ var raw = (app._router && app._router.stack) || (app.router && app.router.stack) || [];
145
+ parseStack(raw, eps);
146
+ app.__monkey_endpoints_cache__ = eps;
147
+ }
148
+ var b64 = encodePayload(eps);
96
149
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
97
150
  return res.send(UI.tester(b64));
98
151
  }
99
152
 
153
+ // Standalone HTML pages — resolved dynamically against discovered routes
154
+ if (normalized === '/login') return res.send(UI.login());
155
+ if (normalized === '/signup') return res.send(UI.signup());
156
+ if (normalized === '/dashboard') {
157
+ if (!app.__monkey_endpoints_cache__) {
158
+ var eps2 = {};
159
+ var raw2 = (app._router && app._router.stack) || (app.router && app.router.stack) || [];
160
+ parseStack(raw2, eps2);
161
+ app.__monkey_endpoints_cache__ = eps2;
162
+ }
163
+ var b64dash = encodePayload(app.__monkey_endpoints_cache__);
164
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
165
+ return res.send(UI.dashboard(b64dash));
166
+ }
167
+
100
168
  next();
101
169
  };
102
170
  }
103
171
 
104
- export default { endtesterExpress };
172
+ export default { endtesterExpress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimeloic/monkey-tester",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "Auto route scanning visual runner dashboard.",
5
5
  "main": "index.js",
6
6
  "type": "module",