aitesting 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 devXcant
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # ๐Ÿš€ AITesting - Complete API Testing & Documentation Suite
2
+
3
+ An AI-powered CLI tool that automatically generates Postman collections, API documentation, test files, and live endpoint testing from your backend codebase.
4
+
5
+ ## โœจ Features
6
+
7
+ - ๐Ÿ” **Route Scanning** - Automatically discovers all API routes from TypeScript files
8
+ - ๐Ÿงช **Live Endpoint Testing** - Makes REAL HTTP requests and records actual responses
9
+ - ๐Ÿ” **Auth Detection** - Automatically detects login endpoints and manages JWT tokens
10
+ - ๐Ÿ“‹ **Schema Detection** - Finds Zod, Joi, and class-validator schemas
11
+ - ๐Ÿ“ **Postman Collections** - Generates organized collections with example responses
12
+ - ๐ŸŒ **Environment Files** - Creates local & production Postman environments
13
+ - ๐Ÿงช **Test Generation** - Generates Bun test files for all routes
14
+ - ๐Ÿ“œ **OpenAPI/Swagger** - Generates OpenAPI 3.0 spec with interactive Swagger UI
15
+ - ๐Ÿ“š **Enhanced Documentation** - Creates comprehensive markdown API docs
16
+ - ๐Ÿ“Š **Test Reports** - Saves detailed live test results in JSON format
17
+
18
+ ## ๐Ÿ› ๏ธ Installation
19
+
20
+ ### Install Globally (Recommended)
21
+
22
+ ```bash
23
+ cd /Users/devx/Desktop/AITESTING
24
+ bun link
25
+ ```
26
+
27
+ Now use `aitesting` command from anywhere!
28
+
29
+ ### Or Run Locally
30
+
31
+ ```bash
32
+ cd /Users/devx/Desktop/AITESTING
33
+ bun install
34
+ ```
35
+
36
+ ## ๐Ÿ“– Usage
37
+
38
+ ### Simple Mode (Just scan and generate Postman collection)
39
+
40
+ ```bash
41
+ aitesting --path /path/to/your/backend
42
+ ```
43
+
44
+ ### Complete Mode (Everything!)
45
+
46
+ ```bash
47
+ aitesting --path /path/to/your/backend --all
48
+ ```
49
+
50
+ ### With Remote Backend
51
+
52
+ ```bash
53
+ aitesting --path /path/to/backend --all --url https://api.yourapp.com
54
+ ```
55
+
56
+ ### Specify Output Directory
57
+
58
+ ```bash
59
+ aitesting --path /backend --all -o ~/Documents/api-docs
60
+ ```
61
+
62
+ The `--all` flag runs the complete workflow:
63
+
64
+ 1. โœ… Scans backend for routes
65
+ 2. โœ… Detects authentication endpoints
66
+ 3. โœ… Finds validation schemas
67
+ 4. โœ… Tests all endpoints live
68
+ 5. โœ… Captures JWT tokens automatically
69
+ 6. โœ… Generates Postman collection with example responses
70
+ 7. โœ… Creates environment files
71
+ 8. โœ… Generates automated test files
72
+ 9. โœ… Creates enhanced documentation
73
+
74
+ ## ๐Ÿ“ฆ Generated Files
75
+
76
+ When you run with `--all`, you'll get:
77
+
78
+ ```
79
+ ๐Ÿ“ Project Root
80
+ โ”œโ”€โ”€ postman_collection.json # Postman collection with examples
81
+ โ”œโ”€โ”€ local_environment.postman_environment.json
82
+ โ”œโ”€โ”€ production_environment.postman_environment.json
83
+ โ”œโ”€โ”€ openapi.json # OpenAPI 3.0 specification
84
+ โ”œโ”€โ”€ api-docs.html # Swagger UI (open in browser!)
85
+ โ”œโ”€โ”€ API_DOCUMENTATION.md # Markdown API docs
86
+ โ”œโ”€โ”€ .aitesting/
87
+ โ”‚ โ””โ”€โ”€ report.json # Live test results
88
+ โ””โ”€โ”€ tests/auto/
89
+ โ”œโ”€โ”€ users.test.ts # Auto-generated tests
90
+ โ”œโ”€โ”€ auth.test.ts
91
+ โ””โ”€โ”€ ...
92
+ ```
93
+
94
+ ## ๐ŸŽฏ What It Does
95
+
96
+ ### 1. Route Discovery
97
+
98
+ Scans your TypeScript files using AST parsing to find all route definitions:
99
+
100
+ ```typescript
101
+ router.get("/users/:id", handler);
102
+ router.post("/auth/login", handler);
103
+ ```
104
+
105
+ ### 2. Live Testing (REAL HTTP Requests!)
106
+
107
+ **โš ๏ธ Makes actual HTTP requests to your backend:**
108
+
109
+ - Tests each endpoint with real network calls (using `fetch`)
110
+ - Records actual response status codes
111
+ - Captures real response bodies
112
+ - Saves headers and error messages
113
+ - **Requires your backend to be running!**
114
+
115
+ ### 3. Auth Handling
116
+
117
+ - Automatically detects login/auth endpoints
118
+ - Obtains JWT tokens during testing
119
+ - Injects tokens into subsequent requests
120
+ - Adds auth headers to Postman collection
121
+
122
+ ### 4. Schema Detection
123
+
124
+ Finds validation schemas in your code:
125
+
126
+ - Zod schemas (`z.object(...)`)
127
+ - Joi schemas (`Joi.object(...)`)
128
+ - class-validator decorators (`@IsString()`, etc.)
129
+
130
+ ### 5. Test Generation
131
+
132
+ Creates ready-to-run Bun tests:
133
+
134
+ ```typescript
135
+ import { test, expect, describe } from "bun:test";
136
+
137
+ describe("Users Routes", () => {
138
+ test("GET /users/:id", async () => {
139
+ const response = await fetch(`${baseUrl}/users/1`);
140
+ expect(response).toBeDefined();
141
+ });
142
+ });
143
+ ```
144
+
145
+ ### 6. OpenAPI/Swagger Generation
146
+
147
+ Generates industry-standard OpenAPI 3.0 specification:
148
+
149
+ - **`openapi.json`** - Complete OpenAPI 3.0 spec
150
+ - **`api-docs.html`** - Interactive Swagger UI
151
+ - Includes auth schemes, request/response examples
152
+ - Path parameters, request bodies, responses
153
+ - **Open `api-docs.html` in browser for interactive docs!**
154
+
155
+ ## ๐Ÿ”ง Configuration
156
+
157
+ The tool works out-of-the-box, but you can customize:
158
+
159
+ - **Base URL**: Edit the `baseUrl` in generated environment files
160
+ - **Auth Token Detection**: Supports `token`, `access_token`, and `accessToken` fields
161
+ - **Route Detection**: Looks for `router.*` patterns in TypeScript files
162
+
163
+ ## ๐Ÿ“‹ Requirements
164
+
165
+ - Bun runtime
166
+ - Backend with TypeScript route definitions
167
+ - Routes using `router.get()`, `router.post()`, etc. pattern
168
+
169
+ ## ๐Ÿšฆ Example Output
170
+
171
+ ```
172
+ ๐Ÿš€ AITesting v1.0 - Full Workflow
173
+
174
+ ๐Ÿ” Scanning backend in: ./backend
175
+
176
+ โœ… Found 23 routes
177
+ ๐Ÿ” Detected auth endpoint: POST /auth/login
178
+ ๐Ÿ“‹ Detected schemas: zod, class-validator
179
+
180
+ ๐Ÿงช Running live endpoint tests...
181
+
182
+ โœ… Auth token obtained
183
+ โœ“ GET /users 200
184
+ โœ“ POST /users 201
185
+ โœ“ GET /users/:id 200
186
+ โœ— DELETE /users/:id 401
187
+
188
+ ๐Ÿ“Š Live test report saved to .aitesting/report.json
189
+
190
+ ๐Ÿ“ Generated: postman_collection.json
191
+ ๐ŸŒ Environments generated (local & production)
192
+ ๐Ÿงช Test files generated in tests/auto/
193
+ ๐Ÿ“˜ Enhanced documentation generated (API_DOCUMENTATION.md)
194
+
195
+ โœ… Complete! All features executed successfully.
196
+ ```
197
+
198
+ ## ๐Ÿ“ Important Notes
199
+
200
+ ### Live Testing (with `--all` flag)
201
+
202
+ - **โš ๏ธ Requires your backend to be running** at `http://localhost:5000` (default)
203
+ - Makes **REAL HTTP requests** using `fetch()` - not mock/sample data!
204
+ - Path parameters (e.g., `:id`) are replaced with `1` during testing
205
+ - Failed tests are recorded in the report but don't stop the workflow
206
+ - Responses in `openapi.json` and Postman collection are **actual responses** from your API
207
+
208
+ ### OpenAPI/Swagger UI
209
+
210
+ - Open `api-docs.html` in any browser for interactive documentation
211
+ - Works offline - Swagger UI loads from CDN
212
+ - Can be hosted on any web server for team sharing
213
+
214
+ ## ๐Ÿค Contributing
215
+
216
+ This is a productivity tool. Feel free to extend and customize for your needs!
217
+
218
+ ## ๐Ÿ“„ License
219
+
220
+ See LICENSE file.
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { generateCollection } from "../src/generate.js";
4
+
5
+ console.log("โœ… AITesting CLI v1.1.0");
6
+
7
+ program
8
+ .name("aitesting")
9
+ .description("AI-powered API testing & documentation generator")
10
+ .version("1.1.0")
11
+ .option("-p, --path <path>", "Path to your backend folder", ".")
12
+ .option(
13
+ "--all",
14
+ "Run complete workflow (scan, test, schemas, auth, tests, docs)"
15
+ )
16
+ .option(
17
+ "--url <url>",
18
+ "Base URL for live testing (default: http://localhost:5050)"
19
+ )
20
+ .option(
21
+ "-o, --output <path>",
22
+ "Output directory for generated files (default: current directory)"
23
+ )
24
+ .action(async (opts) => {
25
+ await generateCollection(opts.path, {
26
+ all: opts.all || false,
27
+ baseUrl: opts.url,
28
+ outputDir: opts.output,
29
+ });
30
+ });
31
+
32
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "aitesting",
3
+ "version": "1.1.0",
4
+ "description": "AI-powered API testing & documentation generator with OpenAPI/Swagger support",
5
+ "main": "bin/postmanai.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=18.0.0"
9
+ },
10
+ "scripts": {
11
+ "prepublishOnly": "echo 'Ready to publish!'"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "src",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "dependencies": {
20
+ "@babel/parser": "^7.28.4",
21
+ "@babel/traverse": "^7.28.4",
22
+ "chalk": "^5.6.2",
23
+ "commander": "^14.0.1",
24
+ "glob": "^11.0.3"
25
+ },
26
+ "bin": {
27
+ "aitesting": "./bin/postmanai.js"
28
+ },
29
+ "keywords": [
30
+ "api",
31
+ "testing",
32
+ "openapi",
33
+ "swagger",
34
+ "postman",
35
+ "documentation",
36
+ "cli",
37
+ "api-documentation",
38
+ "test-generator",
39
+ "openapi-generator"
40
+ ],
41
+ "author": "devxcant",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git@github-devx:devXcant/api_testing_ai.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/devXcant/api_testing_ai/issues"
49
+ },
50
+ "homepage": "https://github.com/devXcant/api_testing_ai"
51
+ }
@@ -0,0 +1,833 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import * as babel from "@babel/parser";
4
+ import * as t from "@babel/types";
5
+ import traverseModule from "@babel/traverse";
6
+ import chalk from "chalk";
7
+
8
+ import * as globModule from "glob";
9
+ const glob = globModule.glob || globModule.default || globModule;
10
+
11
+ const traverse = traverseModule.default || traverseModule;
12
+
13
+ function detectRoutePrefixes(basePath) {
14
+ const mainFiles = glob.sync(
15
+ `${basePath}/**/{index,app,server,main}.{ts,js}`,
16
+ {
17
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
18
+ }
19
+ );
20
+
21
+ const prefixMap = {};
22
+
23
+ for (const file of mainFiles) {
24
+ const ast = parseFile(file);
25
+ if (!ast) continue;
26
+
27
+ traverse(ast, {
28
+ CallExpression(nodePath) {
29
+ const callee = nodePath.node.callee;
30
+
31
+ if (
32
+ t.isMemberExpression(callee) &&
33
+ t.isIdentifier(callee.property, { name: "use" })
34
+ ) {
35
+ const args = nodePath.node.arguments;
36
+
37
+ if (args.length >= 2 && t.isStringLiteral(args[0])) {
38
+ const prefix = args[0].value;
39
+ const routeImport = args[1];
40
+
41
+ let routeIdentifier = null;
42
+ if (t.isIdentifier(routeImport)) {
43
+ routeIdentifier = routeImport.name;
44
+ }
45
+
46
+ if (routeIdentifier) {
47
+ nodePath.scope.traverse(nodePath.scope.block, {
48
+ ImportDeclaration(importPath) {
49
+ importPath.node.specifiers.forEach((spec) => {
50
+ if (
51
+ t.isImportDefaultSpecifier(spec) &&
52
+ spec.local.name === routeIdentifier
53
+ ) {
54
+ const importSource = importPath.node.source.value;
55
+ const routeFile = importSource
56
+ .replace(/^\.\//, "")
57
+ .replace(/^\//, "");
58
+ prefixMap[routeFile] = prefix;
59
+ }
60
+ });
61
+ },
62
+ });
63
+ }
64
+ }
65
+ }
66
+ },
67
+ });
68
+ }
69
+
70
+ return prefixMap;
71
+ }
72
+
73
+ function parseFile(filePath) {
74
+ try {
75
+ const content = fs.readFileSync(filePath, "utf8");
76
+ return babel.parse(content, {
77
+ sourceType: "module",
78
+ plugins: ["typescript", "jsx", "decorators-legacy"],
79
+ });
80
+ } catch (err) {
81
+ console.warn(
82
+ chalk.yellow(`โš ๏ธ Failed to parse ${filePath}: ${err.message}`)
83
+ );
84
+ return null;
85
+ }
86
+ }
87
+
88
+ function extractRoutesFromFile(filePath) {
89
+ const ast = parseFile(filePath);
90
+ if (!ast) return { routes: [], authEndpoint: null };
91
+
92
+ const routes = [];
93
+ let authEndpoint = null;
94
+
95
+ traverse(ast, {
96
+ CallExpression(nodePath) {
97
+ const callee = nodePath.node.callee;
98
+ if (
99
+ t.isMemberExpression(callee) &&
100
+ t.isIdentifier(callee.object, { name: "router" })
101
+ ) {
102
+ const method = callee.property.name?.toUpperCase?.();
103
+ const routeArg = nodePath.node.arguments?.[0];
104
+ if (method && t.isStringLiteral(routeArg)) {
105
+ const routePath = routeArg.value;
106
+ const isAuth =
107
+ routePath.includes("login") || routePath.includes("signin");
108
+
109
+ const route = {
110
+ method,
111
+ path: routePath,
112
+ file: filePath,
113
+ isAuth,
114
+ schema: null,
115
+ };
116
+
117
+ if (isAuth && method === "POST" && !authEndpoint) {
118
+ authEndpoint = route;
119
+ }
120
+
121
+ routes.push(route);
122
+ }
123
+ }
124
+ },
125
+ });
126
+
127
+ return { routes, authEndpoint };
128
+ }
129
+
130
+ function detectSchemas(filePath) {
131
+ const ast = parseFile(filePath);
132
+ if (!ast) return [];
133
+
134
+ const schemas = [];
135
+
136
+ traverse(ast, {
137
+ CallExpression(nodePath) {
138
+ const callee = nodePath.node.callee;
139
+
140
+ if (t.isMemberExpression(callee)) {
141
+ const objName = callee.object?.name;
142
+ const propName = callee.property?.name;
143
+
144
+ if (
145
+ objName === "z" ||
146
+ (propName &&
147
+ ["object", "string", "number", "boolean", "array"].includes(
148
+ propName
149
+ ))
150
+ ) {
151
+ schemas.push({ type: "zod", path: filePath });
152
+ }
153
+ }
154
+
155
+ if (t.isIdentifier(callee) && callee.name === "Joi") {
156
+ schemas.push({ type: "joi", path: filePath });
157
+ }
158
+ },
159
+ Decorator(nodePath) {
160
+ const expr = nodePath.node.expression;
161
+ if (t.isCallExpression(expr) && t.isIdentifier(expr.callee)) {
162
+ const decoratorName = expr.callee.name;
163
+ if (
164
+ ["IsString", "IsNumber", "IsEmail", "IsOptional"].includes(
165
+ decoratorName
166
+ )
167
+ ) {
168
+ schemas.push({ type: "class-validator", path: filePath });
169
+ }
170
+ }
171
+ },
172
+ });
173
+
174
+ return schemas;
175
+ }
176
+
177
+ function groupRoutesByController(routes) {
178
+ const grouped = {};
179
+ for (const route of routes) {
180
+ const folder = path.basename(path.dirname(route.file));
181
+ if (!grouped[folder]) grouped[folder] = [];
182
+ grouped[folder].push(route);
183
+ }
184
+ return grouped;
185
+ }
186
+
187
+ async function testLiveEndpoint(route, baseUrl, authToken = null) {
188
+ try {
189
+ const url = `${baseUrl}${route.path.replace(/:\w+/g, "1")}`;
190
+ const headers = { "Content-Type": "application/json" };
191
+ if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
192
+
193
+ const options = {
194
+ method: route.method,
195
+ headers,
196
+ };
197
+
198
+ if (["POST", "PUT", "PATCH"].includes(route.method)) {
199
+ options.body = JSON.stringify({});
200
+ }
201
+
202
+ const response = await fetch(url, options);
203
+ const responseText = await response.text();
204
+
205
+ let responseBody;
206
+ try {
207
+ responseBody = JSON.parse(responseText);
208
+ } catch {
209
+ responseBody = responseText;
210
+ }
211
+
212
+ return {
213
+ success: response.ok,
214
+ status: response.status,
215
+ headers: Object.fromEntries(response.headers.entries()),
216
+ body: responseBody,
217
+ };
218
+ } catch (err) {
219
+ return {
220
+ success: false,
221
+ error: err.message,
222
+ };
223
+ }
224
+ }
225
+
226
+ function createPostmanCollection(
227
+ groupedRoutes,
228
+ liveResults = {},
229
+ authInfo = null
230
+ ) {
231
+ const collection = {
232
+ info: {
233
+ name: "AI Testing Collection",
234
+ schema:
235
+ "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
236
+ },
237
+ item: [],
238
+ };
239
+
240
+ for (const [folder, routes] of Object.entries(groupedRoutes)) {
241
+ const folderItem = {
242
+ name: folder.charAt(0).toUpperCase() + folder.slice(1),
243
+ item: routes.map((r) => {
244
+ const item = {
245
+ name: `${r.method} ${r.path}`,
246
+ request: {
247
+ method: r.method,
248
+ header: [],
249
+ url: {
250
+ raw: `{{baseUrl}}${r.path}`,
251
+ host: ["{{baseUrl}}"],
252
+ path: r.path.split("/").filter(Boolean),
253
+ },
254
+ },
255
+ };
256
+
257
+ if (authInfo && !r.isAuth) {
258
+ item.request.header.push({
259
+ key: "Authorization",
260
+ value: "Bearer {{authToken}}",
261
+ type: "text",
262
+ });
263
+ }
264
+
265
+ if (["POST", "PUT", "PATCH"].includes(r.method)) {
266
+ item.request.header.push({
267
+ key: "Content-Type",
268
+ value: "application/json",
269
+ });
270
+ item.request.body = {
271
+ mode: "raw",
272
+ raw: JSON.stringify({}, null, 2),
273
+ };
274
+ }
275
+
276
+ const liveKey = `${r.method} ${r.path}`;
277
+ if (liveResults[liveKey]) {
278
+ item.response = [
279
+ {
280
+ name: "Example Response",
281
+ status: `${liveResults[liveKey].status}`,
282
+ code: liveResults[liveKey].status,
283
+ body: JSON.stringify(liveResults[liveKey].body, null, 2),
284
+ },
285
+ ];
286
+ }
287
+
288
+ return item;
289
+ }),
290
+ };
291
+ collection.item.push(folderItem);
292
+ }
293
+
294
+ return collection;
295
+ }
296
+
297
+ function createEnvironmentFiles(authInfo = null) {
298
+ const baseValues = [
299
+ { key: "baseUrl", value: "http://localhost:5050", enabled: true },
300
+ ];
301
+
302
+ if (authInfo) {
303
+ baseValues.push({
304
+ key: "authToken",
305
+ value: "your_jwt_token_here",
306
+ enabled: true,
307
+ });
308
+ }
309
+
310
+ const envs = [
311
+ {
312
+ name: "Local Environment",
313
+ values: baseValues,
314
+ },
315
+ {
316
+ name: "Production Environment",
317
+ values: [
318
+ { key: "baseUrl", value: "https://api.example.com", enabled: true },
319
+ ...(authInfo
320
+ ? [{ key: "authToken", value: "your_jwt_token_here", enabled: true }]
321
+ : []),
322
+ ],
323
+ },
324
+ ];
325
+
326
+ envs.forEach((env) => {
327
+ fs.writeFileSync(
328
+ `${env.name.replace(" ", "_").toLowerCase()}.postman_environment.json`,
329
+ JSON.stringify(env, null, 2)
330
+ );
331
+ });
332
+ console.log(chalk.green("๐ŸŒ Environments generated (local & production)"));
333
+ }
334
+
335
+ function generateTestFiles(groupedRoutes, authInfo = null) {
336
+ const testsDir = "./tests/auto";
337
+ if (!fs.existsSync(testsDir)) {
338
+ fs.mkdirSync(testsDir, { recursive: true });
339
+ }
340
+
341
+ for (const [folder, routes] of Object.entries(groupedRoutes)) {
342
+ const testContent = `import { test, expect, describe } from "bun:test";
343
+
344
+ const baseUrl = "http://localhost:5050";
345
+ ${authInfo ? 'let authToken = "";' : ""}
346
+
347
+ ${
348
+ authInfo
349
+ ? `describe("Authentication", () => {
350
+ test("should login and get token", async () => {
351
+ const response = await fetch(\`\${baseUrl}${authInfo.path}\`, {
352
+ method: "POST",
353
+ headers: { "Content-Type": "application/json" },
354
+ body: JSON.stringify({
355
+ email: "test@example.com",
356
+ password: "password123"
357
+ })
358
+ });
359
+
360
+ const data = await response.json();
361
+ expect(response.ok).toBe(true);
362
+
363
+ authToken = data.token || data.access_token || data.accessToken;
364
+ expect(authToken).toBeDefined();
365
+ });
366
+ });
367
+
368
+ `
369
+ : ""
370
+ }describe("${folder.charAt(0).toUpperCase() + folder.slice(1)} Routes", () => {
371
+ ${routes
372
+ .map(
373
+ (r) => ` test("${r.method} ${r.path}", async () => {
374
+ const url = \`\${baseUrl}${r.path.replace(/:\w+/g, "1")}\`;
375
+ const options = {
376
+ method: "${r.method}",
377
+ headers: {
378
+ "Content-Type": "application/json"${
379
+ authInfo && !r.isAuth
380
+ ? ',\n "Authorization": `Bearer ${authToken}`'
381
+ : ""
382
+ }
383
+ }${
384
+ ["POST", "PUT", "PATCH"].includes(r.method)
385
+ ? ",\n body: JSON.stringify({})"
386
+ : ""
387
+ }
388
+ };
389
+
390
+ const response = await fetch(url, options);
391
+ expect(response).toBeDefined();
392
+ });
393
+ `
394
+ )
395
+ .join("\n")}});
396
+ `;
397
+
398
+ fs.writeFileSync(`${testsDir}/${folder}.test.ts`, testContent);
399
+ }
400
+
401
+ console.log(chalk.green(`๐Ÿงช Test files generated in ${testsDir}/`));
402
+ }
403
+
404
+ function generateOpenAPISpec(
405
+ groupedRoutes,
406
+ schemas = [],
407
+ authInfo = null,
408
+ liveResults = {}
409
+ ) {
410
+ const spec = {
411
+ openapi: "3.0.0",
412
+ info: {
413
+ title: "API Documentation",
414
+ version: "1.0.0",
415
+ description: "Auto-generated API documentation from AITesting",
416
+ },
417
+ servers: [
418
+ {
419
+ url: "http://localhost:5050",
420
+ description: "Local Development Server",
421
+ },
422
+ {
423
+ url: "https://api.example.com",
424
+ description: "Production Server",
425
+ },
426
+ ],
427
+ paths: {},
428
+ };
429
+
430
+ if (authInfo) {
431
+ spec.components = {
432
+ securitySchemes: {
433
+ BearerAuth: {
434
+ type: "http",
435
+ scheme: "bearer",
436
+ bearerFormat: "JWT",
437
+ },
438
+ },
439
+ };
440
+ }
441
+
442
+ for (const [folder, routes] of Object.entries(groupedRoutes)) {
443
+ for (const route of routes) {
444
+ const pathKey = route.path.replace(/:(\w+)/g, "{$1}");
445
+
446
+ if (!spec.paths[pathKey]) {
447
+ spec.paths[pathKey] = {};
448
+ }
449
+
450
+ const operation = {
451
+ tags: [folder.charAt(0).toUpperCase() + folder.slice(1)],
452
+ summary: `${route.method} ${route.path}`,
453
+ description: `Source: ${route.file}`,
454
+ parameters: [],
455
+ responses: {},
456
+ };
457
+
458
+ if (authInfo && !route.isAuth) {
459
+ operation.security = [{ BearerAuth: [] }];
460
+ }
461
+
462
+ const pathParams = route.path.match(/:(\w+)/g);
463
+ if (pathParams) {
464
+ pathParams.forEach((param) => {
465
+ operation.parameters.push({
466
+ name: param.slice(1),
467
+ in: "path",
468
+ required: true,
469
+ schema: { type: "string" },
470
+ description: "Resource identifier",
471
+ });
472
+ });
473
+ }
474
+
475
+ if (["POST", "PUT", "PATCH"].includes(route.method)) {
476
+ operation.requestBody = {
477
+ required: true,
478
+ content: {
479
+ "application/json": {
480
+ schema: {
481
+ type: "object",
482
+ properties: {},
483
+ },
484
+ },
485
+ },
486
+ };
487
+ }
488
+
489
+ const liveKey = `${route.method} ${route.path}`;
490
+ if (liveResults[liveKey]) {
491
+ const result = liveResults[liveKey];
492
+ const statusCode = result.status || 200;
493
+
494
+ operation.responses[statusCode] = {
495
+ description: result.success
496
+ ? "Successful response"
497
+ : "Error response",
498
+ content: {
499
+ "application/json": {
500
+ schema: {
501
+ type: "object",
502
+ },
503
+ example: result.body,
504
+ },
505
+ },
506
+ };
507
+ } else {
508
+ operation.responses["200"] = {
509
+ description: "Successful response",
510
+ };
511
+ }
512
+
513
+ spec.paths[pathKey][route.method.toLowerCase()] = operation;
514
+ }
515
+ }
516
+
517
+ fs.writeFileSync("openapi.json", JSON.stringify(spec, null, 2));
518
+ console.log(chalk.green("๐Ÿ“œ OpenAPI specification generated (openapi.json)"));
519
+
520
+ const swaggerHTML = `<!DOCTYPE html>
521
+ <html lang="en">
522
+ <head>
523
+ <meta charset="UTF-8">
524
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
525
+ <title>API Documentation - Swagger UI</title>
526
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui.css">
527
+ </head>
528
+ <body>
529
+ <div id="swagger-ui"></div>
530
+ <script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-bundle.js"></script>
531
+ <script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-standalone-preset.js"></script>
532
+ <script>
533
+ window.onload = function() {
534
+ SwaggerUIBundle({
535
+ url: './openapi.json',
536
+ dom_id: '#swagger-ui',
537
+ deepLinking: true,
538
+ presets: [
539
+ SwaggerUIBundle.presets.apis,
540
+ SwaggerUIStandalonePreset
541
+ ],
542
+ plugins: [
543
+ SwaggerUIBundle.plugins.DownloadUrl
544
+ ],
545
+ layout: "StandaloneLayout"
546
+ });
547
+ };
548
+ </script>
549
+ </body>
550
+ </html>`;
551
+
552
+ fs.writeFileSync("api-docs.html", swaggerHTML);
553
+ console.log(chalk.green("๐ŸŒ Swagger UI page generated (api-docs.html)"));
554
+ }
555
+
556
+ function generateEnhancedDocs(
557
+ groupedRoutes,
558
+ schemas = [],
559
+ authInfo = null,
560
+ liveResults = {}
561
+ ) {
562
+ let doc = `# ๐Ÿ“š API Documentation
563
+
564
+ Generated by AITesting - ${new Date().toLocaleDateString()}
565
+
566
+ ---
567
+
568
+ `;
569
+
570
+ if (authInfo) {
571
+ doc += `## ๐Ÿ” Authentication
572
+
573
+ This API uses JWT Bearer token authentication.
574
+
575
+ **Login Endpoint:** \`${authInfo.method} ${authInfo.path}\`
576
+
577
+ **Headers Required:**
578
+ \`\`\`
579
+ Authorization: Bearer <your_token>
580
+ \`\`\`
581
+
582
+ ---
583
+
584
+ `;
585
+ }
586
+
587
+ if (schemas.length > 0) {
588
+ const schemaTypes = [...new Set(schemas.map((s) => s.type))];
589
+ doc += `## ๐Ÿ“‹ Validation Schemas
590
+
591
+ Detected validation libraries: ${schemaTypes.join(", ")}
592
+
593
+ ---
594
+
595
+ `;
596
+ }
597
+
598
+ for (const [folder, routes] of Object.entries(groupedRoutes)) {
599
+ doc += `## ${folder.charAt(0).toUpperCase() + folder.slice(1)}\n\n`;
600
+
601
+ for (const route of routes) {
602
+ doc += `### ${route.method} ${route.path}\n\n`;
603
+
604
+ if (route.isAuth) {
605
+ doc += `๐Ÿ”“ **Public endpoint** (Authentication)\n\n`;
606
+ }
607
+
608
+ const pathParams = route.path.match(/:\w+/g);
609
+ if (pathParams) {
610
+ doc += `**Path Parameters:**\n`;
611
+ pathParams.forEach((param) => {
612
+ doc += `- \`${param.slice(1)}\` - ID or identifier\n`;
613
+ });
614
+ doc += `\n`;
615
+ }
616
+
617
+ if (["POST", "PUT", "PATCH"].includes(route.method)) {
618
+ doc += `**Request Body:**\n\`\`\`json\n{}\n\`\`\`\n\n`;
619
+ }
620
+
621
+ const liveKey = `${route.method} ${route.path}`;
622
+ if (liveResults[liveKey] && liveResults[liveKey].success) {
623
+ doc += `**Example Response (${
624
+ liveResults[liveKey].status
625
+ }):**\n\`\`\`json\n${JSON.stringify(
626
+ liveResults[liveKey].body,
627
+ null,
628
+ 2
629
+ )}\n\`\`\`\n\n`;
630
+ }
631
+
632
+ doc += `**File:** \`${route.file}\`\n\n`;
633
+ doc += `---\n\n`;
634
+ }
635
+ }
636
+
637
+ fs.writeFileSync("API_DOCUMENTATION.md", doc);
638
+ console.log(
639
+ chalk.green("๐Ÿ“˜ Enhanced documentation generated (API_DOCUMENTATION.md)")
640
+ );
641
+ }
642
+
643
+ export async function generateCollection(basePath, options = {}) {
644
+ const baseUrl = options.baseUrl || "http://localhost:5050";
645
+ const outputDir = options.outputDir || ".";
646
+
647
+ console.log(chalk.cyan(`\n๐Ÿš€ AITesting v1.1.0 - Full Workflow\n`));
648
+ console.log(chalk.cyan(`๐Ÿ” Scanning backend in: ${basePath}\n`));
649
+ if (options.all && baseUrl) {
650
+ console.log(chalk.blue(`๐ŸŒ Testing URL: ${baseUrl}\n`));
651
+ }
652
+
653
+ console.log(chalk.cyan(`๐Ÿ” Detecting route prefixes...\n`));
654
+ const prefixMap = detectRoutePrefixes(basePath);
655
+
656
+ if (Object.keys(prefixMap).length > 0) {
657
+ console.log(chalk.green(`โœ… Found route prefixes:`));
658
+ Object.entries(prefixMap).forEach(([file, prefix]) => {
659
+ console.log(chalk.white(` ${file} โ†’ ${prefix}`));
660
+ });
661
+ console.log();
662
+ }
663
+
664
+ const files = glob.sync(`${basePath}/**/*.ts`, {
665
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
666
+ });
667
+
668
+ const allRoutes = [];
669
+ let authEndpoint = null;
670
+ const allSchemas = [];
671
+
672
+ for (const file of files) {
673
+ const { routes, authEndpoint: auth } = extractRoutesFromFile(file);
674
+
675
+ const relativeFile = file.replace(`${basePath}/`, "").replace(/\\/g, "/");
676
+
677
+ routes.forEach((route) => {
678
+ for (const [routeFile, prefix] of Object.entries(prefixMap)) {
679
+ if (
680
+ relativeFile.includes(routeFile) ||
681
+ relativeFile.endsWith(routeFile + ".ts")
682
+ ) {
683
+ route.path = prefix + route.path;
684
+ route.prefix = prefix;
685
+ break;
686
+ }
687
+ }
688
+ });
689
+
690
+ allRoutes.push(...routes);
691
+
692
+ if (auth) {
693
+ authEndpoint = auth;
694
+ }
695
+
696
+ if (options.all) {
697
+ const schemas = detectSchemas(file);
698
+ allSchemas.push(...schemas);
699
+ }
700
+ }
701
+
702
+ if (!allRoutes.length) {
703
+ console.warn(chalk.yellow("โš ๏ธ No routes found."));
704
+ return;
705
+ }
706
+
707
+ console.log(chalk.green(`โœ… Found ${allRoutes.length} routes`));
708
+
709
+ if (authEndpoint) {
710
+ console.log(
711
+ chalk.blue(
712
+ `๐Ÿ” Detected auth endpoint: ${authEndpoint.method} ${authEndpoint.path}`
713
+ )
714
+ );
715
+ }
716
+
717
+ if (allSchemas.length > 0) {
718
+ const schemaTypes = [...new Set(allSchemas.map((s) => s.type))];
719
+ console.log(
720
+ chalk.magenta(`๐Ÿ“‹ Detected schemas: ${schemaTypes.join(", ")}`)
721
+ );
722
+ }
723
+
724
+ const groupedRoutes = groupRoutesByController(allRoutes);
725
+
726
+ let liveResults = {};
727
+ let authToken = null;
728
+
729
+ if (options.all) {
730
+ console.log(chalk.cyan(`\n๐Ÿงช Running live endpoint tests...\n`));
731
+ console.log(
732
+ chalk.yellow(`โš ๏ธ Making REAL HTTP requests to your backend!\n`)
733
+ );
734
+
735
+ if (authEndpoint) {
736
+ const authResult = await testLiveEndpoint(authEndpoint, baseUrl);
737
+ if (authResult.success && authResult.body) {
738
+ authToken =
739
+ authResult.body.token ||
740
+ authResult.body.access_token ||
741
+ authResult.body.accessToken;
742
+ if (authToken) {
743
+ console.log(chalk.green(`โœ… Auth token obtained`));
744
+ }
745
+ }
746
+ liveResults[`${authEndpoint.method} ${authEndpoint.path}`] = authResult;
747
+ }
748
+
749
+ for (const route of allRoutes) {
750
+ if (route.isAuth) continue;
751
+
752
+ const result = await testLiveEndpoint(route, baseUrl, authToken);
753
+ liveResults[`${route.method} ${route.path}`] = result;
754
+
755
+ const status = result.success ? chalk.green("โœ“") : chalk.red("โœ—");
756
+ console.log(
757
+ `${status} ${route.method} ${route.path} ${result.status || ""}`
758
+ );
759
+ }
760
+
761
+ const reportDir = "./.aitesting";
762
+ if (!fs.existsSync(reportDir)) {
763
+ fs.mkdirSync(reportDir, { recursive: true });
764
+ }
765
+
766
+ fs.writeFileSync(
767
+ `${reportDir}/report.json`,
768
+ JSON.stringify(
769
+ { routes: allRoutes, results: liveResults, authEndpoint },
770
+ null,
771
+ 2
772
+ )
773
+ );
774
+ console.log(
775
+ chalk.green(`\n๐Ÿ“Š Live test report saved to .aitesting/report.json`)
776
+ );
777
+ }
778
+
779
+ const collection = createPostmanCollection(
780
+ groupedRoutes,
781
+ liveResults,
782
+ authEndpoint
783
+ );
784
+ fs.writeFileSync(
785
+ "postman_collection.json",
786
+ JSON.stringify(collection, null, 2)
787
+ );
788
+ console.log(chalk.green(`\n๐Ÿ“ Generated: postman_collection.json`));
789
+
790
+ createEnvironmentFiles(authEndpoint);
791
+
792
+ if (options.all) {
793
+ generateTestFiles(groupedRoutes, authEndpoint);
794
+ generateOpenAPISpec(groupedRoutes, allSchemas, authEndpoint, liveResults);
795
+ generateEnhancedDocs(groupedRoutes, allSchemas, authEndpoint, liveResults);
796
+ }
797
+
798
+ console.log(
799
+ chalk.green.bold("\nโœ… Complete! All features executed successfully.\n")
800
+ );
801
+
802
+ if (options.all) {
803
+ console.log(chalk.cyan("๐Ÿ“ฆ Generated files:"));
804
+ console.log(
805
+ chalk.white(" - postman_collection.json (with example responses)")
806
+ );
807
+ console.log(chalk.white(" - local_environment.postman_environment.json"));
808
+ console.log(
809
+ chalk.white(" - production_environment.postman_environment.json")
810
+ );
811
+ console.log(chalk.white(" - openapi.json (OpenAPI 3.0 specification)"));
812
+ console.log(
813
+ chalk.white(" - api-docs.html (Swagger UI - open in browser)")
814
+ );
815
+ console.log(
816
+ chalk.white(" - API_DOCUMENTATION.md (markdown documentation)")
817
+ );
818
+ console.log(chalk.white(" - tests/auto/*.test.ts (automated test files)"));
819
+ console.log(
820
+ chalk.white(" - .aitesting/report.json (live test results)\n")
821
+ );
822
+ console.log(
823
+ chalk.blue(
824
+ "\n๐Ÿ’ก Tip: Open api-docs.html in your browser to view interactive Swagger UI!"
825
+ )
826
+ );
827
+ console.log(
828
+ chalk.yellow(
829
+ "โš ๏ธ Note: Live responses are from REAL HTTP requests to your backend.\n"
830
+ )
831
+ );
832
+ }
833
+ }