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