@schmock/faker 2.3.0 → 2.3.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/constants.d.ts +82 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/field-name-matcher.d.ts +19 -0
- package/dist/field-name-matcher.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/jsf-config.d.ts +19 -4
- package/dist/jsf-config.d.ts.map +1 -1
- package/dist/output-limits.d.ts +3 -0
- package/dist/output-limits.d.ts.map +1 -0
- package/dist/overrides.d.ts +2 -1
- package/dist/overrides.d.ts.map +1 -1
- package/dist/schema-children.d.ts +14 -0
- package/dist/schema-children.d.ts.map +1 -0
- package/dist/schema-enhancement.d.ts +1 -0
- package/dist/schema-enhancement.d.ts.map +1 -1
- package/dist/validation.d.ts +24 -5
- package/dist/validation.d.ts.map +1 -1
- package/package.json +17 -7
- package/dist/constants.js +0 -8
- package/dist/field-mappings.js +0 -1114
- package/dist/field-name-matcher.js +0 -148
- package/dist/jsf-config.js +0 -117
- package/dist/overrides.js +0 -137
- package/dist/schema-enhancement.js +0 -104
- package/dist/test-utils.d.ts +0 -57
- package/dist/test-utils.d.ts.map +0 -1
- package/dist/test-utils.js +0 -284
- package/dist/validation.js +0 -300
- package/src/advanced-features.test.ts +0 -912
- package/src/audit-faker-args.test.ts +0 -97
- package/src/constants.d.ts.map +0 -1
- package/src/constants.ts +0 -9
- package/src/data-quality.test.ts +0 -415
- package/src/error-handling.test.ts +0 -504
- package/src/field-mappings.test.ts +0 -351
- package/src/field-mappings.ts +0 -1148
- package/src/field-name-matcher.test.ts +0 -292
- package/src/field-name-matcher.ts +0 -187
- package/src/index.d.ts.map +0 -1
- package/src/index.test.ts +0 -1320
- package/src/index.ts +0 -185
- package/src/integration.test.ts +0 -634
- package/src/jsf-config.d.ts.map +0 -1
- package/src/jsf-config.test.ts +0 -65
- package/src/jsf-config.ts +0 -156
- package/src/overrides.d.ts.map +0 -1
- package/src/overrides.property.test.ts +0 -172
- package/src/overrides.test.ts +0 -38
- package/src/overrides.ts +0 -179
- package/src/performance.test.ts +0 -441
- package/src/plugin-integration.test.ts +0 -575
- package/src/post-process.test.ts +0 -177
- package/src/real-world.test.ts +0 -636
- package/src/schema-enhancement.d.ts.map +0 -1
- package/src/schema-enhancement.test.ts +0 -222
- package/src/schema-enhancement.ts +0 -144
- package/src/steps/deterministic-seeds.steps.ts +0 -112
- package/src/steps/faker-plugin.steps.ts +0 -301
- package/src/test-utils.ts +0 -385
- package/src/validation.d.ts.map +0 -1
- package/src/validation.ts +0 -417
package/src/index.test.ts
DELETED
|
@@ -1,1320 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema7 } from "json-schema";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { version as packageVersion } from "../package.json";
|
|
4
|
-
import { fakerPlugin, generateFromSchema } from "./index";
|
|
5
|
-
import {
|
|
6
|
-
generate,
|
|
7
|
-
performance as perf,
|
|
8
|
-
schemas,
|
|
9
|
-
schemaTests,
|
|
10
|
-
stats,
|
|
11
|
-
validators,
|
|
12
|
-
} from "./test-utils";
|
|
13
|
-
|
|
14
|
-
describe("Schema Generator", () => {
|
|
15
|
-
describe("Core Functionality", () => {
|
|
16
|
-
it("generates data from simple schemas", async () => {
|
|
17
|
-
const result = await generateFromSchema({
|
|
18
|
-
schema: schemas.simple.object({
|
|
19
|
-
id: schemas.simple.number(),
|
|
20
|
-
name: schemas.simple.string(),
|
|
21
|
-
}),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
expect(result).toHaveProperty("id");
|
|
25
|
-
expect(result).toHaveProperty("name");
|
|
26
|
-
expect(typeof result.id).toBe("number");
|
|
27
|
-
expect(typeof result.name).toBe("string");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("generates arrays with specified count", async () => {
|
|
31
|
-
const schema = schemas.simple.array(
|
|
32
|
-
schemas.simple.object({ id: schemas.simple.number() }),
|
|
33
|
-
);
|
|
34
|
-
const result = await generateFromSchema({ schema, count: 5 });
|
|
35
|
-
|
|
36
|
-
expect(Array.isArray(result)).toBe(true);
|
|
37
|
-
expect(result).toHaveLength(5);
|
|
38
|
-
result.forEach((item) => {
|
|
39
|
-
expect(item).toHaveProperty("id");
|
|
40
|
-
expect(typeof item.id).toBe("number");
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("uses one seeded sequence across a generated array", async () => {
|
|
45
|
-
const schema: JSONSchema7 = {
|
|
46
|
-
type: "array",
|
|
47
|
-
items: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
id: { type: "integer" },
|
|
51
|
-
name: { type: "string" },
|
|
52
|
-
},
|
|
53
|
-
required: ["id", "name"],
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const first = await generateFromSchema({ schema, count: 5, seed: 42 });
|
|
58
|
-
const second = await generateFromSchema({ schema, count: 5, seed: 42 });
|
|
59
|
-
|
|
60
|
-
expect(first).toEqual(second);
|
|
61
|
-
expect(Array.isArray(first)).toBe(true);
|
|
62
|
-
if (!Array.isArray(first)) {
|
|
63
|
-
throw new Error("Expected generated data to be an array");
|
|
64
|
-
}
|
|
65
|
-
expect(new Set(first.map((item) => JSON.stringify(item))).size).toBe(5);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("generates tuple positions from their respective schemas", async () => {
|
|
69
|
-
const schema: JSONSchema7 = {
|
|
70
|
-
type: "array",
|
|
71
|
-
items: [{ type: "string" }, { type: "integer" }, { type: "boolean" }],
|
|
72
|
-
minItems: 3,
|
|
73
|
-
maxItems: 3,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const result = await generateFromSchema({ schema, seed: 42 });
|
|
77
|
-
|
|
78
|
-
expect(Array.isArray(result)).toBe(true);
|
|
79
|
-
if (!Array.isArray(result)) {
|
|
80
|
-
throw new Error("Expected generated data to be a tuple");
|
|
81
|
-
}
|
|
82
|
-
expect(result).toHaveLength(3);
|
|
83
|
-
expect(typeof result[0]).toBe("string");
|
|
84
|
-
expect(typeof result[1]).toBe("number");
|
|
85
|
-
expect(typeof result[2]).toBe("boolean");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("generates a nested tuple reached through a definition reference", async () => {
|
|
89
|
-
const schema: JSONSchema7 = {
|
|
90
|
-
type: "object",
|
|
91
|
-
properties: {
|
|
92
|
-
payload: { $ref: "#/definitions/payloadTuple" },
|
|
93
|
-
},
|
|
94
|
-
required: ["payload"],
|
|
95
|
-
definitions: {
|
|
96
|
-
payloadTuple: {
|
|
97
|
-
type: "array",
|
|
98
|
-
items: [
|
|
99
|
-
{ type: "string" },
|
|
100
|
-
{ type: "integer" },
|
|
101
|
-
{ type: "boolean" },
|
|
102
|
-
],
|
|
103
|
-
minItems: 3,
|
|
104
|
-
maxItems: 3,
|
|
105
|
-
additionalItems: false,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const result = await generateFromSchema({ schema, seed: 42 });
|
|
111
|
-
|
|
112
|
-
expect(result).toMatchObject({ payload: expect.any(Array) });
|
|
113
|
-
if (
|
|
114
|
-
typeof result !== "object" ||
|
|
115
|
-
result === null ||
|
|
116
|
-
!("payload" in result) ||
|
|
117
|
-
!Array.isArray(result.payload)
|
|
118
|
-
) {
|
|
119
|
-
throw new Error("Expected generated payload to be a tuple");
|
|
120
|
-
}
|
|
121
|
-
expect(result.payload).toHaveLength(3);
|
|
122
|
-
expect(typeof result.payload[0]).toBe("string");
|
|
123
|
-
expect(typeof result.payload[1]).toBe("number");
|
|
124
|
-
expect(typeof result.payload[2]).toBe("boolean");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("honors uniqueItems while generating the complete array", async () => {
|
|
128
|
-
const schema: JSONSchema7 = {
|
|
129
|
-
type: "array",
|
|
130
|
-
items: { type: "integer" },
|
|
131
|
-
minItems: 8,
|
|
132
|
-
maxItems: 8,
|
|
133
|
-
uniqueItems: true,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const result = await generateFromSchema({ schema, seed: 42 });
|
|
137
|
-
|
|
138
|
-
expect(Array.isArray(result)).toBe(true);
|
|
139
|
-
if (!Array.isArray(result)) {
|
|
140
|
-
throw new Error("Expected generated data to be an array");
|
|
141
|
-
}
|
|
142
|
-
expect(result).toHaveLength(8);
|
|
143
|
-
expect(new Set(result).size).toBe(8);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("respects array constraints from schema", async () => {
|
|
147
|
-
const schema = schemas.simple.array(schemas.simple.string(), {
|
|
148
|
-
minItems: 2,
|
|
149
|
-
maxItems: 5,
|
|
150
|
-
});
|
|
151
|
-
const results = await generate.samples<string[]>(schema, 20);
|
|
152
|
-
|
|
153
|
-
results.forEach((result) => {
|
|
154
|
-
expect(result.length).toBeGreaterThanOrEqual(2);
|
|
155
|
-
expect(result.length).toBeLessThanOrEqual(5);
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("handles nested schemas correctly", async () => {
|
|
160
|
-
const schema = schemas.nested.deep(3, schemas.simple.string());
|
|
161
|
-
const result = await generateFromSchema({ schema });
|
|
162
|
-
|
|
163
|
-
expect(result).toHaveProperty("nested");
|
|
164
|
-
expect(result.nested).toHaveProperty("nested");
|
|
165
|
-
expect(result.nested.nested).toHaveProperty("nested");
|
|
166
|
-
expect(typeof result.nested.nested.nested).toBe("string");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("generates consistent data types", async () => {
|
|
170
|
-
const schema = schemas.complex.apiResponse();
|
|
171
|
-
const samples = await generate.samples(schema, 10);
|
|
172
|
-
|
|
173
|
-
samples.forEach((sample) => {
|
|
174
|
-
expect(typeof sample.success).toBe("boolean");
|
|
175
|
-
expect(Array.isArray(sample.data)).toBe(true);
|
|
176
|
-
expect(typeof sample.meta.page).toBe("number");
|
|
177
|
-
expect(typeof sample.meta.total).toBe("number");
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe("Schema Validation", () => {
|
|
183
|
-
describe("invalid schemas", () => {
|
|
184
|
-
it("rejects empty schema objects", async () => {
|
|
185
|
-
await schemaTests.expectInvalid({}, "Schema cannot be empty");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("rejects null and undefined schemas", async () => {
|
|
189
|
-
await schemaTests.expectInvalid(
|
|
190
|
-
null,
|
|
191
|
-
"Schema must be a valid JSON Schema object",
|
|
192
|
-
);
|
|
193
|
-
await schemaTests.expectInvalid(
|
|
194
|
-
undefined,
|
|
195
|
-
"Schema must be a valid JSON Schema object",
|
|
196
|
-
);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("rejects non-object schema types", async () => {
|
|
200
|
-
await schemaTests.expectInvalid(
|
|
201
|
-
"string",
|
|
202
|
-
"Schema must be a valid JSON Schema object",
|
|
203
|
-
);
|
|
204
|
-
await schemaTests.expectInvalid(
|
|
205
|
-
123,
|
|
206
|
-
"Schema must be a valid JSON Schema object",
|
|
207
|
-
);
|
|
208
|
-
await schemaTests.expectInvalid(
|
|
209
|
-
true,
|
|
210
|
-
"Schema must be a valid JSON Schema object",
|
|
211
|
-
);
|
|
212
|
-
await schemaTests.expectInvalid([], "Schema cannot be empty");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("rejects invalid type values", async () => {
|
|
216
|
-
await schemaTests.expectSchemaError(
|
|
217
|
-
{ type: "invalid" },
|
|
218
|
-
"$",
|
|
219
|
-
'Invalid schema type: "invalid"',
|
|
220
|
-
);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("rejects malformed object properties", async () => {
|
|
224
|
-
await schemaTests.expectSchemaError(
|
|
225
|
-
{ type: "object", properties: "invalid" },
|
|
226
|
-
"$.properties",
|
|
227
|
-
"Properties must be an object mapping",
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("rejects arrays without items", async () => {
|
|
232
|
-
await schemaTests.expectSchemaError(
|
|
233
|
-
{ type: "array", items: null },
|
|
234
|
-
"$.items",
|
|
235
|
-
"Array schema must have valid items definition",
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("validates nested schemas recursively", async () => {
|
|
240
|
-
await schemaTests.expectSchemaError(
|
|
241
|
-
{
|
|
242
|
-
type: "object",
|
|
243
|
-
properties: {
|
|
244
|
-
nested: {
|
|
245
|
-
type: "object",
|
|
246
|
-
properties: {
|
|
247
|
-
bad: { type: "invalid" },
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
"$.properties.nested.properties.bad",
|
|
253
|
-
"Invalid schema type",
|
|
254
|
-
);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe("resource limits", () => {
|
|
259
|
-
it("enforces array size limits", async () => {
|
|
260
|
-
const schema = schemas.simple.array(schemas.simple.string(), {
|
|
261
|
-
maxItems: 50000,
|
|
262
|
-
});
|
|
263
|
-
await expect(generateFromSchema({ schema })).rejects.toThrow(
|
|
264
|
-
"array_max_items",
|
|
265
|
-
);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("enforces nesting depth limits", async () => {
|
|
269
|
-
const deepSchema = schemas.nested.deep(15);
|
|
270
|
-
await expect(
|
|
271
|
-
generateFromSchema({ schema: deepSchema }),
|
|
272
|
-
).rejects.toThrow("schema_nesting_depth");
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it("detects circular references", async () => {
|
|
276
|
-
// Create a circular reference
|
|
277
|
-
const schema: any = {
|
|
278
|
-
type: "object",
|
|
279
|
-
properties: {
|
|
280
|
-
self: { $ref: "#" },
|
|
281
|
-
},
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
await schemaTests.expectInvalid(schema, /circular/i);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("prevents memory exhaustion from deep nesting with large arrays", async () => {
|
|
288
|
-
const schema = {
|
|
289
|
-
type: "object",
|
|
290
|
-
properties: {
|
|
291
|
-
level1: {
|
|
292
|
-
type: "object",
|
|
293
|
-
properties: {
|
|
294
|
-
level2: {
|
|
295
|
-
type: "object",
|
|
296
|
-
properties: {
|
|
297
|
-
level3: {
|
|
298
|
-
type: "object",
|
|
299
|
-
properties: {
|
|
300
|
-
level4: {
|
|
301
|
-
type: "array",
|
|
302
|
-
items: { type: "string" },
|
|
303
|
-
maxItems: 1000,
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
await expect(generateFromSchema({ schema })).rejects.toThrow(
|
|
315
|
-
/memory|deep_nesting/,
|
|
316
|
-
);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
describe("edge cases", () => {
|
|
321
|
-
it("handles schemas without explicit type", async () => {
|
|
322
|
-
const schema = { properties: { name: { type: "string" as const } } };
|
|
323
|
-
await schemaTests.expectValid(schema);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it("handles empty properties object", async () => {
|
|
327
|
-
const schema = { type: "object" as const, properties: {} };
|
|
328
|
-
const result = await generateFromSchema({ schema });
|
|
329
|
-
expect(typeof result).toBe("object");
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it("handles array with multiple item types", async () => {
|
|
333
|
-
const schema = {
|
|
334
|
-
type: "array" as const,
|
|
335
|
-
items: [
|
|
336
|
-
{ type: "string" as const },
|
|
337
|
-
{ type: "number" as const },
|
|
338
|
-
{ type: "boolean" as const },
|
|
339
|
-
],
|
|
340
|
-
};
|
|
341
|
-
await schemaTests.expectValid(schema);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
describe("Smart Field Mapping", () => {
|
|
347
|
-
describe("mapping behavior", () => {
|
|
348
|
-
it("maps email fields to appropriate generator", async () => {
|
|
349
|
-
const samples = await generate.samples<any>(
|
|
350
|
-
schemas.simple.object({
|
|
351
|
-
email: schemas.simple.string(),
|
|
352
|
-
userEmail: schemas.simple.string(),
|
|
353
|
-
contact_email: schemas.simple.string(),
|
|
354
|
-
}),
|
|
355
|
-
20,
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
// All email fields should contain @ and .
|
|
359
|
-
samples.forEach((sample) => {
|
|
360
|
-
expect(
|
|
361
|
-
validators.appearsToBeFromCategory([sample.email], "email"),
|
|
362
|
-
).toBe(true);
|
|
363
|
-
expect(
|
|
364
|
-
validators.appearsToBeFromCategory([sample.userEmail], "email"),
|
|
365
|
-
).toBe(true);
|
|
366
|
-
expect(
|
|
367
|
-
validators.appearsToBeFromCategory([sample.contact_email], "email"),
|
|
368
|
-
).toBe(true);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Should have good uniqueness (not all the same)
|
|
372
|
-
const emails = samples.map((s) => s.email);
|
|
373
|
-
expect(validators.uniquenessRatio(emails)).toBeGreaterThan(0.7);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("maps name fields appropriately", async () => {
|
|
377
|
-
const samples = await generate.samples<any>(
|
|
378
|
-
schemas.simple.object({
|
|
379
|
-
firstName: schemas.simple.string(),
|
|
380
|
-
first_name: schemas.simple.string(),
|
|
381
|
-
lastName: schemas.simple.string(),
|
|
382
|
-
last_name: schemas.simple.string(),
|
|
383
|
-
name: schemas.simple.string(),
|
|
384
|
-
fullname: schemas.simple.string(),
|
|
385
|
-
}),
|
|
386
|
-
20,
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
samples.forEach((sample) => {
|
|
390
|
-
// First names should be single words
|
|
391
|
-
expect(sample.firstName).not.toContain(" ");
|
|
392
|
-
expect(sample.first_name).not.toContain(" ");
|
|
393
|
-
|
|
394
|
-
// Last names should be single words
|
|
395
|
-
expect(sample.lastName).not.toContain(" ");
|
|
396
|
-
expect(sample.last_name).not.toContain(" ");
|
|
397
|
-
|
|
398
|
-
// Full names should contain spaces
|
|
399
|
-
expect(sample.name).toContain(" ");
|
|
400
|
-
expect(sample.fullname).toContain(" ");
|
|
401
|
-
|
|
402
|
-
// All should start with capital letters
|
|
403
|
-
expect(
|
|
404
|
-
validators.appearsToBeFromCategory([sample.firstName], "name"),
|
|
405
|
-
).toBe(true);
|
|
406
|
-
expect(
|
|
407
|
-
validators.appearsToBeFromCategory([sample.lastName], "name"),
|
|
408
|
-
).toBe(true);
|
|
409
|
-
expect(
|
|
410
|
-
validators.appearsToBeFromCategory([sample.name], "name"),
|
|
411
|
-
).toBe(true);
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Check uniqueness
|
|
415
|
-
const firstNames = samples.map((s) => s.firstName);
|
|
416
|
-
expect(validators.uniquenessRatio(firstNames)).toBeGreaterThan(0.5);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it("maps date fields to date generators", async () => {
|
|
420
|
-
const samples = await generate.samples<any>(
|
|
421
|
-
schemas.simple.object({
|
|
422
|
-
createdAt: schemas.simple.string(),
|
|
423
|
-
created_at: schemas.simple.string(),
|
|
424
|
-
updatedAt: schemas.simple.string(),
|
|
425
|
-
updated_at: schemas.simple.string(),
|
|
426
|
-
}),
|
|
427
|
-
10,
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
samples.forEach((sample) => {
|
|
431
|
-
expect(
|
|
432
|
-
validators.appearsToBeFromCategory([sample.createdAt], "date"),
|
|
433
|
-
).toBe(true);
|
|
434
|
-
expect(
|
|
435
|
-
validators.appearsToBeFromCategory([sample.created_at], "date"),
|
|
436
|
-
).toBe(true);
|
|
437
|
-
expect(
|
|
438
|
-
validators.appearsToBeFromCategory([sample.updatedAt], "date"),
|
|
439
|
-
).toBe(true);
|
|
440
|
-
expect(
|
|
441
|
-
validators.appearsToBeFromCategory([sample.updated_at], "date"),
|
|
442
|
-
).toBe(true);
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it("maps UUID fields correctly", async () => {
|
|
447
|
-
const samples = await generate.samples<any>(
|
|
448
|
-
schemas.simple.object({
|
|
449
|
-
uuid: schemas.simple.string(),
|
|
450
|
-
id: { type: "string" as const, format: "uuid" as const },
|
|
451
|
-
}),
|
|
452
|
-
10,
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
samples.forEach((sample) => {
|
|
456
|
-
expect(
|
|
457
|
-
validators.appearsToBeFromCategory([sample.uuid], "uuid"),
|
|
458
|
-
).toBe(true);
|
|
459
|
-
expect(validators.appearsToBeFromCategory([sample.id], "uuid")).toBe(
|
|
460
|
-
true,
|
|
461
|
-
);
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
it("does not map unrecognized fields", async () => {
|
|
466
|
-
const samples = await generate.samples<any>(
|
|
467
|
-
schemas.simple.object({
|
|
468
|
-
randomField: schemas.simple.string(),
|
|
469
|
-
customProperty: schemas.simple.string(),
|
|
470
|
-
someValue: schemas.simple.string(),
|
|
471
|
-
}),
|
|
472
|
-
20,
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
// These should be random strings, not following any specific pattern
|
|
476
|
-
const randomFields = samples.map((s) => s.randomField);
|
|
477
|
-
const _customProps = samples.map((s) => s.customProperty);
|
|
478
|
-
|
|
479
|
-
// Should not appear to be from any specific category
|
|
480
|
-
expect(validators.appearsToBeFromCategory(randomFields, "email")).toBe(
|
|
481
|
-
false,
|
|
482
|
-
);
|
|
483
|
-
expect(validators.appearsToBeFromCategory(randomFields, "name")).toBe(
|
|
484
|
-
false,
|
|
485
|
-
);
|
|
486
|
-
expect(validators.appearsToBeFromCategory(randomFields, "uuid")).toBe(
|
|
487
|
-
false,
|
|
488
|
-
);
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it("preserves explicit faker methods over smart mapping", async () => {
|
|
492
|
-
const schema = schemas.simple.object({
|
|
493
|
-
email: schemas.withFaker("string", "person.firstName"),
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
const samples = await generate.samples<any>(schema, 10);
|
|
497
|
-
|
|
498
|
-
// Should NOT be emails since we explicitly set it to firstName
|
|
499
|
-
samples.forEach((sample) => {
|
|
500
|
-
expect(sample.email).not.toContain("@");
|
|
501
|
-
expect(
|
|
502
|
-
validators.appearsToBeFromCategory([sample.email], "email"),
|
|
503
|
-
).toBe(false);
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it("validates faker method namespaces", async () => {
|
|
508
|
-
const schema = schemas.simple.object({
|
|
509
|
-
field: schemas.withFaker("string", "invalidnamespace.method"),
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
await schemaTests.expectInvalid(schema, /Invalid faker method/);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it("handles all common field mapping categories", async () => {
|
|
516
|
-
const schema = schemas.simple.object({
|
|
517
|
-
// Names
|
|
518
|
-
firstName: schemas.simple.string(),
|
|
519
|
-
lastName: schemas.simple.string(),
|
|
520
|
-
|
|
521
|
-
// Contact
|
|
522
|
-
email: schemas.simple.string(),
|
|
523
|
-
phone: schemas.simple.string(),
|
|
524
|
-
mobile: schemas.simple.string(),
|
|
525
|
-
|
|
526
|
-
// Address
|
|
527
|
-
street: schemas.simple.string(),
|
|
528
|
-
city: schemas.simple.string(),
|
|
529
|
-
zipcode: schemas.simple.string(),
|
|
530
|
-
|
|
531
|
-
// Business
|
|
532
|
-
company: schemas.simple.string(),
|
|
533
|
-
position: schemas.simple.string(),
|
|
534
|
-
|
|
535
|
-
// Money
|
|
536
|
-
price: schemas.simple.string(),
|
|
537
|
-
amount: schemas.simple.string(),
|
|
538
|
-
|
|
539
|
-
// Time
|
|
540
|
-
createdAt: schemas.simple.string(),
|
|
541
|
-
updatedAt: schemas.simple.string(),
|
|
542
|
-
|
|
543
|
-
// IDs
|
|
544
|
-
uuid: schemas.simple.string(),
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const result = await generateFromSchema({ schema });
|
|
548
|
-
|
|
549
|
-
// Just verify all fields are generated without checking specific patterns
|
|
550
|
-
Object.keys(schema.properties).forEach((key) => {
|
|
551
|
-
expect(result).toHaveProperty(key);
|
|
552
|
-
expect(typeof result[key]).toBe("string");
|
|
553
|
-
expect(result[key].length).toBeGreaterThan(0);
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
describe("mapping effectiveness", () => {
|
|
559
|
-
it("generates diverse data for mapped fields", async () => {
|
|
560
|
-
const schema = schemas.simple.object({
|
|
561
|
-
email: schemas.simple.string(),
|
|
562
|
-
name: schemas.simple.string(),
|
|
563
|
-
phone: schemas.simple.string(),
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
const samples = await generate.samples<any>(schema, 50);
|
|
567
|
-
|
|
568
|
-
// Check entropy/diversity
|
|
569
|
-
const emails = samples.map((s) => s.email);
|
|
570
|
-
const names = samples.map((s) => s.name);
|
|
571
|
-
const phones = samples.map((s) => s.phone);
|
|
572
|
-
|
|
573
|
-
// Should have high uniqueness for these fields
|
|
574
|
-
expect(validators.uniquenessRatio(emails)).toBeGreaterThan(0.8);
|
|
575
|
-
expect(validators.uniquenessRatio(names)).toBeGreaterThan(0.7);
|
|
576
|
-
expect(validators.uniquenessRatio(phones)).toBeGreaterThan(0.8);
|
|
577
|
-
|
|
578
|
-
// Should have good entropy
|
|
579
|
-
expect(stats.entropy(emails)).toBeGreaterThan(3);
|
|
580
|
-
expect(stats.entropy(names)).toBeGreaterThan(3);
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
it("mapped fields generate different patterns than unmapped fields", async () => {
|
|
584
|
-
// This test verifies that our smart mapping actually does something
|
|
585
|
-
const mappedSamples = (
|
|
586
|
-
await generate.samples<any>(
|
|
587
|
-
schemas.simple.object({
|
|
588
|
-
email: schemas.simple.string(),
|
|
589
|
-
}),
|
|
590
|
-
20,
|
|
591
|
-
)
|
|
592
|
-
).map((s) => s.email);
|
|
593
|
-
|
|
594
|
-
const unmappedSamples = (
|
|
595
|
-
await generate.samples<any>(
|
|
596
|
-
schemas.simple.object({
|
|
597
|
-
randomFieldXYZ123: schemas.simple.string(),
|
|
598
|
-
}),
|
|
599
|
-
20,
|
|
600
|
-
)
|
|
601
|
-
).map((s) => s.randomFieldXYZ123);
|
|
602
|
-
|
|
603
|
-
// Email fields should all have @ sign
|
|
604
|
-
const mappedHasAt = mappedSamples.every((s) => s.includes("@"));
|
|
605
|
-
const unmappedHasAt = unmappedSamples.every((s) => s.includes("@"));
|
|
606
|
-
|
|
607
|
-
expect(mappedHasAt).toBe(true);
|
|
608
|
-
expect(unmappedHasAt).toBe(false);
|
|
609
|
-
});
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
describe("Template Processing", () => {
|
|
614
|
-
describe("basic template substitution", () => {
|
|
615
|
-
it("processes param templates", async () => {
|
|
616
|
-
const schema = schemas.simple.object({
|
|
617
|
-
userId: schemas.simple.string(),
|
|
618
|
-
});
|
|
619
|
-
const result = await generateFromSchema({
|
|
620
|
-
schema,
|
|
621
|
-
overrides: { userId: "{{params.id}}" },
|
|
622
|
-
params: { id: "123" },
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
expect(result.userId).toBe("123"); // Templates return string values
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
it("processes state templates", async () => {
|
|
629
|
-
const schema = schemas.simple.object({
|
|
630
|
-
username: schemas.simple.string(),
|
|
631
|
-
});
|
|
632
|
-
const result = await generateFromSchema({
|
|
633
|
-
schema,
|
|
634
|
-
overrides: { username: "{{state.currentUser}}" },
|
|
635
|
-
state: { currentUser: "alice" },
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
expect(result.username).toBe("alice");
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
it("processes query templates", async () => {
|
|
642
|
-
const schema = schemas.simple.object({
|
|
643
|
-
filter: schemas.simple.string(),
|
|
644
|
-
});
|
|
645
|
-
const result = await generateFromSchema({
|
|
646
|
-
schema,
|
|
647
|
-
overrides: { filter: "{{query.category}}" },
|
|
648
|
-
query: { category: "electronics" },
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
expect(result.filter).toBe("electronics");
|
|
652
|
-
});
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
describe("nested templates", () => {
|
|
656
|
-
it("resolves deeply nested properties", async () => {
|
|
657
|
-
const schema = schemas.simple.object({
|
|
658
|
-
value: schemas.simple.string(),
|
|
659
|
-
});
|
|
660
|
-
const result = await generateFromSchema({
|
|
661
|
-
schema,
|
|
662
|
-
overrides: { value: "{{state.user.profile.settings.theme}}" },
|
|
663
|
-
state: {
|
|
664
|
-
user: {
|
|
665
|
-
profile: {
|
|
666
|
-
settings: {
|
|
667
|
-
theme: "dark",
|
|
668
|
-
},
|
|
669
|
-
},
|
|
670
|
-
},
|
|
671
|
-
},
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
expect(result.value).toBe("dark");
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it("handles missing nested properties gracefully", async () => {
|
|
678
|
-
const schema = schemas.simple.object({
|
|
679
|
-
value: schemas.simple.string(),
|
|
680
|
-
});
|
|
681
|
-
const result = await generateFromSchema({
|
|
682
|
-
schema,
|
|
683
|
-
overrides: { value: "{{state.nonexistent.property}}" },
|
|
684
|
-
state: { other: "value" },
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
expect(result.value).toBe("{{state.nonexistent.property}}");
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
describe("template edge cases", () => {
|
|
692
|
-
it("handles multiple templates in one string", async () => {
|
|
693
|
-
const schema = schemas.simple.object({
|
|
694
|
-
message: schemas.simple.string(),
|
|
695
|
-
});
|
|
696
|
-
const result = await generateFromSchema({
|
|
697
|
-
schema,
|
|
698
|
-
overrides: { message: "User {{params.id}} in {{state.location}}" },
|
|
699
|
-
params: { id: "123" },
|
|
700
|
-
state: { location: "NYC" },
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
expect(result.message).toBe("User 123 in NYC");
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it("preserves non-template content", async () => {
|
|
707
|
-
const schema = schemas.simple.object({ text: schemas.simple.string() });
|
|
708
|
-
const result = await generateFromSchema({
|
|
709
|
-
schema,
|
|
710
|
-
overrides: { text: "Static text with {{params.id}} and more static" },
|
|
711
|
-
params: { id: "456" },
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
expect(result.text).toBe("Static text with 456 and more static");
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
it("handles malformed templates", async () => {
|
|
718
|
-
const schema = schemas.simple.object({
|
|
719
|
-
bad1: schemas.simple.string(),
|
|
720
|
-
bad2: schemas.simple.string(),
|
|
721
|
-
bad3: schemas.simple.string(),
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
const result = await generateFromSchema({
|
|
725
|
-
schema,
|
|
726
|
-
overrides: {
|
|
727
|
-
bad1: "{params.id}", // Missing one brace
|
|
728
|
-
bad2: "{{}}", // Empty template
|
|
729
|
-
bad3: "{{ }}", // Just spaces
|
|
730
|
-
},
|
|
731
|
-
params: { id: "123" },
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
expect(result.bad1).toBe("{params.id}");
|
|
735
|
-
expect(result.bad2).toBe("{{}}");
|
|
736
|
-
expect(result.bad3).toBe("{{ }}");
|
|
737
|
-
});
|
|
738
|
-
|
|
739
|
-
it("converts numeric strings appropriately", async () => {
|
|
740
|
-
const schema = schemas.simple.object({
|
|
741
|
-
intValue: schemas.simple.number(),
|
|
742
|
-
floatValue: schemas.simple.number(),
|
|
743
|
-
stringValue: schemas.simple.string(),
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
const result = await generateFromSchema({
|
|
747
|
-
schema,
|
|
748
|
-
overrides: {
|
|
749
|
-
intValue: "{{params.int}}",
|
|
750
|
-
floatValue: "{{params.float}}",
|
|
751
|
-
stringValue: "{{params.mixed}}",
|
|
752
|
-
},
|
|
753
|
-
params: {
|
|
754
|
-
int: "42",
|
|
755
|
-
float: "3.14",
|
|
756
|
-
mixed: "abc123",
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
expect(result.intValue).toBe("42"); // All template values are strings
|
|
761
|
-
expect(result.floatValue).toBe("3.14");
|
|
762
|
-
expect(result.stringValue).toBe("abc123");
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it("handles null and undefined in templates", async () => {
|
|
766
|
-
const schema = schemas.simple.object({
|
|
767
|
-
nullValue: schemas.simple.string(),
|
|
768
|
-
undefinedValue: schemas.simple.string(),
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
const result = await generateFromSchema({
|
|
772
|
-
schema,
|
|
773
|
-
overrides: {
|
|
774
|
-
nullValue: "{{state.nullVal}}",
|
|
775
|
-
undefinedValue: "{{state.undefinedVal}}",
|
|
776
|
-
},
|
|
777
|
-
state: {
|
|
778
|
-
nullVal: null,
|
|
779
|
-
undefinedVal: undefined,
|
|
780
|
-
},
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
expect(result.nullValue).toBe(null); // null values preserved
|
|
784
|
-
expect(result.undefinedValue).toBe("{{state.undefinedVal}}"); // Template returns original when undefined
|
|
785
|
-
});
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
describe("template in arrays", () => {
|
|
789
|
-
it("applies templates to array items", async () => {
|
|
790
|
-
const schema = schemas.simple.array(
|
|
791
|
-
schemas.simple.object({ userId: schemas.simple.string() }),
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
const result = await generateFromSchema({
|
|
795
|
-
schema,
|
|
796
|
-
count: 3,
|
|
797
|
-
overrides: { userId: "{{params.baseId}}" },
|
|
798
|
-
params: { baseId: "user_" },
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
expect(Array.isArray(result)).toBe(true);
|
|
802
|
-
result.forEach((item) => {
|
|
803
|
-
expect(item.userId).toBe("user_");
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
});
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
describe("Performance", () => {
|
|
810
|
-
it("generates simple schemas quickly", async () => {
|
|
811
|
-
const schema = schemas.simple.object({
|
|
812
|
-
id: schemas.simple.number(),
|
|
813
|
-
name: schemas.simple.string(),
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
const { duration } = await perf.measure(() =>
|
|
817
|
-
generateFromSchema({ schema }),
|
|
818
|
-
);
|
|
819
|
-
|
|
820
|
-
expect(duration).toBeLessThan(100); // Should be fast (but reasonable for CI)
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
it("handles large arrays efficiently", async () => {
|
|
824
|
-
const schema = schemas.simple.array(
|
|
825
|
-
schemas.simple.object({ id: schemas.simple.number() }),
|
|
826
|
-
{ maxItems: 100 },
|
|
827
|
-
);
|
|
828
|
-
|
|
829
|
-
const { duration } = await perf.measure(() =>
|
|
830
|
-
generateFromSchema({ schema }),
|
|
831
|
-
);
|
|
832
|
-
|
|
833
|
-
expect(duration).toBeLessThan(500); // Reasonable time for 100 items
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
it("benchmarks show consistent performance", async () => {
|
|
837
|
-
const schema = schemas.complex.user();
|
|
838
|
-
|
|
839
|
-
const benchmark = await perf.benchmark(
|
|
840
|
-
"user generation",
|
|
841
|
-
() => generateFromSchema({ schema }),
|
|
842
|
-
50,
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
expect(benchmark.mean).toBeLessThan(50); // Reasonable for CI
|
|
846
|
-
// Just check that performance is reasonable, not strict ratios for small values
|
|
847
|
-
expect(benchmark.max).toBeLessThan(100); // No huge outliers
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
it("deep nesting doesn't cause exponential slowdown", async () => {
|
|
851
|
-
const shallow = schemas.nested.deep(2);
|
|
852
|
-
const deep = schemas.nested.deep(5);
|
|
853
|
-
|
|
854
|
-
await perf.measure(() => generateFromSchema({ schema: shallow }));
|
|
855
|
-
|
|
856
|
-
const { duration: deepTime } = await perf.measure(() =>
|
|
857
|
-
generateFromSchema({ schema: deep }),
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
// Just ensure it completes in reasonable time
|
|
861
|
-
expect(deepTime).toBeLessThan(100); // Should complete quickly
|
|
862
|
-
// The times might be too small to compare ratios reliably
|
|
863
|
-
});
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
describe("Schema Plugin", () => {
|
|
867
|
-
it("creates plugin with correct interface", () => {
|
|
868
|
-
const plugin = fakerPlugin({
|
|
869
|
-
schema: schemas.simple.object({ id: schemas.simple.number() }),
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
expect(plugin).toHaveProperty("name", "faker");
|
|
873
|
-
expect(plugin).toHaveProperty("version", packageVersion);
|
|
874
|
-
expect(plugin).toHaveProperty("process");
|
|
875
|
-
expect(typeof plugin.process).toBe("function");
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
it("validates schema at plugin creation time", () => {
|
|
879
|
-
expect(() => {
|
|
880
|
-
fakerPlugin({ schema: {} as any });
|
|
881
|
-
}).toThrow("Schema cannot be empty");
|
|
882
|
-
|
|
883
|
-
expect(() => {
|
|
884
|
-
fakerPlugin({ schema: { type: "invalid" as any } });
|
|
885
|
-
}).toThrow("Invalid schema type");
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
it("generates data when processing context", async () => {
|
|
889
|
-
const plugin = fakerPlugin({
|
|
890
|
-
schema: schemas.simple.object({
|
|
891
|
-
id: schemas.simple.number(),
|
|
892
|
-
name: schemas.simple.string(),
|
|
893
|
-
}),
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
const mockContext = {
|
|
897
|
-
method: "GET",
|
|
898
|
-
path: "/test",
|
|
899
|
-
params: {},
|
|
900
|
-
query: {},
|
|
901
|
-
state: {},
|
|
902
|
-
headers: {},
|
|
903
|
-
body: null,
|
|
904
|
-
route: {},
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
const result = await plugin.process(mockContext);
|
|
908
|
-
|
|
909
|
-
expect(result.response).toHaveProperty("id");
|
|
910
|
-
expect(result.response).toHaveProperty("name");
|
|
911
|
-
expect(typeof result.response.id).toBe("number");
|
|
912
|
-
expect(typeof result.response.name).toBe("string");
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
it("passes through existing responses", async () => {
|
|
916
|
-
const plugin = fakerPlugin({
|
|
917
|
-
schema: schemas.simple.object({ id: schemas.simple.number() }),
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
const mockContext = {
|
|
921
|
-
method: "GET",
|
|
922
|
-
path: "/test",
|
|
923
|
-
params: {},
|
|
924
|
-
query: {},
|
|
925
|
-
state: {},
|
|
926
|
-
headers: {},
|
|
927
|
-
body: null,
|
|
928
|
-
route: {},
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
const existingResponse = { custom: "response", data: [1, 2, 3] };
|
|
932
|
-
const result = await plugin.process(mockContext, existingResponse);
|
|
933
|
-
|
|
934
|
-
expect(result.response).toEqual(existingResponse);
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
it("applies overrides with template processing", async () => {
|
|
938
|
-
const plugin = fakerPlugin({
|
|
939
|
-
schema: schemas.simple.object({
|
|
940
|
-
userId: schemas.simple.string(),
|
|
941
|
-
timestamp: schemas.simple.string(),
|
|
942
|
-
}),
|
|
943
|
-
overrides: {
|
|
944
|
-
userId: "{{params.id}}",
|
|
945
|
-
timestamp: "{{state.currentTime}}",
|
|
946
|
-
},
|
|
947
|
-
});
|
|
948
|
-
|
|
949
|
-
const mockContext = {
|
|
950
|
-
method: "GET",
|
|
951
|
-
path: "/test/123",
|
|
952
|
-
params: { id: "123" },
|
|
953
|
-
query: {},
|
|
954
|
-
state: new Map(),
|
|
955
|
-
routeState: { currentTime: "2024-01-01T00:00:00Z" },
|
|
956
|
-
headers: {},
|
|
957
|
-
body: null,
|
|
958
|
-
route: {},
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
const result = await plugin.process(mockContext);
|
|
962
|
-
|
|
963
|
-
expect(result.response.userId).toBe("123"); // Template values are strings
|
|
964
|
-
expect(result.response.timestamp).toBe("2024-01-01T00:00:00Z");
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
it("handles errors gracefully", async () => {
|
|
968
|
-
const plugin = fakerPlugin({
|
|
969
|
-
schema: schemas.simple.object({ id: schemas.simple.number() }),
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
// Create a context that might cause issues
|
|
973
|
-
const badContext = {
|
|
974
|
-
method: "GET",
|
|
975
|
-
path: "/test",
|
|
976
|
-
params: null as any, // This might cause template processing to fail
|
|
977
|
-
query: {},
|
|
978
|
-
state: {},
|
|
979
|
-
headers: {},
|
|
980
|
-
body: null,
|
|
981
|
-
route: {},
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
// Should not throw, should handle gracefully
|
|
985
|
-
await expect(plugin.process(badContext)).resolves.not.toThrow();
|
|
986
|
-
});
|
|
987
|
-
});
|
|
988
|
-
|
|
989
|
-
describe("Error Handling", () => {
|
|
990
|
-
it("provides clear error messages for validation failures", async () => {
|
|
991
|
-
try {
|
|
992
|
-
await generateFromSchema({
|
|
993
|
-
schema: {
|
|
994
|
-
type: "object",
|
|
995
|
-
properties: {
|
|
996
|
-
nested: {
|
|
997
|
-
type: "array",
|
|
998
|
-
items: {
|
|
999
|
-
type: "object",
|
|
1000
|
-
properties: {
|
|
1001
|
-
field: {
|
|
1002
|
-
type: "string",
|
|
1003
|
-
faker: "invalid.namespace.method",
|
|
1004
|
-
},
|
|
1005
|
-
},
|
|
1006
|
-
},
|
|
1007
|
-
},
|
|
1008
|
-
},
|
|
1009
|
-
},
|
|
1010
|
-
});
|
|
1011
|
-
expect.fail("Should have thrown");
|
|
1012
|
-
} catch (error: any) {
|
|
1013
|
-
expect(error.message).toContain("Invalid faker method");
|
|
1014
|
-
expect(error.message).toContain("invalid.namespace.method");
|
|
1015
|
-
}
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
it("wraps generation errors appropriately", async () => {
|
|
1019
|
-
const schema = schemas.simple.object({
|
|
1020
|
-
field: { type: "string" as const, pattern: "[" } as any, // Invalid regex
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
// json-schema-faker 0.6.0 may not throw on invalid regex patterns,
|
|
1024
|
-
// but the result should still be handled gracefully
|
|
1025
|
-
const result = await generateFromSchema({ schema });
|
|
1026
|
-
expect(result).toBeDefined();
|
|
1027
|
-
});
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
describe("Integration", () => {
|
|
1031
|
-
it("works with real-world schemas", async () => {
|
|
1032
|
-
const openAPISchema: JSONSchema7 = {
|
|
1033
|
-
type: "object",
|
|
1034
|
-
properties: {
|
|
1035
|
-
id: { type: "string", format: "uuid" },
|
|
1036
|
-
email: { type: "string", format: "email" },
|
|
1037
|
-
profile: {
|
|
1038
|
-
type: "object",
|
|
1039
|
-
properties: {
|
|
1040
|
-
firstName: { type: "string" },
|
|
1041
|
-
lastName: { type: "string" },
|
|
1042
|
-
age: { type: "integer", minimum: 0, maximum: 120 },
|
|
1043
|
-
interests: {
|
|
1044
|
-
type: "array",
|
|
1045
|
-
items: { type: "string" },
|
|
1046
|
-
maxItems: 10,
|
|
1047
|
-
},
|
|
1048
|
-
},
|
|
1049
|
-
required: ["firstName", "lastName"],
|
|
1050
|
-
},
|
|
1051
|
-
createdAt: { type: "string", format: "date-time" },
|
|
1052
|
-
isActive: { type: "boolean" },
|
|
1053
|
-
},
|
|
1054
|
-
required: ["id", "email", "profile"],
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
const result = await generateFromSchema({ schema: openAPISchema });
|
|
1058
|
-
|
|
1059
|
-
// Verify structure
|
|
1060
|
-
expect(result).toHaveProperty("id");
|
|
1061
|
-
expect(result).toHaveProperty("email");
|
|
1062
|
-
expect(result).toHaveProperty("profile");
|
|
1063
|
-
expect(result.profile).toHaveProperty("firstName");
|
|
1064
|
-
expect(result.profile).toHaveProperty("lastName");
|
|
1065
|
-
|
|
1066
|
-
// Verify formats
|
|
1067
|
-
expect(validators.appearsToBeFromCategory([result.id], "uuid")).toBe(
|
|
1068
|
-
true,
|
|
1069
|
-
);
|
|
1070
|
-
expect(validators.appearsToBeFromCategory([result.email], "email")).toBe(
|
|
1071
|
-
true,
|
|
1072
|
-
);
|
|
1073
|
-
expect(
|
|
1074
|
-
validators.appearsToBeFromCategory([result.createdAt], "date"),
|
|
1075
|
-
).toBe(true);
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
it("integrates with plugin pipeline", async () => {
|
|
1079
|
-
const plugin = fakerPlugin({
|
|
1080
|
-
schema: schemas.complex.apiResponse(),
|
|
1081
|
-
count: 5,
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
// Simulate plugin pipeline
|
|
1085
|
-
const context = {
|
|
1086
|
-
method: "GET",
|
|
1087
|
-
path: "/api/users",
|
|
1088
|
-
params: {},
|
|
1089
|
-
query: { page: "1" },
|
|
1090
|
-
state: {},
|
|
1091
|
-
headers: {},
|
|
1092
|
-
body: null,
|
|
1093
|
-
route: {},
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
const result1 = await plugin.process(context);
|
|
1097
|
-
const result2 = await plugin.process(context);
|
|
1098
|
-
|
|
1099
|
-
// Should generate different data each time
|
|
1100
|
-
expect(result1.response).not.toEqual(result2.response);
|
|
1101
|
-
expect(Array.isArray(result1.response.data)).toBe(true);
|
|
1102
|
-
expect(Array.isArray(result2.response.data)).toBe(true);
|
|
1103
|
-
// Count was specified at plugin level, not in the schema response
|
|
1104
|
-
});
|
|
1105
|
-
});
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
describe("Additional Coverage Tests", () => {
|
|
1109
|
-
describe("Schema Enhancement", () => {
|
|
1110
|
-
it("enhances simple fields without existing faker methods", async () => {
|
|
1111
|
-
const schema = schemas.simple.object({
|
|
1112
|
-
email: schemas.simple.string(),
|
|
1113
|
-
phone: schemas.simple.string(),
|
|
1114
|
-
uuid: schemas.simple.string(),
|
|
1115
|
-
});
|
|
1116
|
-
|
|
1117
|
-
const samples = await generate.samples<any>(schema, 5);
|
|
1118
|
-
|
|
1119
|
-
samples.forEach((sample) => {
|
|
1120
|
-
expect(
|
|
1121
|
-
validators.appearsToBeFromCategory([sample.email], "email"),
|
|
1122
|
-
).toBe(true);
|
|
1123
|
-
expect(
|
|
1124
|
-
validators.appearsToBeFromCategory([sample.phone], "phone"),
|
|
1125
|
-
).toBe(true);
|
|
1126
|
-
expect(validators.appearsToBeFromCategory([sample.uuid], "uuid")).toBe(
|
|
1127
|
-
true,
|
|
1128
|
-
);
|
|
1129
|
-
});
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
it("preserves explicit faker methods over enhancements", async () => {
|
|
1133
|
-
const schema = {
|
|
1134
|
-
type: "object" as const,
|
|
1135
|
-
properties: {
|
|
1136
|
-
email: {
|
|
1137
|
-
type: "string" as const,
|
|
1138
|
-
faker: "lorem.word" as any,
|
|
1139
|
-
},
|
|
1140
|
-
},
|
|
1141
|
-
};
|
|
1142
|
-
|
|
1143
|
-
const result = await generateFromSchema({ schema });
|
|
1144
|
-
|
|
1145
|
-
// Should use lorem.word, not email pattern
|
|
1146
|
-
expect(result.email).not.toContain("@");
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
it("handles array items with smart field mapping", async () => {
|
|
1150
|
-
const schema = schemas.simple.array(
|
|
1151
|
-
schemas.simple.object({
|
|
1152
|
-
email: schemas.simple.string(),
|
|
1153
|
-
createdAt: schemas.simple.string(),
|
|
1154
|
-
}),
|
|
1155
|
-
);
|
|
1156
|
-
|
|
1157
|
-
const result = await generateFromSchema({ schema, count: 3 });
|
|
1158
|
-
|
|
1159
|
-
result.forEach((item) => {
|
|
1160
|
-
expect(validators.appearsToBeFromCategory([item.email], "email")).toBe(
|
|
1161
|
-
true,
|
|
1162
|
-
);
|
|
1163
|
-
expect(
|
|
1164
|
-
validators.appearsToBeFromCategory([item.createdAt], "date"),
|
|
1165
|
-
).toBe(true);
|
|
1166
|
-
});
|
|
1167
|
-
});
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
describe("Edge Cases", () => {
|
|
1171
|
-
it("handles empty string patterns", async () => {
|
|
1172
|
-
const schema = schemas.simple.object({
|
|
1173
|
-
value: { type: "string" as const, pattern: "" as const },
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
const result = await generateFromSchema({ schema });
|
|
1177
|
-
expect(typeof result.value).toBe("string");
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
it("handles whitespace in templates", async () => {
|
|
1181
|
-
const schema = schemas.simple.object({ value: schemas.simple.string() });
|
|
1182
|
-
|
|
1183
|
-
const result = await generateFromSchema({
|
|
1184
|
-
schema,
|
|
1185
|
-
overrides: { value: " {{ params.id }} " },
|
|
1186
|
-
params: { id: "test" },
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
expect(result.value).toBe(" test "); // Preserves outer whitespace
|
|
1190
|
-
});
|
|
1191
|
-
|
|
1192
|
-
it("handles boolean type with schema", async () => {
|
|
1193
|
-
const schema = schemas.simple.object({
|
|
1194
|
-
flag: { type: "boolean" as const },
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
const samples = await generate.samples<any>(schema, 20);
|
|
1198
|
-
|
|
1199
|
-
// All samples should have boolean flags
|
|
1200
|
-
samples.forEach((sample) => {
|
|
1201
|
-
expect(typeof sample.flag).toBe("boolean");
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
// At least some variation (or all same value is acceptable for JSF)
|
|
1205
|
-
const trueCount = samples.filter((s) => s.flag === true).length;
|
|
1206
|
-
const falseCount = samples.filter((s) => s.flag === false).length;
|
|
1207
|
-
expect(trueCount + falseCount).toBe(20);
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
it("handles integer vs number types", async () => {
|
|
1211
|
-
const schema = schemas.simple.object({
|
|
1212
|
-
intValue: { type: "integer" as const },
|
|
1213
|
-
numValue: { type: "number" as const },
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
const samples = await generate.samples<any>(schema, 10);
|
|
1217
|
-
|
|
1218
|
-
samples.forEach((sample) => {
|
|
1219
|
-
expect(Number.isInteger(sample.intValue)).toBe(true);
|
|
1220
|
-
expect(typeof sample.numValue).toBe("number");
|
|
1221
|
-
});
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
it("handles null type", async () => {
|
|
1225
|
-
const schema = schemas.simple.object({
|
|
1226
|
-
nullValue: { type: "null" as const },
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
const result = await generateFromSchema({ schema });
|
|
1230
|
-
expect(result.nullValue).toBe(null);
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
it("handles format without explicit type", async () => {
|
|
1234
|
-
const schema = {
|
|
1235
|
-
type: "object" as const,
|
|
1236
|
-
properties: {
|
|
1237
|
-
email: { format: "email" as const },
|
|
1238
|
-
},
|
|
1239
|
-
};
|
|
1240
|
-
|
|
1241
|
-
const result = await generateFromSchema({ schema });
|
|
1242
|
-
expect(result.email).toContain("@");
|
|
1243
|
-
});
|
|
1244
|
-
|
|
1245
|
-
it("generates consistent results with same schema instance", async () => {
|
|
1246
|
-
const schema = schemas.complex.user();
|
|
1247
|
-
|
|
1248
|
-
const results: any[] = [];
|
|
1249
|
-
for (let i = 0; i < 5; i++) {
|
|
1250
|
-
results.push(await generateFromSchema({ schema }));
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// All should be valid but different
|
|
1254
|
-
results.forEach((result) => {
|
|
1255
|
-
expect(validators.appearsToBeFromCategory([result.id], "uuid")).toBe(
|
|
1256
|
-
true,
|
|
1257
|
-
);
|
|
1258
|
-
expect(
|
|
1259
|
-
validators.appearsToBeFromCategory([result.email], "email"),
|
|
1260
|
-
).toBe(true);
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
// Should be different instances
|
|
1264
|
-
const emails = results.map((r) => r.email);
|
|
1265
|
-
const uniqueEmails = new Set(emails);
|
|
1266
|
-
expect(uniqueEmails.size).toBeGreaterThan(1);
|
|
1267
|
-
});
|
|
1268
|
-
|
|
1269
|
-
it("handles minProperties and maxProperties constraints", async () => {
|
|
1270
|
-
const schema = {
|
|
1271
|
-
type: "object" as const,
|
|
1272
|
-
properties: {
|
|
1273
|
-
prop1: { type: "string" as const },
|
|
1274
|
-
prop2: { type: "string" as const },
|
|
1275
|
-
},
|
|
1276
|
-
minProperties: 2,
|
|
1277
|
-
maxProperties: 4,
|
|
1278
|
-
additionalProperties: { type: "string" as const },
|
|
1279
|
-
};
|
|
1280
|
-
|
|
1281
|
-
const samples = await generate.samples<any>(schema, 10);
|
|
1282
|
-
|
|
1283
|
-
samples.forEach((sample) => {
|
|
1284
|
-
const propCount = Object.keys(sample).length;
|
|
1285
|
-
// Should have at least the defined properties
|
|
1286
|
-
expect(propCount).toBeGreaterThanOrEqual(2);
|
|
1287
|
-
// May not respect maxProperties perfectly but should be reasonable
|
|
1288
|
-
expect(propCount).toBeLessThan(20); // Sanity check
|
|
1289
|
-
});
|
|
1290
|
-
});
|
|
1291
|
-
|
|
1292
|
-
it("handles required fields correctly", async () => {
|
|
1293
|
-
const schema = {
|
|
1294
|
-
type: "object" as const,
|
|
1295
|
-
properties: {
|
|
1296
|
-
required1: { type: "string" as const },
|
|
1297
|
-
required2: { type: "number" as const },
|
|
1298
|
-
optional1: { type: "boolean" as const },
|
|
1299
|
-
optional2: { type: "string" as const },
|
|
1300
|
-
},
|
|
1301
|
-
required: ["required1", "required2"],
|
|
1302
|
-
};
|
|
1303
|
-
|
|
1304
|
-
const samples = await generate.samples<any>(schema, 10);
|
|
1305
|
-
|
|
1306
|
-
samples.forEach((sample) => {
|
|
1307
|
-
// Required fields should always be present
|
|
1308
|
-
expect(sample).toHaveProperty("required1");
|
|
1309
|
-
expect(sample).toHaveProperty("required2");
|
|
1310
|
-
expect(typeof sample.required1).toBe("string");
|
|
1311
|
-
expect(typeof sample.required2).toBe("number");
|
|
1312
|
-
|
|
1313
|
-
// Optional fields may or may not be present
|
|
1314
|
-
if ("optional1" in sample) {
|
|
1315
|
-
expect(typeof sample.optional1).toBe("boolean");
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
});
|
|
1319
|
-
});
|
|
1320
|
-
});
|