create-backlist 6.0.3 → 6.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.
package/package.json
CHANGED
package/src/analyzer.js
CHANGED
|
@@ -1,44 +1,134 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { glob } = require("glob");
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const parser = require("@babel/parser");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
7
8
|
|
|
9
|
+
const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete"]);
|
|
10
|
+
|
|
11
|
+
// -------------------------
|
|
12
|
+
// Small utils
|
|
13
|
+
// -------------------------
|
|
14
|
+
function normalizeSlashes(p) {
|
|
15
|
+
return String(p || "").replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJSONSafe(p) {
|
|
19
|
+
try {
|
|
20
|
+
return fs.readJsonSync(p);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// -------------------------
|
|
27
|
+
// AUTH detection (for addAuth)
|
|
28
|
+
// -------------------------
|
|
29
|
+
function findAuthUsageInRepo(rootDir) {
|
|
30
|
+
// 1) package.json quick check
|
|
31
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
32
|
+
const pkg = readJSONSafe(pkgPath) || {};
|
|
33
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
34
|
+
|
|
35
|
+
const authDeps = [
|
|
36
|
+
"next-auth",
|
|
37
|
+
"@auth/core",
|
|
38
|
+
"@clerk/nextjs",
|
|
39
|
+
"@supabase/auth-helpers-nextjs",
|
|
40
|
+
"@supabase/supabase-js",
|
|
41
|
+
"firebase",
|
|
42
|
+
"lucia",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
if (authDeps.some((d) => deps[d])) return true;
|
|
46
|
+
|
|
47
|
+
// 2) Source scan for common auth identifiers
|
|
48
|
+
const scanDirs = ["src", "app", "pages", "components", "lib", "utils"]
|
|
49
|
+
.map((d) => path.join(rootDir, d))
|
|
50
|
+
.filter((d) => fs.existsSync(d));
|
|
51
|
+
|
|
52
|
+
const patterns = [
|
|
53
|
+
"next-auth",
|
|
54
|
+
"getServerSession",
|
|
55
|
+
"useSession",
|
|
56
|
+
"SessionProvider",
|
|
57
|
+
"@clerk/nextjs",
|
|
58
|
+
"ClerkProvider",
|
|
59
|
+
"auth()",
|
|
60
|
+
"currentUser",
|
|
61
|
+
"createServerClient",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const dir of scanDirs) {
|
|
65
|
+
const files = glob.sync(`${normalizeSlashes(dir)}/**/*.{js,ts,jsx,tsx}`, {
|
|
66
|
+
ignore: [
|
|
67
|
+
"**/node_modules/**",
|
|
68
|
+
"**/dist/**",
|
|
69
|
+
"**/build/**",
|
|
70
|
+
"**/.next/**",
|
|
71
|
+
"**/coverage/**",
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
try {
|
|
77
|
+
const content = fs.readFileSync(f, "utf8");
|
|
78
|
+
if (patterns.some((p) => content.includes(p))) return true;
|
|
79
|
+
} catch {
|
|
80
|
+
// ignore read errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// -------------------------
|
|
89
|
+
// Frontend API call analyzer (your code)
|
|
90
|
+
// -------------------------
|
|
8
91
|
function toTitleCase(str) {
|
|
9
|
-
if (!str) return
|
|
92
|
+
if (!str) return "Default";
|
|
10
93
|
return String(str)
|
|
11
94
|
.replace(/[-_]+(\w)/g, (_, c) => c.toUpperCase())
|
|
12
|
-
.replace(/^\w/, c => c.toUpperCase())
|
|
13
|
-
.replace(/[^a-zA-Z0-9]/g,
|
|
95
|
+
.replace(/^\w/, (c) => c.toUpperCase())
|
|
96
|
+
.replace(/[^a-zA-Z0-9]/g, "");
|
|
14
97
|
}
|
|
15
98
|
|
|
16
99
|
function normalizeRouteForBackend(urlValue) {
|
|
17
|
-
return urlValue.replace(/\{(\w+)\}/g,
|
|
100
|
+
return urlValue.replace(/\{(\w+)\}/g, ":$1");
|
|
18
101
|
}
|
|
19
102
|
|
|
20
103
|
function inferTypeFromNode(node) {
|
|
21
|
-
if (!node) return
|
|
104
|
+
if (!node) return "String";
|
|
22
105
|
switch (node.type) {
|
|
23
|
-
case
|
|
24
|
-
|
|
25
|
-
case
|
|
26
|
-
|
|
27
|
-
|
|
106
|
+
case "StringLiteral":
|
|
107
|
+
return "String";
|
|
108
|
+
case "NumericLiteral":
|
|
109
|
+
return "Number";
|
|
110
|
+
case "BooleanLiteral":
|
|
111
|
+
return "Boolean";
|
|
112
|
+
case "NullLiteral":
|
|
113
|
+
return "String";
|
|
114
|
+
default:
|
|
115
|
+
return "String";
|
|
28
116
|
}
|
|
29
117
|
}
|
|
30
118
|
|
|
31
119
|
function extractObjectSchema(objExpr) {
|
|
32
120
|
const schemaFields = {};
|
|
33
|
-
if (!objExpr || objExpr.type !==
|
|
121
|
+
if (!objExpr || objExpr.type !== "ObjectExpression") return null;
|
|
34
122
|
|
|
35
123
|
for (const prop of objExpr.properties) {
|
|
36
|
-
if (prop.type !==
|
|
124
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
37
125
|
|
|
38
126
|
const key =
|
|
39
|
-
prop.key.type ===
|
|
40
|
-
|
|
41
|
-
|
|
127
|
+
prop.key.type === "Identifier"
|
|
128
|
+
? prop.key.name
|
|
129
|
+
: prop.key.type === "StringLiteral"
|
|
130
|
+
? prop.key.value
|
|
131
|
+
: null;
|
|
42
132
|
|
|
43
133
|
if (!key) continue;
|
|
44
134
|
schemaFields[key] = inferTypeFromNode(prop.value);
|
|
@@ -46,14 +136,14 @@ function extractObjectSchema(objExpr) {
|
|
|
46
136
|
return schemaFields;
|
|
47
137
|
}
|
|
48
138
|
|
|
49
|
-
function resolveIdentifierToInit(
|
|
139
|
+
function resolveIdentifierToInit(pathObj, identifierName) {
|
|
50
140
|
try {
|
|
51
|
-
const binding =
|
|
141
|
+
const binding = pathObj.scope.getBinding(identifierName);
|
|
52
142
|
if (!binding) return null;
|
|
53
143
|
const declPath = binding.path;
|
|
54
144
|
if (!declPath || !declPath.node) return null;
|
|
55
145
|
|
|
56
|
-
if (declPath.node.type ===
|
|
146
|
+
if (declPath.node.type === "VariableDeclarator") return declPath.node.init || null;
|
|
57
147
|
return null;
|
|
58
148
|
} catch {
|
|
59
149
|
return null;
|
|
@@ -63,17 +153,16 @@ function resolveIdentifierToInit(path, identifierName) {
|
|
|
63
153
|
function getUrlValue(urlNode) {
|
|
64
154
|
if (!urlNode) return null;
|
|
65
155
|
|
|
66
|
-
if (urlNode.type ===
|
|
156
|
+
if (urlNode.type === "StringLiteral") return urlNode.value;
|
|
67
157
|
|
|
68
|
-
if (urlNode.type ===
|
|
69
|
-
// `/api/users/${id}` -> `/api/users/{id}` or `{param1}`
|
|
158
|
+
if (urlNode.type === "TemplateLiteral") {
|
|
70
159
|
const quasis = urlNode.quasis || [];
|
|
71
160
|
const exprs = urlNode.expressions || [];
|
|
72
|
-
let out =
|
|
161
|
+
let out = "";
|
|
73
162
|
for (let i = 0; i < quasis.length; i++) {
|
|
74
163
|
out += quasis[i].value.raw;
|
|
75
164
|
if (exprs[i]) {
|
|
76
|
-
if (exprs[i].type ===
|
|
165
|
+
if (exprs[i].type === "Identifier") out += `{${exprs[i].name}}`;
|
|
77
166
|
else out += `{param${i + 1}}`;
|
|
78
167
|
}
|
|
79
168
|
}
|
|
@@ -84,26 +173,21 @@ function getUrlValue(urlNode) {
|
|
|
84
173
|
}
|
|
85
174
|
|
|
86
175
|
function extractApiPath(urlValue) {
|
|
87
|
-
// supports:
|
|
88
|
-
// - /api/...
|
|
89
|
-
// - http://localhost:5000/api/...
|
|
90
176
|
if (!urlValue) return null;
|
|
91
|
-
const idx = urlValue.indexOf(
|
|
177
|
+
const idx = urlValue.indexOf("/api/");
|
|
92
178
|
if (idx === -1) return null;
|
|
93
|
-
return urlValue.slice(idx);
|
|
179
|
+
return urlValue.slice(idx);
|
|
94
180
|
}
|
|
95
181
|
|
|
96
182
|
function deriveControllerNameFromUrl(urlValue) {
|
|
97
183
|
const apiPath = extractApiPath(urlValue) || urlValue;
|
|
98
|
-
const parts = String(apiPath).split(
|
|
99
|
-
const apiIndex = parts.indexOf(
|
|
184
|
+
const parts = String(apiPath).split("/").filter(Boolean);
|
|
185
|
+
const apiIndex = parts.indexOf("api");
|
|
100
186
|
|
|
101
187
|
let seg = null;
|
|
102
188
|
|
|
103
189
|
if (apiIndex >= 0) {
|
|
104
190
|
seg = parts[apiIndex + 1] || null;
|
|
105
|
-
|
|
106
|
-
// skip version segment (v1, v2, v10...)
|
|
107
191
|
if (seg && /^v\d+$/i.test(seg)) {
|
|
108
192
|
seg = parts[apiIndex + 2] || seg;
|
|
109
193
|
}
|
|
@@ -115,8 +199,8 @@ function deriveControllerNameFromUrl(urlValue) {
|
|
|
115
199
|
}
|
|
116
200
|
|
|
117
201
|
function deriveActionName(method, route) {
|
|
118
|
-
const cleaned = String(route).replace(/^\/api\//,
|
|
119
|
-
const last = cleaned.trim().split(/\s+/).filter(Boolean).pop() ||
|
|
202
|
+
const cleaned = String(route).replace(/^\/api\//, "/").replace(/[/:{}-]/g, " ");
|
|
203
|
+
const last = cleaned.trim().split(/\s+/).filter(Boolean).pop() || "Action";
|
|
120
204
|
return `${String(method).toLowerCase()}${toTitleCase(last)}`;
|
|
121
205
|
}
|
|
122
206
|
|
|
@@ -130,21 +214,23 @@ function extractPathParams(route) {
|
|
|
130
214
|
|
|
131
215
|
function extractQueryParamsFromUrl(urlValue) {
|
|
132
216
|
try {
|
|
133
|
-
const qIndex = urlValue.indexOf(
|
|
217
|
+
const qIndex = urlValue.indexOf("?");
|
|
134
218
|
if (qIndex === -1) return [];
|
|
135
219
|
const qs = urlValue.slice(qIndex + 1);
|
|
136
|
-
return qs
|
|
220
|
+
return qs
|
|
221
|
+
.split("&")
|
|
222
|
+
.map((p) => p.split("=")[0])
|
|
223
|
+
.filter(Boolean);
|
|
137
224
|
} catch {
|
|
138
225
|
return [];
|
|
139
226
|
}
|
|
140
227
|
}
|
|
141
228
|
|
|
142
229
|
function detectAxiosLikeMethod(node) {
|
|
143
|
-
|
|
144
|
-
if (!node.callee || node.callee.type !== 'MemberExpression') return null;
|
|
230
|
+
if (!node.callee || node.callee.type !== "MemberExpression") return null;
|
|
145
231
|
|
|
146
232
|
const prop = node.callee.property;
|
|
147
|
-
if (!prop || prop.type !==
|
|
233
|
+
if (!prop || prop.type !== "Identifier") return null;
|
|
148
234
|
|
|
149
235
|
const name = prop.name.toLowerCase();
|
|
150
236
|
if (!HTTP_METHODS.has(name)) return null;
|
|
@@ -153,22 +239,28 @@ function detectAxiosLikeMethod(node) {
|
|
|
153
239
|
}
|
|
154
240
|
|
|
155
241
|
async function analyzeFrontend(srcPath) {
|
|
242
|
+
if (!srcPath) throw new Error("analyzeFrontend: srcPath is required");
|
|
156
243
|
if (!fs.existsSync(srcPath)) {
|
|
157
244
|
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
158
245
|
}
|
|
159
246
|
|
|
160
|
-
const files = await glob(`${srcPath}/**/*.{js,ts,jsx,tsx}`, {
|
|
161
|
-
ignore: [
|
|
247
|
+
const files = await glob(`${normalizeSlashes(srcPath)}/**/*.{js,ts,jsx,tsx}`, {
|
|
248
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**", "**/coverage/**"],
|
|
162
249
|
});
|
|
163
250
|
|
|
164
251
|
const endpoints = new Map();
|
|
165
252
|
|
|
166
253
|
for (const file of files) {
|
|
167
|
-
|
|
254
|
+
let code;
|
|
255
|
+
try {
|
|
256
|
+
code = await fs.readFile(file, "utf-8");
|
|
257
|
+
} catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
168
260
|
|
|
169
261
|
let ast;
|
|
170
262
|
try {
|
|
171
|
-
ast = parser.parse(code, { sourceType:
|
|
263
|
+
ast = parser.parse(code, { sourceType: "module", plugins: ["jsx", "typescript"] });
|
|
172
264
|
} catch {
|
|
173
265
|
continue;
|
|
174
266
|
}
|
|
@@ -177,52 +269,56 @@ async function analyzeFrontend(srcPath) {
|
|
|
177
269
|
CallExpression(callPath) {
|
|
178
270
|
const node = callPath.node;
|
|
179
271
|
|
|
180
|
-
const isFetch = node.callee.type ===
|
|
272
|
+
const isFetch = node.callee.type === "Identifier" && node.callee.name === "fetch";
|
|
181
273
|
const axiosMethod = detectAxiosLikeMethod(node);
|
|
182
274
|
|
|
183
275
|
if (!isFetch && !axiosMethod) return;
|
|
184
276
|
|
|
185
277
|
let urlValue = null;
|
|
186
|
-
let method =
|
|
278
|
+
let method = "GET";
|
|
187
279
|
let schemaFields = null;
|
|
188
280
|
|
|
189
|
-
// ---- fetch() ----
|
|
190
281
|
if (isFetch) {
|
|
191
282
|
urlValue = getUrlValue(node.arguments[0]);
|
|
192
283
|
const optionsNode = node.arguments[1];
|
|
193
284
|
|
|
194
|
-
if (optionsNode && optionsNode.type ===
|
|
285
|
+
if (optionsNode && optionsNode.type === "ObjectExpression") {
|
|
195
286
|
const methodProp = optionsNode.properties.find(
|
|
196
|
-
p =>
|
|
287
|
+
(p) =>
|
|
288
|
+
p.type === "ObjectProperty" &&
|
|
289
|
+
p.key.type === "Identifier" &&
|
|
290
|
+
p.key.name === "method"
|
|
197
291
|
);
|
|
198
|
-
if (methodProp && methodProp.value.type ===
|
|
292
|
+
if (methodProp && methodProp.value.type === "StringLiteral") {
|
|
199
293
|
method = methodProp.value.value.toUpperCase();
|
|
200
294
|
}
|
|
201
295
|
|
|
202
|
-
|
|
203
|
-
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
296
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
204
297
|
const bodyProp = optionsNode.properties.find(
|
|
205
|
-
p =>
|
|
298
|
+
(p) =>
|
|
299
|
+
p.type === "ObjectProperty" &&
|
|
300
|
+
p.key.type === "Identifier" &&
|
|
301
|
+
p.key.name === "body"
|
|
206
302
|
);
|
|
207
303
|
|
|
208
304
|
if (bodyProp) {
|
|
209
305
|
const v = bodyProp.value;
|
|
210
306
|
|
|
211
307
|
if (
|
|
212
|
-
v.type ===
|
|
213
|
-
v.callee.type ===
|
|
214
|
-
v.callee.object.type ===
|
|
215
|
-
v.callee.object.name ===
|
|
216
|
-
v.callee.property.type ===
|
|
217
|
-
v.callee.property.name ===
|
|
308
|
+
v.type === "CallExpression" &&
|
|
309
|
+
v.callee.type === "MemberExpression" &&
|
|
310
|
+
v.callee.object.type === "Identifier" &&
|
|
311
|
+
v.callee.object.name === "JSON" &&
|
|
312
|
+
v.callee.property.type === "Identifier" &&
|
|
313
|
+
v.callee.property.name === "stringify"
|
|
218
314
|
) {
|
|
219
315
|
const arg0 = v.arguments[0];
|
|
220
316
|
|
|
221
|
-
if (arg0?.type ===
|
|
317
|
+
if (arg0?.type === "ObjectExpression") {
|
|
222
318
|
schemaFields = extractObjectSchema(arg0);
|
|
223
|
-
} else if (arg0?.type ===
|
|
319
|
+
} else if (arg0?.type === "Identifier") {
|
|
224
320
|
const init = resolveIdentifierToInit(callPath, arg0.name);
|
|
225
|
-
if (init?.type ===
|
|
321
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
226
322
|
}
|
|
227
323
|
}
|
|
228
324
|
}
|
|
@@ -230,27 +326,25 @@ async function analyzeFrontend(srcPath) {
|
|
|
230
326
|
}
|
|
231
327
|
}
|
|
232
328
|
|
|
233
|
-
// ---- axios-like client ----
|
|
234
329
|
if (axiosMethod) {
|
|
235
330
|
method = axiosMethod;
|
|
236
331
|
urlValue = getUrlValue(node.arguments[0]);
|
|
237
332
|
|
|
238
|
-
if ([
|
|
333
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
239
334
|
const dataArg = node.arguments[1];
|
|
240
|
-
if (dataArg?.type ===
|
|
335
|
+
if (dataArg?.type === "ObjectExpression") {
|
|
241
336
|
schemaFields = extractObjectSchema(dataArg);
|
|
242
|
-
} else if (dataArg?.type ===
|
|
337
|
+
} else if (dataArg?.type === "Identifier") {
|
|
243
338
|
const init = resolveIdentifierToInit(callPath, dataArg.name);
|
|
244
|
-
if (init?.type ===
|
|
339
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
245
340
|
}
|
|
246
341
|
}
|
|
247
342
|
}
|
|
248
343
|
|
|
249
|
-
// accept only URLs that contain /api/
|
|
250
344
|
const apiPath = extractApiPath(urlValue);
|
|
251
345
|
if (!apiPath) return;
|
|
252
346
|
|
|
253
|
-
const route = normalizeRouteForBackend(apiPath.split(
|
|
347
|
+
const route = normalizeRouteForBackend(apiPath.split("?")[0]);
|
|
254
348
|
const controllerName = deriveControllerNameFromUrl(apiPath);
|
|
255
349
|
const actionName = deriveActionName(method, route);
|
|
256
350
|
|
|
@@ -266,14 +360,31 @@ async function analyzeFrontend(srcPath) {
|
|
|
266
360
|
queryParams: extractQueryParamsFromUrl(apiPath),
|
|
267
361
|
schemaFields,
|
|
268
362
|
requestBody: schemaFields ? { fields: schemaFields } : null,
|
|
269
|
-
sourceFile: file
|
|
363
|
+
sourceFile: normalizeSlashes(file),
|
|
270
364
|
});
|
|
271
365
|
}
|
|
272
|
-
}
|
|
366
|
+
},
|
|
273
367
|
});
|
|
274
368
|
}
|
|
275
369
|
|
|
276
370
|
return Array.from(endpoints.values());
|
|
277
371
|
}
|
|
278
372
|
|
|
279
|
-
|
|
373
|
+
// -------------------------
|
|
374
|
+
// Main analyze() exported for CLI
|
|
375
|
+
// -------------------------
|
|
376
|
+
function analyze(projectRoot = process.cwd()) {
|
|
377
|
+
const rootDir = path.resolve(projectRoot);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
rootDir: normalizeSlashes(rootDir),
|
|
381
|
+
hasAuth: findAuthUsageInRepo(rootDir),
|
|
382
|
+
// If CLI expects addAuth directly, keep both:
|
|
383
|
+
addAuth: findAuthUsageInRepo(rootDir),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = {
|
|
388
|
+
analyze,
|
|
389
|
+
analyzeFrontend,
|
|
390
|
+
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import request from 'supertest';
|
|
3
3
|
import express from 'express';
|
|
4
4
|
|
|
5
|
-
import apiRoutes from '../routes';
|
|
5
|
+
import apiRoutes from '../routes';
|
|
6
|
+
|
|
6
7
|
<% if (addAuth) { -%>
|
|
7
8
|
import authRoutes from '../routes/Auth.routes';
|
|
8
9
|
<% } -%>
|
|
@@ -13,6 +14,7 @@ app.use(express.json());
|
|
|
13
14
|
<% if (addAuth) { -%>
|
|
14
15
|
app.use('/api/auth', authRoutes);
|
|
15
16
|
<% } -%>
|
|
17
|
+
|
|
16
18
|
app.use('/api', apiRoutes);
|
|
17
19
|
|
|
18
20
|
describe('API Endpoints (Generated)', () => {
|
|
@@ -22,14 +24,35 @@ describe('API Endpoints (Generated)', () => {
|
|
|
22
24
|
|
|
23
25
|
<% endpoints
|
|
24
26
|
.filter(ep => ep && ep.route && ep.method)
|
|
25
|
-
.forEach(ep => {
|
|
26
|
-
const method = ep.method.toLowerCase();
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
.forEach(ep => {
|
|
28
|
+
const method = String(ep.method).toLowerCase();
|
|
29
|
+
|
|
30
|
+
// ep.route might be "/api/..." or "/..."
|
|
31
|
+
const url = (String(ep.route).startsWith('/api/')
|
|
32
|
+
? String(ep.route).replace(/^\/api/, '')
|
|
33
|
+
: String(ep.route)
|
|
34
|
+
)
|
|
35
|
+
// replace ":id" style params with dummy value
|
|
36
|
+
.replace(/:\w+/g, '1');
|
|
29
37
|
-%>
|
|
30
38
|
it('<%= ep.method %> <%= url %> should respond', async () => {
|
|
31
|
-
const
|
|
32
|
-
|
|
39
|
+
const req = request(app).<%= method %>('<%= '/api' + url %>');
|
|
40
|
+
|
|
41
|
+
<% if (['post','put','patch'].includes(method) && ep.schemaFields) { -%>
|
|
42
|
+
req.send(
|
|
43
|
+
<%- JSON.stringify(
|
|
44
|
+
Object.fromEntries(
|
|
45
|
+
Object.entries(ep.schemaFields).map(([k, t]) => [
|
|
46
|
+
k,
|
|
47
|
+
t === 'Number' ? 1 : (t === 'Boolean' ? true : 'test')
|
|
48
|
+
])
|
|
49
|
+
)
|
|
50
|
+
) %>
|
|
51
|
+
);
|
|
52
|
+
<% } -%>
|
|
53
|
+
|
|
54
|
+
const res = await req;
|
|
55
|
+
|
|
33
56
|
// We only assert "not 404" because generated handlers may be TODO stubs,
|
|
34
57
|
// and auth/validation may affect exact status codes.
|
|
35
58
|
expect(res.statusCode).not.toBe(404);
|