@schmock/faker 2.2.3 → 2.3.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.
@@ -1,30 +1,61 @@
1
1
  import type { JSONSchema7 } from "json-schema";
2
2
  import { describe, expect, it } from "vitest";
3
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 () => {
4
+ import { generate, schemas } from "./test-utils";
5
+
6
+ function isRecord(value: unknown): value is Record<string, unknown> {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ function expectRecord(
11
+ value: unknown,
12
+ ): asserts value is Record<string, unknown> {
13
+ expect(isRecord(value)).toBe(true);
14
+ if (!isRecord(value)) throw new Error("Expected generated object data");
15
+ }
16
+
17
+ function expectGeneratedUser(value: unknown): void {
18
+ expectRecord(value);
19
+ expect(value.id).toEqual(expect.any(String));
20
+ expect(value.email).toEqual(expect.any(String));
21
+ expect(value.firstName).toEqual(expect.any(String));
22
+ expect(value.lastName).toEqual(expect.any(String));
23
+ expect(value.createdAt).toEqual(expect.any(String));
24
+ }
25
+
26
+ function pluginContext(): Schmock.PluginContext {
27
+ return {
28
+ method: "GET",
29
+ path: "/test",
30
+ params: {},
31
+ query: {},
32
+ state: new Map(),
33
+ routeState: {},
34
+ headers: {},
35
+ body: null,
36
+ route: {},
37
+ };
38
+ }
39
+
40
+ describe("Faker generation workload reliability", () => {
41
+ describe("Representative schema workloads", () => {
42
+ it("preserves simple object shape across repeated generation", async () => {
9
43
  const schema = schemas.simple.object({
10
44
  id: schemas.simple.number(),
11
45
  name: schemas.simple.string(),
12
46
  active: { type: "boolean" },
13
47
  });
14
48
 
15
- const times: number[] = [];
16
- for (let i = 0; i < 10; i++) {
17
- const { duration } = await perf.measure(
18
- async () => await generateFromSchema({ schema }),
19
- );
20
- times.push(duration);
49
+ for (let seed = 0; seed < 10; seed += 1) {
50
+ const result = await generateFromSchema({ schema, seed });
51
+ expectRecord(result);
52
+ expect(result.id).toEqual(expect.any(Number));
53
+ expect(result.name).toEqual(expect.any(String));
54
+ expect(result.active).toEqual(expect.any(Boolean));
21
55
  }
22
-
23
- const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
24
- expect(avgTime).toBeLessThan(50); // Should average under 50ms
25
56
  });
26
57
 
27
- it("handles nested objects efficiently", async () => {
58
+ it("preserves a three-level nested object shape", async () => {
28
59
  const schema = schemas.nested.deep(
29
60
  3,
30
61
  schemas.simple.object({
@@ -33,14 +64,17 @@ describe("Performance and Memory", () => {
33
64
  }),
34
65
  );
35
66
 
36
- const { duration } = await perf.measure(
37
- async () => await generateFromSchema({ schema }),
38
- );
39
-
40
- expect(duration).toBeLessThan(100); // Reasonable for nested structure
67
+ let level = await generateFromSchema({ schema, seed: 42 });
68
+ for (let depth = 0; depth < 3; depth += 1) {
69
+ expectRecord(level);
70
+ level = level.nested;
71
+ }
72
+ expectRecord(level);
73
+ expect(level.id).toEqual(expect.any(Number));
74
+ expect(level.value).toEqual(expect.any(String));
41
75
  });
42
76
 
43
- it("generates arrays efficiently", async () => {
77
+ it("generates every item in a fixed-size object array", async () => {
44
78
  const schema = schemas.simple.array(
45
79
  schemas.simple.object({
46
80
  id: schemas.simple.number(),
@@ -49,14 +83,18 @@ describe("Performance and Memory", () => {
49
83
  { minItems: 50, maxItems: 50 },
50
84
  );
51
85
 
52
- const { duration } = await perf.measure(
53
- async () => await generateFromSchema({ schema }),
54
- );
55
-
56
- expect(duration).toBeLessThan(200); // Should handle 50 items quickly
86
+ const result = await generateFromSchema({ schema, seed: 42 });
87
+ expect(Array.isArray(result)).toBe(true);
88
+ if (!Array.isArray(result)) throw new Error("Expected generated array");
89
+ expect(result).toHaveLength(50);
90
+ for (const item of result) {
91
+ expectRecord(item);
92
+ expect(item.id).toEqual(expect.any(Number));
93
+ expect(item.name).toEqual(expect.any(String));
94
+ }
57
95
  });
58
96
 
59
- it("handles complex schemas with multiple constraints", async () => {
97
+ it("honors constraints in a complex collection schema", async () => {
60
98
  const schema: JSONSchema7 = {
61
99
  type: "object",
62
100
  properties: {
@@ -80,389 +118,285 @@ describe("Performance and Memory", () => {
80
118
  maxItems: 10,
81
119
  },
82
120
  },
121
+ required: ["users"],
83
122
  };
84
123
 
85
- const { duration } = await perf.measure(
86
- async () => await 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
- await generateFromSchema({ schema: smallSchema });
108
- await 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
- await generateFromSchema({ schema: smallSchema });
118
- const small = performance.now() - start1;
119
- smallTimes.push(small);
120
-
121
- const start2 = performance.now();
122
- await generateFromSchema({ schema: largeSchema });
123
- const large = performance.now() - start2;
124
- largeTimes.push(large);
124
+ const result = await generateFromSchema({ schema, seed: 42 });
125
+ expectRecord(result);
126
+ expect(Array.isArray(result.users)).toBe(true);
127
+ if (!Array.isArray(result.users)) {
128
+ throw new Error("Expected users collection");
125
129
  }
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
130
+ expect(result.users).toHaveLength(10);
131
+ for (const user of result.users) {
132
+ expectRecord(user);
133
+ expect(user.id).toMatch(
134
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
135
+ );
136
+ expect(user.email).toEqual(expect.any(String));
137
+ expect(user.age).toBeGreaterThanOrEqual(18);
138
+ expect(user.age).toBeLessThanOrEqual(100);
139
+ expect(Array.isArray(user.tags)).toBe(true);
140
+ if (!Array.isArray(user.tags)) throw new Error("Expected user tags");
141
+ expect(user.tags.length).toBeLessThanOrEqual(5);
142
+ for (const tag of user.tags) expect(tag).toMatch(/^[a-z]+$/);
142
143
  }
143
144
  });
144
145
 
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
- await generateFromSchema({ schema: narrowSchema });
152
- await generateFromSchema({ schema: wideSchema });
146
+ it("handles both small and large fixed array workloads", async () => {
147
+ const sizes = [50, 500];
148
+
149
+ for (const size of sizes) {
150
+ const schema = schemas.simple.array(schemas.simple.string(), {
151
+ minItems: size,
152
+ maxItems: size,
153
+ });
154
+ const result = await generateFromSchema({ schema, seed: 42 });
155
+ expect(Array.isArray(result)).toBe(true);
156
+ if (!Array.isArray(result)) throw new Error("Expected generated array");
157
+ expect(result).toHaveLength(size);
158
+ expect(result.every((item) => typeof item === "string")).toBe(true);
153
159
  }
160
+ });
154
161
 
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
- await generateFromSchema({ schema: narrowSchema });
162
- narrowTimes.push(performance.now() - start1);
163
-
164
- const start2 = performance.now();
165
- await 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 medNarrow = median(narrowTimes);
179
- const medWide = median(wideTimes);
180
-
181
- // Scale assertion (machine-speed-independent): wide has 5x the
182
- // properties of narrow, so its generation time should scale roughly
183
- // with that. Allow 20x headroom for measurement noise + small superlinear
184
- // behavior; this catches order-of-magnitude regressions without flaking
185
- // on shared CI runners where absolute wall-clock thresholds are unreliable.
186
- if (medNarrow > 0.1) {
187
- // Only check scaling if we have measurable timing
188
- expect(medWide).toBeGreaterThan(medNarrow * 0.5); // sanity: more work, not less
189
- expect(medWide).toBeLessThan(medNarrow * 20); // catches >4x-per-property regressions
162
+ it("handles both narrow and wide object workloads", async () => {
163
+ for (const width of [20, 100]) {
164
+ const result = await generateFromSchema({
165
+ schema: schemas.nested.wide(width),
166
+ seed: 42,
167
+ });
168
+ expectRecord(result);
169
+ expect(Object.keys(result)).toHaveLength(width);
170
+ expect(
171
+ Object.values(result).every((value) => typeof value === "string"),
172
+ ).toBe(true);
190
173
  }
191
174
  });
192
175
  });
193
176
 
194
- describe("Plugin Performance", () => {
195
- it("plugin creation is fast", async () => {
196
- const schema = schemas.complex.user();
177
+ describe("Plugin workloads", () => {
178
+ it("constructs a plugin with stable metadata", () => {
179
+ const plugin = fakerPlugin({ schema: schemas.complex.user() });
197
180
 
198
- const { duration } = await perf.measure(() => fakerPlugin({ schema }));
199
-
200
- expect(duration).toBeLessThan(10); // Plugin creation should be instant
181
+ expect(plugin.name).toBe("faker");
182
+ expect(plugin.version).toMatch(/^\d+\.\d+\.\d+/);
201
183
  });
202
184
 
203
- it("plugin processing adds minimal overhead", async () => {
204
- const schema = schemas.complex.apiResponse();
205
- const plugin = fakerPlugin({ schema });
206
-
207
- const context = {
208
- method: "GET",
209
- path: "/test",
210
- params: {},
211
- query: {},
212
- state: {},
213
- headers: {},
214
- body: null,
215
- route: {},
216
- };
217
-
218
- const times: number[] = [];
219
- for (let i = 0; i < 10; i++) {
220
- const { duration } = await perf.measure(() => plugin.process(context));
221
- times.push(duration);
185
+ it("generates a complete response through repeated plugin processing", async () => {
186
+ const plugin = fakerPlugin({
187
+ schema: schemas.complex.apiResponse(),
188
+ seed: 42,
189
+ });
190
+ const context = pluginContext();
191
+ let firstResponse: unknown;
192
+
193
+ for (let iteration = 0; iteration < 10; iteration += 1) {
194
+ const result = await plugin.process(context);
195
+ expect(result.context).toBe(context);
196
+ expectRecord(result.response);
197
+ expect(result.response.success).toEqual(expect.any(Boolean));
198
+ expect(Array.isArray(result.response.data)).toBe(true);
199
+ if (iteration === 0) firstResponse = result.response;
200
+ else expect(result.response).toEqual(firstResponse);
222
201
  }
223
-
224
- const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
225
- expect(avgTime).toBeLessThan(100);
226
202
  });
227
203
 
228
- it("template processing is efficient", async () => {
204
+ it("applies template overrides under a representative workload", async () => {
229
205
  const schema = schemas.simple.object({
230
206
  id: schemas.simple.string(),
231
207
  userId: schemas.simple.string(),
232
208
  timestamp: schemas.simple.string(),
233
209
  message: schemas.simple.string(),
234
210
  });
211
+ const timestamp = "2026-07-12T12:00:00.000Z";
212
+
213
+ const result = await generateFromSchema({
214
+ schema,
215
+ seed: 42,
216
+ overrides: {
217
+ id: "{{params.id}}",
218
+ userId: "{{state.user.id}}",
219
+ timestamp: "{{state.timestamp}}",
220
+ message: "User {{params.id}} at {{state.timestamp}}",
221
+ },
222
+ params: { id: "123" },
223
+ state: { user: { id: "user-456" }, timestamp },
224
+ });
235
225
 
236
- const overrides = {
237
- id: "{{params.id}}",
238
- userId: "{{state.user.id}}",
239
- timestamp: "{{state.timestamp}}",
240
- message: "User {{params.id}} at {{state.timestamp}}",
241
- };
242
-
243
- const { duration } = await perf.measure(
244
- async () =>
245
- await generateFromSchema({
246
- schema,
247
- overrides,
248
- params: { id: "123" },
249
- state: {
250
- user: { id: "user-456" },
251
- timestamp: new Date().toISOString(),
252
- },
253
- }),
254
- );
255
-
256
- expect(duration).toBeLessThan(50); // Template processing should be fast
226
+ expect(result).toEqual({
227
+ id: "123",
228
+ userId: "user-456",
229
+ timestamp,
230
+ message: `User 123 at ${timestamp}`,
231
+ });
257
232
  });
258
233
  });
259
234
 
260
- describe("Concurrent Generation", () => {
261
- it("handles multiple concurrent generations", async () => {
235
+ describe("Concurrent and repeated workloads", () => {
236
+ it("keeps twenty concurrent generations isolated and well formed", async () => {
262
237
  const schema = schemas.complex.user();
238
+ const results = await Promise.all(
239
+ Array.from({ length: 20 }, (_, seed) =>
240
+ generateFromSchema({ schema, seed }),
241
+ ),
242
+ );
263
243
 
264
- const { duration } = await perf.measure(async () => {
265
- const promises = Array.from(
266
- { length: 20 },
267
- async () => await generateFromSchema({ schema }),
268
- );
269
- await Promise.all(promises);
270
- });
271
-
272
- expect(duration).toBeLessThan(500); // Should handle concurrency well
244
+ expect(results).toHaveLength(20);
245
+ for (const result of results) expectGeneratedUser(result);
246
+ expect(
247
+ new Set(results.map((result) => JSON.stringify(result))).size,
248
+ ).toBe(20);
273
249
  });
274
250
 
275
- it("maintains performance under load", async () => {
251
+ it("preserves shape under a hundred sequential generations", async () => {
276
252
  const schema = schemas.simple.object({
277
253
  id: schemas.simple.number(),
278
254
  data: schemas.simple.string(),
279
255
  });
256
+ const results: unknown[] = [];
280
257
 
281
- // Warm up
282
- await generateFromSchema({ schema });
283
-
284
- // Test under load
285
- const iterations = 100;
286
- const { duration } = await perf.measure(async () => {
287
- for (let i = 0; i < iterations; i++) {
288
- await generateFromSchema({ schema });
289
- }
290
- });
291
-
292
- const avgTime = duration / iterations;
293
- expect(avgTime).toBeLessThan(10); // Should maintain speed
258
+ for (let seed = 0; seed < 100; seed += 1) {
259
+ const result = await generateFromSchema({ schema, seed });
260
+ expectRecord(result);
261
+ expect(result.id).toEqual(expect.any(Number));
262
+ expect(result.data).toEqual(expect.any(String));
263
+ results.push(result);
264
+ }
265
+ expect(
266
+ new Set(results.map((result) => JSON.stringify(result))).size,
267
+ ).toBe(100);
294
268
  });
295
- });
296
269
 
297
- describe("Memory Efficiency", () => {
298
- it("doesn't leak memory on repeated generation", async () => {
270
+ it("preserves generated shape across a thousand calls", async () => {
299
271
  const schema = schemas.simple.object({
300
272
  id: schemas.simple.number(),
301
273
  name: schemas.simple.string(),
302
274
  });
303
-
304
- // Generate many times
305
- for (let i = 0; i < 1000; i++) {
306
- const result = await generateFromSchema({ schema });
307
- // Result should be garbage collectable
308
- expect(result).toBeDefined();
275
+ const serializedResults = new Set<string>();
276
+
277
+ for (let seed = 0; seed < 1_000; seed += 1) {
278
+ const result = await generateFromSchema({ schema, seed });
279
+ expectRecord(result);
280
+ expect(result.id).toEqual(expect.any(Number));
281
+ expect(result.name).toEqual(expect.any(String));
282
+ serializedResults.add(JSON.stringify(result));
309
283
  }
310
284
 
311
- // If we got here without crashing, memory is managed well
312
- expect(true).toBe(true);
285
+ expect(serializedResults.size).toBe(1_000);
313
286
  });
314
287
 
315
- it("handles large data structures without excessive memory", async () => {
288
+ it("generates a thousand-item data structure", async () => {
316
289
  const schema = schemas.simple.array(
317
290
  schemas.simple.object({
318
291
  id: schemas.simple.string(),
319
292
  data: schemas.simple.string(),
320
293
  }),
321
- { minItems: 1000, maxItems: 1000 },
294
+ { minItems: 1_000, maxItems: 1_000 },
322
295
  );
323
296
 
324
- // Should be able to generate without memory issues
325
- const result = await generateFromSchema({ schema });
326
- expect(result).toHaveLength(1000);
297
+ const result = await generateFromSchema({ schema, seed: 42 });
298
+ expect(Array.isArray(result)).toBe(true);
299
+ if (!Array.isArray(result)) throw new Error("Expected generated array");
300
+ expect(result).toHaveLength(1_000);
301
+ for (const item of result) {
302
+ expectRecord(item);
303
+ expect(item.id).toEqual(expect.any(String));
304
+ expect(item.data).toEqual(expect.any(String));
305
+ }
327
306
  });
328
307
 
329
- it("cleans up after schema validation errors", async () => {
330
- // Generate errors repeatedly
331
- for (let i = 0; i < 100; i++) {
332
- try {
333
- await generateFromSchema({ schema: { type: "invalid" as any } });
334
- } catch (_e) {
335
- // Expected
336
- }
308
+ it("recovers after repeated resource-limit failures", async () => {
309
+ const oversizedSchema = schemas.simple.array(schemas.simple.string(), {
310
+ minItems: 50_001,
311
+ maxItems: 50_001,
312
+ });
313
+
314
+ for (let iteration = 0; iteration < 100; iteration += 1) {
315
+ await expect(
316
+ generateFromSchema({ schema: oversizedSchema }),
317
+ ).rejects.toThrow("array_max_items");
337
318
  }
338
319
 
339
- // Should not have memory leaks from error objects
340
- expect(true).toBe(true);
320
+ const recovered = await generateFromSchema({
321
+ schema: schemas.simple.object({ id: schemas.simple.number() }),
322
+ seed: 42,
323
+ });
324
+ expectRecord(recovered);
325
+ expect(recovered.id).toEqual(expect.any(Number));
341
326
  });
342
327
  });
343
328
 
344
- describe("Optimization Opportunities", () => {
345
- it("generates data consistently across multiple calls", async () => {
329
+ describe("Repeated mapping and edge-case workloads", () => {
330
+ it("keeps smart field mapping valid across multiple seeds", async () => {
346
331
  const schema = schemas.simple.object({
347
332
  email: schemas.simple.string(),
348
333
  firstName: schemas.simple.string(),
349
334
  phone: schemas.simple.string(),
350
335
  });
351
-
352
- // Generate multiple times to ensure consistent behavior
353
- const results: any[] = [];
354
- for (let i = 0; i < 10; i++) {
355
- const result = await generateFromSchema({ schema });
336
+ const results: Record<string, unknown>[] = [];
337
+
338
+ for (let seed = 0; seed < 10; seed += 1) {
339
+ const result = await generateFromSchema({ schema, seed });
340
+ expectRecord(result);
341
+ expect(result.email).toEqual(expect.any(String));
342
+ expect(result.firstName).toEqual(expect.any(String));
343
+ expect(result.phone).toEqual(expect.any(String));
356
344
  results.push(result);
357
345
  }
358
346
 
359
- // Verify all results have the expected structure
360
- results.forEach((result) => {
361
- expect(result).toHaveProperty("email");
362
- expect(result).toHaveProperty("firstName");
363
- expect(result).toHaveProperty("phone");
364
- expect(typeof result.email).toBe("string");
365
- expect(typeof result.firstName).toBe("string");
366
- expect(typeof result.phone).toBe("string");
367
- });
368
-
369
- // Verify that smart field mapping worked across all calls
370
- const emails = results.map((r) => r.email);
371
- const firstNames = results.map((r) => r.firstName);
372
-
373
- // Should have good diversity (no exact duplicates expected)
374
- const uniqueEmails = new Set(emails);
375
- const uniqueFirstNames = new Set(firstNames);
376
- expect(uniqueEmails.size).toBeGreaterThan(1);
377
- expect(uniqueFirstNames.size).toBeGreaterThan(1);
347
+ expect(new Set(results.map((result) => result.email)).size).toBe(10);
348
+ expect(new Set(results.map((result) => result.firstName)).size).toBe(10);
378
349
  });
379
350
 
380
- it("reuses schema enhancement for repeated generations", async () => {
381
- const schema = schemas.complex.user();
382
- const plugin = fakerPlugin({ schema });
383
-
384
- const context = {
385
- method: "GET",
386
- path: "/test",
387
- params: {},
388
- query: {},
389
- state: {},
390
- headers: {},
391
- body: null,
392
- route: {},
393
- };
394
-
395
- // Warmup
396
- for (let i = 0; i < 3; i++) {
397
- plugin.process(context);
398
- }
351
+ it("reuses one plugin without changing seeded output", async () => {
352
+ const plugin = fakerPlugin({ schema: schemas.complex.user(), seed: 42 });
353
+ const context = pluginContext();
354
+ const responses: unknown[] = [];
399
355
 
400
- // Multiple calls through same plugin with proper timing
401
- const times: number[] = [];
402
- for (let i = 0; i < 15; i++) {
403
- const start = performance.now();
404
- plugin.process(context);
405
- times.push(performance.now() - start);
356
+ for (let iteration = 0; iteration < 15; iteration += 1) {
357
+ const result = await plugin.process(context);
358
+ expectGeneratedUser(result.response);
359
+ responses.push(result.response);
406
360
  }
407
361
 
408
- // Calculate statistics
409
- const avgTime = times.reduce((a, b) => a + b, 0) / times.length;
410
- const maxTime = Math.max(...times);
411
- const minTime = Math.min(...times);
412
-
413
- // Performance should be consistent and reasonable
414
- expect(avgTime).toBeLessThan(50); // Should be reasonably fast
415
- expect(maxTime).toBeLessThan(100); // No extreme outliers
416
- expect(minTime).toBeGreaterThanOrEqual(0); // Valid timing
417
-
418
- // All measurements should be reasonable - no extreme variance
419
- const reasonableMaxTime = Math.max(avgTime * 5, 10); // At least 10ms tolerance
420
- expect(maxTime).toBeLessThan(reasonableMaxTime);
362
+ expect(responses).toEqual(Array.from({ length: 15 }, () => responses[0]));
421
363
  });
422
- });
423
-
424
- describe("Edge Case Performance", () => {
425
- it("handles empty schemas efficiently", async () => {
426
- const schema = schemas.simple.object({});
427
-
428
- const { duration } = await perf.measure(
429
- async () => await generateFromSchema({ schema }),
430
- );
431
364
 
432
- expect(duration).toBeLessThan(10); // Empty should be instant
365
+ it("handles an empty object schema", async () => {
366
+ await expect(
367
+ generateFromSchema({ schema: schemas.simple.object({}), seed: 42 }),
368
+ ).resolves.toEqual({});
433
369
  });
434
370
 
435
- it("handles schemas with many enum values", async () => {
371
+ it("selects only declared values from a large enum", async () => {
372
+ const countries = Array.from(
373
+ { length: 200 },
374
+ (_, index) => `country-${index}`,
375
+ );
436
376
  const schema = schemas.simple.object({
437
- country: {
438
- type: "string",
439
- enum: Array.from({ length: 200 }, (_, i) => `country-${i}`),
440
- },
377
+ country: { type: "string", enum: countries },
441
378
  });
379
+ const result = await generateFromSchema({ schema, seed: 42 });
442
380
 
443
- const { duration } = await perf.measure(
444
- async () => await generateFromSchema({ schema }),
445
- );
446
-
447
- expect(duration).toBeLessThan(50); // Large enum shouldn't be slow
381
+ expectRecord(result);
382
+ expect(countries).toContain(result.country);
448
383
  });
449
384
 
450
- it("handles complex regex patterns efficiently", async () => {
385
+ it("generates samples matching a complex pattern", async () => {
386
+ const pattern = /^[A-Z]{2}-[0-9]{4}-[a-z]{2}-[0-9A-F]{8}$/;
451
387
  const schema = schemas.simple.object({
452
- code: {
453
- type: "string",
454
- pattern: "^[A-Z]{2}-[0-9]{4}-[a-z]{2}-[0-9A-F]{8}$",
455
- },
388
+ code: { type: "string", pattern: pattern.source },
456
389
  });
390
+ const results = await generate.samples(schema, 10, { seed: 42 });
457
391
 
458
- const { duration } = await perf.measure(() =>
459
- generate.samples(schema, 10),
460
- );
461
-
462
- expect(duration).toBeLessThan(100); // Complex patterns OK
392
+ expect(results).toHaveLength(10);
393
+ for (const result of results) {
394
+ expectRecord(result);
395
+ expect(result.code).toMatch(pattern);
396
+ }
463
397
  });
464
398
 
465
- it("handles mixed constraint schemas", async () => {
399
+ it("satisfies one branch of a mixed constraint schema", async () => {
466
400
  const schema: JSONSchema7 = {
467
401
  anyOf: [
468
402
  {
@@ -471,10 +405,11 @@ describe("Performance and Memory", () => {
471
405
  type: { const: "A" },
472
406
  data: { type: "string", minLength: 100 },
473
407
  },
408
+ required: ["type", "data"],
474
409
  },
475
410
  {
476
411
  type: "array",
477
- items: { type: "number", minimum: 0, maximum: 1000 },
412
+ items: { type: "number", minimum: 0, maximum: 1_000 },
478
413
  minItems: 50,
479
414
  },
480
415
  {
@@ -484,11 +419,23 @@ describe("Performance and Memory", () => {
484
419
  ],
485
420
  };
486
421
 
487
- const { duration } = await perf.measure(
488
- async () => await generateFromSchema({ schema }),
489
- );
422
+ const result = await generateFromSchema({ schema, seed: 42 });
423
+ const isValidObject =
424
+ isRecord(result) &&
425
+ result.type === "A" &&
426
+ typeof result.data === "string" &&
427
+ result.data.length >= 100;
428
+ const isValidArray =
429
+ Array.isArray(result) &&
430
+ result.length >= 50 &&
431
+ result.every(
432
+ (value) => typeof value === "number" && value >= 0 && value <= 1_000,
433
+ );
434
+ const isValidString =
435
+ typeof result === "string" &&
436
+ /^[A-Za-z0-9+/]{100,}={0,2}$/.test(result);
490
437
 
491
- expect(duration).toBeLessThan(200); // Complex but manageable
438
+ expect(isValidObject || isValidArray || isValidString).toBe(true);
492
439
  });
493
440
  });
494
441
  });