@rog0x/mcp-testing-tools 1.0.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/README.md +109 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +250 -0
- package/dist/tools/api-mock.d.ts +30 -0
- package/dist/tools/api-mock.js +193 -0
- package/dist/tools/assertion-helper.d.ts +25 -0
- package/dist/tools/assertion-helper.js +223 -0
- package/dist/tools/mock-data.d.ts +14 -0
- package/dist/tools/mock-data.js +191 -0
- package/dist/tools/test-coverage-analyzer.d.ts +30 -0
- package/dist/tools/test-coverage-analyzer.js +247 -0
- package/dist/tools/test-generator.d.ts +7 -0
- package/dist/tools/test-generator.js +278 -0
- package/package.json +26 -0
- package/src/index.ts +311 -0
- package/src/tools/api-mock.ts +221 -0
- package/src/tools/assertion-helper.ts +273 -0
- package/src/tools/mock-data.ts +241 -0
- package/src/tools/test-coverage-analyzer.ts +283 -0
- package/src/tools/test-generator.ts +300 -0
- package/tsconfig.json +19 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { generateTests } from "./tools/test-generator.js";
|
|
10
|
+
import { generateMockData, generateMixedMockData } from "./tools/mock-data.js";
|
|
11
|
+
import { generateApiMockResponse } from "./tools/api-mock.js";
|
|
12
|
+
import { analyzeCoverage } from "./tools/test-coverage-analyzer.js";
|
|
13
|
+
import { generateAssertions } from "./tools/assertion-helper.js";
|
|
14
|
+
|
|
15
|
+
const server = new Server(
|
|
16
|
+
{
|
|
17
|
+
name: "mcp-testing-tools",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
capabilities: {
|
|
22
|
+
tools: {},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// List available tools
|
|
28
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
29
|
+
tools: [
|
|
30
|
+
{
|
|
31
|
+
name: "generate_tests",
|
|
32
|
+
description:
|
|
33
|
+
"Generate test cases from a function signature. Produces happy path, edge cases, error cases, and boundary value tests as Jest/Vitest test code.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object" as const,
|
|
36
|
+
properties: {
|
|
37
|
+
signature: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description:
|
|
40
|
+
'The function signature to generate tests for, e.g. "export async function fetchUser(id: number): Promise<User>"',
|
|
41
|
+
},
|
|
42
|
+
framework: {
|
|
43
|
+
type: "string",
|
|
44
|
+
enum: ["jest", "vitest"],
|
|
45
|
+
description: "Test framework to generate for (default: vitest)",
|
|
46
|
+
},
|
|
47
|
+
module_path: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description:
|
|
50
|
+
'Import path for the module under test (default: "./module")',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ["signature"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "generate_mock_data",
|
|
58
|
+
description:
|
|
59
|
+
"Generate realistic mock data: names, emails, addresses, dates, UUIDs, phone numbers, company names, credit cards (fake), IP addresses. Configurable count and locale.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object" as const,
|
|
62
|
+
properties: {
|
|
63
|
+
type: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description:
|
|
66
|
+
"Type of data to generate: name, email, address, date, uuid, phone, company, credit_card, ip",
|
|
67
|
+
},
|
|
68
|
+
count: {
|
|
69
|
+
type: "number",
|
|
70
|
+
description: "Number of items to generate (default: 10, max: 1000)",
|
|
71
|
+
},
|
|
72
|
+
locale: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: 'Locale for generated data: "en" or "es" (default: "en")',
|
|
75
|
+
},
|
|
76
|
+
types: {
|
|
77
|
+
type: "array",
|
|
78
|
+
items: { type: "string" },
|
|
79
|
+
description:
|
|
80
|
+
"Generate mixed records with multiple field types (alternative to single type). Each record will have all specified fields.",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ["type"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "generate_api_mock",
|
|
88
|
+
description:
|
|
89
|
+
"Generate mock API responses from a schema. Creates realistic JSON responses for REST endpoints based on field names and types.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object" as const,
|
|
92
|
+
properties: {
|
|
93
|
+
endpoint: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: 'API endpoint path, e.g. "/api/v1/users"',
|
|
96
|
+
},
|
|
97
|
+
method: {
|
|
98
|
+
type: "string",
|
|
99
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
100
|
+
description: "HTTP method (default: GET)",
|
|
101
|
+
},
|
|
102
|
+
fields: {
|
|
103
|
+
type: "array",
|
|
104
|
+
description:
|
|
105
|
+
"Array of field schemas. Each field has: name (string), type (string|number|boolean|date|array|object), optional items (for arrays), optional fields (for nested objects), optional nullable, optional enum.",
|
|
106
|
+
items: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
name: { type: "string" },
|
|
110
|
+
type: { type: "string" },
|
|
111
|
+
items: { type: "array" },
|
|
112
|
+
fields: { type: "array" },
|
|
113
|
+
nullable: { type: "boolean" },
|
|
114
|
+
enum: { type: "array", items: { type: "string" } },
|
|
115
|
+
},
|
|
116
|
+
required: ["name", "type"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
count: {
|
|
120
|
+
type: "number",
|
|
121
|
+
description:
|
|
122
|
+
"Number of records to generate (default: 1, max: 100)",
|
|
123
|
+
},
|
|
124
|
+
status_code: {
|
|
125
|
+
type: "number",
|
|
126
|
+
description: "HTTP status code for the response (default: 200)",
|
|
127
|
+
},
|
|
128
|
+
wrap_in_envelope: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
description:
|
|
131
|
+
"Wrap response in { success, data, meta } envelope (default: true)",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ["endpoint", "fields"],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "analyze_test_coverage",
|
|
139
|
+
description:
|
|
140
|
+
"Parse source code and test code to identify untested functions. Suggests which functions need tests most based on complexity, export status, and parameter count.",
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: "object" as const,
|
|
143
|
+
properties: {
|
|
144
|
+
source_code: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "The source code to analyze for functions",
|
|
147
|
+
},
|
|
148
|
+
test_code: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description:
|
|
151
|
+
"The test code to check which functions are already tested",
|
|
152
|
+
},
|
|
153
|
+
source_file_name: {
|
|
154
|
+
type: "string",
|
|
155
|
+
description:
|
|
156
|
+
'Filename for the source (used in output, default: "source.ts")',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
required: ["source_code", "test_code"],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "generate_assertions",
|
|
164
|
+
description:
|
|
165
|
+
"Given expected and actual values (as JSON strings), generate detailed assertion code with descriptive messages. Supports deep object comparison, array comparison, and type checking.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object" as const,
|
|
168
|
+
properties: {
|
|
169
|
+
expected: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description:
|
|
172
|
+
"The expected value as a JSON string (or plain string for primitives)",
|
|
173
|
+
},
|
|
174
|
+
actual: {
|
|
175
|
+
type: "string",
|
|
176
|
+
description:
|
|
177
|
+
"The actual value as a JSON string (or plain string for primitives)",
|
|
178
|
+
},
|
|
179
|
+
label: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description:
|
|
182
|
+
'Descriptive label for the comparison (default: "value comparison")',
|
|
183
|
+
},
|
|
184
|
+
framework: {
|
|
185
|
+
type: "string",
|
|
186
|
+
enum: ["jest", "vitest", "chai"],
|
|
187
|
+
description:
|
|
188
|
+
"Assertion framework to generate for (default: jest)",
|
|
189
|
+
},
|
|
190
|
+
deep: {
|
|
191
|
+
type: "boolean",
|
|
192
|
+
description:
|
|
193
|
+
"Use deep equality for objects/arrays (default: true)",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ["expected", "actual"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
// Handle tool calls
|
|
203
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
204
|
+
const { name, arguments: args } = request.params;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
switch (name) {
|
|
208
|
+
case "generate_tests": {
|
|
209
|
+
const result = generateTests(
|
|
210
|
+
args?.signature as string,
|
|
211
|
+
(args?.framework as string) || "vitest",
|
|
212
|
+
(args?.module_path as string) || "./module"
|
|
213
|
+
);
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: result }],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case "generate_mock_data": {
|
|
220
|
+
if (args?.types && Array.isArray(args.types)) {
|
|
221
|
+
const result = generateMixedMockData(
|
|
222
|
+
args.types as string[],
|
|
223
|
+
(args?.count as number) || 10,
|
|
224
|
+
(args?.locale as string) || "en"
|
|
225
|
+
);
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const result = generateMockData(
|
|
233
|
+
args?.type as string,
|
|
234
|
+
(args?.count as number) || 10,
|
|
235
|
+
(args?.locale as string) || "en"
|
|
236
|
+
);
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
case "generate_api_mock": {
|
|
243
|
+
const result = generateApiMockResponse({
|
|
244
|
+
endpoint: args?.endpoint as string,
|
|
245
|
+
method: (args?.method as string) || "GET",
|
|
246
|
+
fields: args?.fields as Array<{
|
|
247
|
+
name: string;
|
|
248
|
+
type: string;
|
|
249
|
+
items?: Array<{ name: string; type: string }>;
|
|
250
|
+
fields?: Array<{ name: string; type: string }>;
|
|
251
|
+
nullable?: boolean;
|
|
252
|
+
enum?: string[];
|
|
253
|
+
}>,
|
|
254
|
+
count: (args?.count as number) || 1,
|
|
255
|
+
statusCode: (args?.status_code as number) || 200,
|
|
256
|
+
wrapInEnvelope:
|
|
257
|
+
args?.wrap_in_envelope !== undefined
|
|
258
|
+
? (args.wrap_in_envelope as boolean)
|
|
259
|
+
: true,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
case "analyze_test_coverage": {
|
|
267
|
+
const result = analyzeCoverage(
|
|
268
|
+
args?.source_code as string,
|
|
269
|
+
args?.test_code as string,
|
|
270
|
+
(args?.source_file_name as string) || "source.ts"
|
|
271
|
+
);
|
|
272
|
+
return {
|
|
273
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case "generate_assertions": {
|
|
278
|
+
const result = generateAssertions({
|
|
279
|
+
expected: args?.expected as string,
|
|
280
|
+
actual: args?.actual as string,
|
|
281
|
+
label: (args?.label as string) || "value comparison",
|
|
282
|
+
framework:
|
|
283
|
+
(args?.framework as "jest" | "vitest" | "chai") || "jest",
|
|
284
|
+
deep: args?.deep !== undefined ? (args.deep as boolean) : true,
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
default:
|
|
292
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
293
|
+
}
|
|
294
|
+
} catch (error: unknown) {
|
|
295
|
+
const message =
|
|
296
|
+
error instanceof Error ? error.message : String(error);
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
299
|
+
isError: true,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Start server
|
|
305
|
+
async function main() {
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
console.error("MCP Testing Tools server running on stdio");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate mock API responses from a schema definition.
|
|
3
|
+
* Creates realistic JSON responses for REST endpoints
|
|
4
|
+
* based on field names and types.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface FieldSchema {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
items?: FieldSchema[];
|
|
11
|
+
fields?: FieldSchema[];
|
|
12
|
+
nullable?: boolean;
|
|
13
|
+
enum?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ApiMockOptions {
|
|
17
|
+
endpoint: string;
|
|
18
|
+
method: string;
|
|
19
|
+
fields: FieldSchema[];
|
|
20
|
+
count?: number;
|
|
21
|
+
statusCode?: number;
|
|
22
|
+
wrapInEnvelope?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class MockRng {
|
|
26
|
+
private s: number;
|
|
27
|
+
constructor(seed: number) {
|
|
28
|
+
this.s = seed;
|
|
29
|
+
}
|
|
30
|
+
next(): number {
|
|
31
|
+
this.s = (this.s * 1664525 + 1013904223) & 0x7fffffff;
|
|
32
|
+
return this.s / 0x7fffffff;
|
|
33
|
+
}
|
|
34
|
+
int(min: number, max: number): number {
|
|
35
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
36
|
+
}
|
|
37
|
+
pick<T>(arr: readonly T[]): T {
|
|
38
|
+
return arr[this.int(0, arr.length - 1)];
|
|
39
|
+
}
|
|
40
|
+
float(min: number, max: number, decimals: number = 2): number {
|
|
41
|
+
return parseFloat((this.next() * (max - min) + min).toFixed(decimals));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SAMPLE_WORDS = [
|
|
46
|
+
"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
|
|
47
|
+
"hotel", "india", "juliet", "kilo", "lima", "mike", "november",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const SAMPLE_NAMES = [
|
|
51
|
+
"Alice Morgan", "Bob Chen", "Carol Evans", "Dan Walsh", "Eve Porter",
|
|
52
|
+
"Frank Santos", "Grace Kim", "Henry Liu", "Irene Cruz", "Jack Reed",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const SAMPLE_TITLES = [
|
|
56
|
+
"Getting Started Guide", "Advanced Configuration", "Performance Tuning",
|
|
57
|
+
"Security Best Practices", "API Reference Manual", "Migration Notes",
|
|
58
|
+
"Release Highlights", "Troubleshooting Tips", "Architecture Overview",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const SAMPLE_DESCRIPTIONS = [
|
|
62
|
+
"A comprehensive solution for modern workflows.",
|
|
63
|
+
"Streamlined process with enterprise-grade reliability.",
|
|
64
|
+
"Built for scalability and high availability.",
|
|
65
|
+
"Designed with developer experience in mind.",
|
|
66
|
+
"Optimized for performance and low latency.",
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const SAMPLE_URLS = [
|
|
70
|
+
"https://api.example.com/v1/resource",
|
|
71
|
+
"https://cdn.example.com/assets/image.png",
|
|
72
|
+
"https://docs.example.com/guides/setup",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const SAMPLE_EMAILS = [
|
|
76
|
+
"user@example.com", "admin@testdomain.org", "support@mockapi.net",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
function inferValueFromName(name: string, rng: MockRng): unknown {
|
|
80
|
+
const lower = name.toLowerCase();
|
|
81
|
+
|
|
82
|
+
if (lower === "id" || lower.endsWith("_id") || lower.endsWith("Id")) {
|
|
83
|
+
return rng.int(1, 99999);
|
|
84
|
+
}
|
|
85
|
+
if (lower === "uuid" || lower.endsWith("_uuid")) {
|
|
86
|
+
const hex = () => rng.int(0, 15).toString(16);
|
|
87
|
+
const seg = (n: number) => Array.from({ length: n }, hex).join("");
|
|
88
|
+
return `${seg(8)}-${seg(4)}-4${seg(3)}-${rng.pick(["8", "9", "a", "b"])}${seg(3)}-${seg(12)}`;
|
|
89
|
+
}
|
|
90
|
+
if (lower.includes("email")) return rng.pick(SAMPLE_EMAILS);
|
|
91
|
+
if (lower.includes("name") || lower === "author" || lower === "user") return rng.pick(SAMPLE_NAMES);
|
|
92
|
+
if (lower.includes("title") || lower === "subject") return rng.pick(SAMPLE_TITLES);
|
|
93
|
+
if (lower.includes("description") || lower.includes("summary") || lower === "body" || lower === "content") return rng.pick(SAMPLE_DESCRIPTIONS);
|
|
94
|
+
if (lower.includes("url") || lower.includes("link") || lower.includes("href")) return rng.pick(SAMPLE_URLS);
|
|
95
|
+
if (lower.includes("image") || lower.includes("avatar") || lower.includes("photo")) return `https://picsum.photos/seed/${rng.int(1, 999)}/200/200`;
|
|
96
|
+
if (lower.includes("price") || lower.includes("amount") || lower.includes("cost") || lower.includes("total")) return rng.float(1, 999, 2);
|
|
97
|
+
if (lower.includes("count") || lower.includes("quantity") || lower === "total" || lower === "size") return rng.int(0, 500);
|
|
98
|
+
if (lower.includes("rating") || lower.includes("score")) return rng.float(1, 5, 1);
|
|
99
|
+
if (lower.includes("active") || lower.includes("enabled") || lower.includes("verified") || lower.startsWith("is_") || lower.startsWith("has_")) return rng.next() > 0.3;
|
|
100
|
+
if (lower.includes("date") || lower.includes("created") || lower.includes("updated") || lower.includes("timestamp")) {
|
|
101
|
+
const y = rng.int(2020, 2026);
|
|
102
|
+
const m = String(rng.int(1, 12)).padStart(2, "0");
|
|
103
|
+
const d = String(rng.int(1, 28)).padStart(2, "0");
|
|
104
|
+
return `${y}-${m}-${d}T${String(rng.int(0, 23)).padStart(2, "0")}:${String(rng.int(0, 59)).padStart(2, "0")}:00Z`;
|
|
105
|
+
}
|
|
106
|
+
if (lower.includes("phone")) return `+1 (${rng.int(200, 999)}) ${rng.int(200, 999)}-${rng.int(1000, 9999)}`;
|
|
107
|
+
if (lower.includes("status")) return rng.pick(["active", "inactive", "pending", "completed"]);
|
|
108
|
+
if (lower.includes("type") || lower.includes("category")) return rng.pick(["standard", "premium", "basic", "enterprise"]);
|
|
109
|
+
if (lower.includes("tag")) return rng.pick(SAMPLE_WORDS);
|
|
110
|
+
if (lower.includes("color")) return `#${rng.int(0, 0xffffff).toString(16).padStart(6, "0")}`;
|
|
111
|
+
if (lower.includes("ip")) return `${rng.int(1, 254)}.${rng.int(0, 255)}.${rng.int(0, 255)}.${rng.int(1, 254)}`;
|
|
112
|
+
if (lower.includes("latitude") || lower === "lat") return rng.float(-90, 90, 6);
|
|
113
|
+
if (lower.includes("longitude") || lower === "lng" || lower === "lon") return rng.float(-180, 180, 6);
|
|
114
|
+
|
|
115
|
+
return rng.pick(SAMPLE_WORDS);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function generateFieldValue(field: FieldSchema, rng: MockRng): unknown {
|
|
119
|
+
if (field.nullable && rng.next() < 0.15) return null;
|
|
120
|
+
if (field.enum && field.enum.length > 0) return rng.pick(field.enum);
|
|
121
|
+
|
|
122
|
+
const type = field.type.toLowerCase();
|
|
123
|
+
|
|
124
|
+
if (type === "string") return String(inferValueFromName(field.name, rng));
|
|
125
|
+
if (type === "number" || type === "integer" || type === "int") {
|
|
126
|
+
const inferred = inferValueFromName(field.name, rng);
|
|
127
|
+
return typeof inferred === "number" ? inferred : rng.int(1, 1000);
|
|
128
|
+
}
|
|
129
|
+
if (type === "float" || type === "decimal" || type === "double") {
|
|
130
|
+
const inferred = inferValueFromName(field.name, rng);
|
|
131
|
+
return typeof inferred === "number" ? inferred : rng.float(0, 1000, 2);
|
|
132
|
+
}
|
|
133
|
+
if (type === "boolean" || type === "bool") {
|
|
134
|
+
const inferred = inferValueFromName(field.name, rng);
|
|
135
|
+
return typeof inferred === "boolean" ? inferred : rng.next() > 0.5;
|
|
136
|
+
}
|
|
137
|
+
if (type === "date" || type === "datetime" || type === "timestamp") {
|
|
138
|
+
return inferValueFromName(field.name.includes("date") ? field.name : "created_at", rng);
|
|
139
|
+
}
|
|
140
|
+
if (type === "array") {
|
|
141
|
+
const arrLen = rng.int(1, 5);
|
|
142
|
+
if (field.items && field.items.length > 0) {
|
|
143
|
+
if (field.items.length === 1 && !field.items[0].fields) {
|
|
144
|
+
return Array.from({ length: arrLen }, () => generateFieldValue(field.items![0], rng));
|
|
145
|
+
}
|
|
146
|
+
return Array.from({ length: arrLen }, () => {
|
|
147
|
+
const obj: Record<string, unknown> = {};
|
|
148
|
+
for (const sub of field.items!) {
|
|
149
|
+
obj[sub.name] = generateFieldValue(sub, rng);
|
|
150
|
+
}
|
|
151
|
+
return obj;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return Array.from({ length: arrLen }, () => rng.pick(SAMPLE_WORDS));
|
|
155
|
+
}
|
|
156
|
+
if (type === "object") {
|
|
157
|
+
if (field.fields && field.fields.length > 0) {
|
|
158
|
+
const obj: Record<string, unknown> = {};
|
|
159
|
+
for (const sub of field.fields) {
|
|
160
|
+
obj[sub.name] = generateFieldValue(sub, rng);
|
|
161
|
+
}
|
|
162
|
+
return obj;
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return inferValueFromName(field.name, rng);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function generateSingleRecord(fields: FieldSchema[], rng: MockRng): Record<string, unknown> {
|
|
171
|
+
const record: Record<string, unknown> = {};
|
|
172
|
+
for (const field of fields) {
|
|
173
|
+
record[field.name] = generateFieldValue(field, rng);
|
|
174
|
+
}
|
|
175
|
+
return record;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function generateApiMockResponse(options: ApiMockOptions): {
|
|
179
|
+
endpoint: string;
|
|
180
|
+
method: string;
|
|
181
|
+
statusCode: number;
|
|
182
|
+
headers: Record<string, string>;
|
|
183
|
+
body: unknown;
|
|
184
|
+
} {
|
|
185
|
+
const {
|
|
186
|
+
endpoint,
|
|
187
|
+
method = "GET",
|
|
188
|
+
fields,
|
|
189
|
+
count = 1,
|
|
190
|
+
statusCode = 200,
|
|
191
|
+
wrapInEnvelope = true,
|
|
192
|
+
} = options;
|
|
193
|
+
|
|
194
|
+
const rng = new MockRng(Date.now() % 100000);
|
|
195
|
+
const safeCount = Math.min(Math.max(1, count), 100);
|
|
196
|
+
const isList = safeCount > 1 || method.toUpperCase() === "GET" && endpoint.match(/\/\w+\/?$/);
|
|
197
|
+
|
|
198
|
+
let body: unknown;
|
|
199
|
+
|
|
200
|
+
if (safeCount === 1 && !isList) {
|
|
201
|
+
const record = generateSingleRecord(fields, rng);
|
|
202
|
+
body = wrapInEnvelope ? { success: true, data: record } : record;
|
|
203
|
+
} else {
|
|
204
|
+
const records = Array.from({ length: safeCount }, () => generateSingleRecord(fields, rng));
|
|
205
|
+
body = wrapInEnvelope
|
|
206
|
+
? { success: true, data: records, meta: { total: rng.int(safeCount, safeCount * 10), page: 1, perPage: safeCount } }
|
|
207
|
+
: records;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
endpoint,
|
|
212
|
+
method: method.toUpperCase(),
|
|
213
|
+
statusCode,
|
|
214
|
+
headers: {
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
"X-Request-Id": String(rng.int(100000, 999999)),
|
|
217
|
+
"X-RateLimit-Remaining": String(rng.int(50, 1000)),
|
|
218
|
+
},
|
|
219
|
+
body,
|
|
220
|
+
};
|
|
221
|
+
}
|