@schmock/faker 1.4.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.
@@ -0,0 +1,483 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ import { describe, expect, it } from "vitest";
3
+ import { fakerPlugin, generateFromSchema } from "./index";
4
+ import { generate, performance as perf, schemas } from "./test-utils";
5
+
6
+ describe("Performance and Memory", () => {
7
+ describe("Generation Speed", () => {
8
+ it("generates simple objects quickly", async () => {
9
+ const schema = schemas.simple.object({
10
+ id: schemas.simple.number(),
11
+ name: schemas.simple.string(),
12
+ active: { type: "boolean" },
13
+ });
14
+
15
+ const times: number[] = [];
16
+ for (let i = 0; i < 10; i++) {
17
+ const { duration } = await perf.measure(() =>
18
+ generateFromSchema({ schema }),
19
+ );
20
+ times.push(duration);
21
+ }
22
+
23
+ const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
24
+ expect(avgTime).toBeLessThan(50); // Should average under 50ms
25
+ });
26
+
27
+ it("handles nested objects efficiently", async () => {
28
+ const schema = schemas.nested.deep(
29
+ 3,
30
+ schemas.simple.object({
31
+ id: schemas.simple.number(),
32
+ value: schemas.simple.string(),
33
+ }),
34
+ );
35
+
36
+ const { duration } = await perf.measure(() =>
37
+ generateFromSchema({ schema }),
38
+ );
39
+
40
+ expect(duration).toBeLessThan(100); // Reasonable for nested structure
41
+ });
42
+
43
+ it("generates arrays efficiently", async () => {
44
+ const schema = schemas.simple.array(
45
+ schemas.simple.object({
46
+ id: schemas.simple.number(),
47
+ name: schemas.simple.string(),
48
+ }),
49
+ { minItems: 50, maxItems: 50 },
50
+ );
51
+
52
+ const { duration } = await perf.measure(() =>
53
+ generateFromSchema({ schema }),
54
+ );
55
+
56
+ expect(duration).toBeLessThan(200); // Should handle 50 items quickly
57
+ });
58
+
59
+ it("handles complex schemas with multiple constraints", async () => {
60
+ const schema: JSONSchema7 = {
61
+ type: "object",
62
+ properties: {
63
+ users: {
64
+ type: "array",
65
+ items: {
66
+ type: "object",
67
+ properties: {
68
+ id: { type: "string", format: "uuid" },
69
+ email: { type: "string", format: "email" },
70
+ age: { type: "integer", minimum: 18, maximum: 100 },
71
+ tags: {
72
+ type: "array",
73
+ items: { type: "string", pattern: "^[a-z]+$" },
74
+ maxItems: 5,
75
+ },
76
+ },
77
+ required: ["id", "email"],
78
+ },
79
+ minItems: 10,
80
+ maxItems: 10,
81
+ },
82
+ },
83
+ };
84
+
85
+ const { duration } = await perf.measure(() =>
86
+ generateFromSchema({ schema }),
87
+ );
88
+
89
+ expect(duration).toBeLessThan(300); // Complex but still reasonable
90
+ });
91
+ });
92
+
93
+ describe("Scaling Behavior", () => {
94
+ it("scales linearly with array size", async () => {
95
+ const smallSchema = schemas.simple.array(schemas.simple.string(), {
96
+ minItems: 50,
97
+ maxItems: 50,
98
+ });
99
+
100
+ const largeSchema = schemas.simple.array(schemas.simple.string(), {
101
+ minItems: 500,
102
+ maxItems: 500,
103
+ });
104
+
105
+ // Warmup runs to stabilize JIT
106
+ for (let i = 0; i < 3; i++) {
107
+ generateFromSchema({ schema: smallSchema });
108
+ generateFromSchema({ schema: largeSchema });
109
+ }
110
+
111
+ // Multiple measurement runs for statistical stability
112
+ const smallTimes: number[] = [];
113
+ const largeTimes: number[] = [];
114
+
115
+ for (let i = 0; i < 10; i++) {
116
+ const start1 = performance.now();
117
+ generateFromSchema({ schema: smallSchema });
118
+ const small = performance.now() - start1;
119
+ smallTimes.push(small);
120
+
121
+ const start2 = performance.now();
122
+ generateFromSchema({ schema: largeSchema });
123
+ const large = performance.now() - start2;
124
+ largeTimes.push(large);
125
+ }
126
+
127
+ // Remove outliers and calculate medians for stability
128
+ smallTimes.sort((a, b) => a - b);
129
+ largeTimes.sort((a, b) => a - b);
130
+ const medianSmall = smallTimes[Math.floor(smallTimes.length / 2)];
131
+ const medianLarge = largeTimes[Math.floor(largeTimes.length / 2)];
132
+
133
+ // Both should complete in reasonable time (main goal is to ensure it works, not strict timing)
134
+ expect(medianSmall).toBeLessThan(50); // Small arrays should be fast
135
+ expect(medianLarge).toBeLessThan(200); // Large arrays should still be reasonable
136
+
137
+ // Optional: Check scaling if timing is meaningful
138
+ if (medianSmall > 0.1) {
139
+ // Only check scaling if we have measurable timing
140
+ expect(medianLarge).toBeGreaterThan(medianSmall * 0.5); // Should take at least half as long
141
+ expect(medianLarge).toBeLessThan(medianSmall * 50); // But not extremely longer
142
+ }
143
+ });
144
+
145
+ it("handles wide objects efficiently", async () => {
146
+ const narrowSchema = schemas.nested.wide(20);
147
+ const wideSchema = schemas.nested.wide(100);
148
+
149
+ // Warmup
150
+ for (let i = 0; i < 3; i++) {
151
+ generateFromSchema({ schema: narrowSchema });
152
+ generateFromSchema({ schema: wideSchema });
153
+ }
154
+
155
+ // Measure with multiple runs, dropping outliers
156
+ const narrowTimes: number[] = [];
157
+ const wideTimes: number[] = [];
158
+
159
+ for (let i = 0; i < 10; i++) {
160
+ const start1 = performance.now();
161
+ generateFromSchema({ schema: narrowSchema });
162
+ narrowTimes.push(performance.now() - start1);
163
+
164
+ const start2 = performance.now();
165
+ generateFromSchema({ schema: wideSchema });
166
+ wideTimes.push(performance.now() - start2);
167
+ }
168
+
169
+ // Use median instead of average to reduce sensitivity to GC pauses
170
+ const median = (arr: number[]) => {
171
+ const sorted = [...arr].sort((a, b) => a - b);
172
+ const mid = Math.floor(sorted.length / 2);
173
+ return sorted.length % 2
174
+ ? sorted[mid]
175
+ : (sorted[mid - 1] + sorted[mid]) / 2;
176
+ };
177
+
178
+ const medWide = median(wideTimes);
179
+
180
+ // 100-property object should still generate quickly (under 50ms)
181
+ expect(medWide).toBeLessThan(50);
182
+ });
183
+ });
184
+
185
+ describe("Plugin Performance", () => {
186
+ it("plugin creation is fast", async () => {
187
+ const schema = schemas.complex.user();
188
+
189
+ const { duration } = await perf.measure(() => fakerPlugin({ schema }));
190
+
191
+ expect(duration).toBeLessThan(10); // Plugin creation should be instant
192
+ });
193
+
194
+ it("plugin processing adds minimal overhead", async () => {
195
+ const schema = schemas.complex.apiResponse();
196
+ const plugin = fakerPlugin({ schema });
197
+
198
+ const context = {
199
+ method: "GET",
200
+ path: "/test",
201
+ params: {},
202
+ query: {},
203
+ state: {},
204
+ headers: {},
205
+ body: null,
206
+ route: {},
207
+ };
208
+
209
+ const times: number[] = [];
210
+ for (let i = 0; i < 10; i++) {
211
+ const { duration } = await perf.measure(() => plugin.process(context));
212
+ times.push(duration);
213
+ }
214
+
215
+ const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
216
+ expect(avgTime).toBeLessThan(100);
217
+ });
218
+
219
+ it("template processing is efficient", async () => {
220
+ const schema = schemas.simple.object({
221
+ id: schemas.simple.string(),
222
+ userId: schemas.simple.string(),
223
+ timestamp: schemas.simple.string(),
224
+ message: schemas.simple.string(),
225
+ });
226
+
227
+ const overrides = {
228
+ id: "{{params.id}}",
229
+ userId: "{{state.user.id}}",
230
+ timestamp: "{{state.timestamp}}",
231
+ message: "User {{params.id}} at {{state.timestamp}}",
232
+ };
233
+
234
+ const { duration } = await perf.measure(() =>
235
+ generateFromSchema({
236
+ schema,
237
+ overrides,
238
+ params: { id: "123" },
239
+ state: {
240
+ user: { id: "user-456" },
241
+ timestamp: new Date().toISOString(),
242
+ },
243
+ }),
244
+ );
245
+
246
+ expect(duration).toBeLessThan(50); // Template processing should be fast
247
+ });
248
+ });
249
+
250
+ describe("Concurrent Generation", () => {
251
+ it("handles multiple concurrent generations", async () => {
252
+ const schema = schemas.complex.user();
253
+
254
+ const { duration } = await perf.measure(async () => {
255
+ const promises = Array.from({ length: 20 }, () =>
256
+ generateFromSchema({ schema }),
257
+ );
258
+ await Promise.all(promises);
259
+ });
260
+
261
+ expect(duration).toBeLessThan(500); // Should handle concurrency well
262
+ });
263
+
264
+ it("maintains performance under load", async () => {
265
+ const schema = schemas.simple.object({
266
+ id: schemas.simple.number(),
267
+ data: schemas.simple.string(),
268
+ });
269
+
270
+ // Warm up
271
+ generateFromSchema({ schema });
272
+
273
+ // Test under load
274
+ const iterations = 100;
275
+ const { duration } = await perf.measure(async () => {
276
+ for (let i = 0; i < iterations; i++) {
277
+ generateFromSchema({ schema });
278
+ }
279
+ });
280
+
281
+ const avgTime = duration / iterations;
282
+ expect(avgTime).toBeLessThan(10); // Should maintain speed
283
+ });
284
+ });
285
+
286
+ describe("Memory Efficiency", () => {
287
+ it("doesn't leak memory on repeated generation", () => {
288
+ const schema = schemas.simple.object({
289
+ id: schemas.simple.number(),
290
+ name: schemas.simple.string(),
291
+ });
292
+
293
+ // Generate many times
294
+ for (let i = 0; i < 1000; i++) {
295
+ const result = generateFromSchema({ schema });
296
+ // Result should be garbage collectable
297
+ expect(result).toBeDefined();
298
+ }
299
+
300
+ // If we got here without crashing, memory is managed well
301
+ expect(true).toBe(true);
302
+ });
303
+
304
+ it("handles large data structures without excessive memory", () => {
305
+ const schema = schemas.simple.array(
306
+ schemas.simple.object({
307
+ id: schemas.simple.string(),
308
+ data: schemas.simple.string(),
309
+ }),
310
+ { minItems: 1000, maxItems: 1000 },
311
+ );
312
+
313
+ // Should be able to generate without memory issues
314
+ const result = generateFromSchema({ schema });
315
+ expect(result).toHaveLength(1000);
316
+ });
317
+
318
+ it("cleans up after schema validation errors", () => {
319
+ // Generate errors repeatedly
320
+ for (let i = 0; i < 100; i++) {
321
+ try {
322
+ generateFromSchema({ schema: { type: "invalid" as any } });
323
+ } catch (_e) {
324
+ // Expected
325
+ }
326
+ }
327
+
328
+ // Should not have memory leaks from error objects
329
+ expect(true).toBe(true);
330
+ });
331
+ });
332
+
333
+ describe("Optimization Opportunities", () => {
334
+ it("generates data consistently across multiple calls", async () => {
335
+ const schema = schemas.simple.object({
336
+ email: schemas.simple.string(),
337
+ firstName: schemas.simple.string(),
338
+ phone: schemas.simple.string(),
339
+ });
340
+
341
+ // Generate multiple times to ensure consistent behavior
342
+ const results: any[] = [];
343
+ for (let i = 0; i < 10; i++) {
344
+ const result = generateFromSchema({ schema });
345
+ results.push(result);
346
+ }
347
+
348
+ // Verify all results have the expected structure
349
+ results.forEach((result) => {
350
+ expect(result).toHaveProperty("email");
351
+ expect(result).toHaveProperty("firstName");
352
+ expect(result).toHaveProperty("phone");
353
+ expect(typeof result.email).toBe("string");
354
+ expect(typeof result.firstName).toBe("string");
355
+ expect(typeof result.phone).toBe("string");
356
+ });
357
+
358
+ // Verify that smart field mapping worked across all calls
359
+ const emails = results.map((r) => r.email);
360
+ const firstNames = results.map((r) => r.firstName);
361
+
362
+ // Should have good diversity (no exact duplicates expected)
363
+ const uniqueEmails = new Set(emails);
364
+ const uniqueFirstNames = new Set(firstNames);
365
+ expect(uniqueEmails.size).toBeGreaterThan(1);
366
+ expect(uniqueFirstNames.size).toBeGreaterThan(1);
367
+ });
368
+
369
+ it("reuses schema enhancement for repeated generations", async () => {
370
+ const schema = schemas.complex.user();
371
+ const plugin = fakerPlugin({ schema });
372
+
373
+ const context = {
374
+ method: "GET",
375
+ path: "/test",
376
+ params: {},
377
+ query: {},
378
+ state: {},
379
+ headers: {},
380
+ body: null,
381
+ route: {},
382
+ };
383
+
384
+ // Warmup
385
+ for (let i = 0; i < 3; i++) {
386
+ plugin.process(context);
387
+ }
388
+
389
+ // Multiple calls through same plugin with proper timing
390
+ const times: number[] = [];
391
+ for (let i = 0; i < 15; i++) {
392
+ const start = performance.now();
393
+ plugin.process(context);
394
+ times.push(performance.now() - start);
395
+ }
396
+
397
+ // Calculate statistics
398
+ const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
399
+ const maxTime = Math.max(...times);
400
+ const minTime = Math.min(...times);
401
+
402
+ // Performance should be consistent and reasonable
403
+ expect(avgTime).toBeLessThan(50); // Should be reasonably fast
404
+ expect(maxTime).toBeLessThan(100); // No extreme outliers
405
+ expect(minTime).toBeGreaterThanOrEqual(0); // Valid timing
406
+
407
+ // All measurements should be reasonable - no extreme variance
408
+ const reasonableMaxTime = Math.max(avgTime * 5, 10); // At least 10ms tolerance
409
+ expect(maxTime).toBeLessThan(reasonableMaxTime);
410
+ });
411
+ });
412
+
413
+ describe("Edge Case Performance", () => {
414
+ it("handles empty schemas efficiently", async () => {
415
+ const schema = schemas.simple.object({});
416
+
417
+ const { duration } = await perf.measure(() =>
418
+ generateFromSchema({ schema }),
419
+ );
420
+
421
+ expect(duration).toBeLessThan(10); // Empty should be instant
422
+ });
423
+
424
+ it("handles schemas with many enum values", async () => {
425
+ const schema = schemas.simple.object({
426
+ country: {
427
+ type: "string",
428
+ enum: Array.from({ length: 200 }, (_, i) => `country-${i}`),
429
+ },
430
+ });
431
+
432
+ const { duration } = await perf.measure(() =>
433
+ generateFromSchema({ schema }),
434
+ );
435
+
436
+ expect(duration).toBeLessThan(50); // Large enum shouldn't be slow
437
+ });
438
+
439
+ it("handles complex regex patterns efficiently", async () => {
440
+ const schema = schemas.simple.object({
441
+ code: {
442
+ type: "string",
443
+ pattern: "^[A-Z]{2}-[0-9]{4}-[a-z]{2}-[0-9A-F]{8}$",
444
+ },
445
+ });
446
+
447
+ const { duration } = await perf.measure(() =>
448
+ generate.samples(schema, 10),
449
+ );
450
+
451
+ expect(duration).toBeLessThan(100); // Complex patterns OK
452
+ });
453
+
454
+ it("handles mixed constraint schemas", async () => {
455
+ const schema: JSONSchema7 = {
456
+ anyOf: [
457
+ {
458
+ type: "object",
459
+ properties: {
460
+ type: { const: "A" },
461
+ data: { type: "string", minLength: 100 },
462
+ },
463
+ },
464
+ {
465
+ type: "array",
466
+ items: { type: "number", minimum: 0, maximum: 1000 },
467
+ minItems: 50,
468
+ },
469
+ {
470
+ type: "string",
471
+ pattern: "^[A-Za-z0-9+/]{100,}={0,2}$",
472
+ },
473
+ ],
474
+ };
475
+
476
+ const { duration } = await perf.measure(() =>
477
+ generateFromSchema({ schema }),
478
+ );
479
+
480
+ expect(duration).toBeLessThan(200); // Complex but manageable
481
+ });
482
+ });
483
+ });