@schmock/faker 1.12.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { ALL_FIELD_MAPPINGS, type FieldMapping } from "./field-mappings";
2
+ import { ALL_FIELD_MAPPINGS } from "./field-mappings";
3
3
  import { findBestMapping } from "./field-name-matcher";
4
4
  import { generateFromSchema } from "./index";
5
5
 
@@ -7,14 +7,17 @@ import { generateFromSchema } from "./index";
7
7
  * Generate a value for a field by placing it inside an object schema
8
8
  * and calling generateFromSchema. Returns the generated value.
9
9
  */
10
- function generateField(fieldName: string, schemaType: string): unknown {
10
+ async function generateField(
11
+ fieldName: string,
12
+ schemaType: string,
13
+ ): Promise<unknown> {
11
14
  const schema = {
12
15
  type: "object" as const,
13
16
  properties: {
14
17
  [fieldName]: { type: schemaType as "string" | "number" | "boolean" },
15
18
  },
16
19
  };
17
- const result = generateFromSchema({ schema });
20
+ const result = await generateFromSchema({ schema });
18
21
  return result[fieldName];
19
22
  }
20
23
 
@@ -53,8 +56,8 @@ describe("Field mapping generation", () => {
53
56
  for (const mapping of stringMappings) {
54
57
  const keyword = mapping.keywords[0];
55
58
 
56
- it(`"${keyword}" generates a non-empty string`, () => {
57
- const value = generateField(keyword, "string");
59
+ it(`"${keyword}" generates a non-empty string`, async () => {
60
+ const value = await generateField(keyword, "string");
58
61
  expect(typeof value).toBe("string");
59
62
  expect((value as string).length).toBeGreaterThan(0);
60
63
  });
@@ -65,8 +68,8 @@ describe("Field mapping generation", () => {
65
68
  for (const mapping of numberMappings) {
66
69
  const keyword = mapping.keywords[0];
67
70
 
68
- it(`"${keyword}" generates a number`, () => {
69
- const value = generateField(keyword, "number");
71
+ it(`"${keyword}" generates a number`, async () => {
72
+ const value = await generateField(keyword, "number");
70
73
  expect(typeof value).toBe("number");
71
74
  expect(Number.isFinite(value as number)).toBe(true);
72
75
  });
@@ -77,8 +80,8 @@ describe("Field mapping generation", () => {
77
80
  for (const mapping of booleanMappings) {
78
81
  const keyword = mapping.keywords[0];
79
82
 
80
- it(`"${keyword}" generates a boolean`, () => {
81
- const value = generateField(keyword, "boolean");
83
+ it(`"${keyword}" generates a boolean`, async () => {
84
+ const value = await generateField(keyword, "boolean");
82
85
  expect(typeof value).toBe("boolean");
83
86
  });
84
87
  }
@@ -111,19 +114,27 @@ describe("All keyword variants match", () => {
111
114
  // ─── Category-specific quality checks ──────────────────────────────
112
115
 
113
116
  describe("Category quality checks", () => {
114
- function samples(fieldName: string, type: string, count = 10): unknown[] {
115
- return Array.from({ length: count }, () => generateField(fieldName, type));
117
+ async function samples(
118
+ fieldName: string,
119
+ type: string,
120
+ count = 10,
121
+ ): Promise<unknown[]> {
122
+ const results: unknown[] = [];
123
+ for (let i = 0; i < count; i++) {
124
+ results.push(await generateField(fieldName, type));
125
+ }
126
+ return results;
116
127
  }
117
128
 
118
129
  describe("Identity & Auth", () => {
119
- it("email contains @ and .", () => {
120
- for (const v of samples("email", "string")) {
130
+ it("email contains @ and .", async () => {
131
+ for (const v of await samples("email", "string")) {
121
132
  expect(v).toMatch(/@.*\./);
122
133
  }
123
134
  });
124
135
 
125
- it("username is a non-empty string", () => {
126
- for (const v of samples("username", "string")) {
136
+ it("username is a non-empty string", async () => {
137
+ for (const v of await samples("username", "string")) {
127
138
  expect(typeof v).toBe("string");
128
139
  expect((v as string).length).toBeGreaterThan(0);
129
140
  }
@@ -131,92 +142,92 @@ describe("Category quality checks", () => {
131
142
  });
132
143
 
133
144
  describe("Person", () => {
134
- it("firstName starts with uppercase", () => {
135
- for (const v of samples("firstName", "string")) {
145
+ it("firstName starts with uppercase", async () => {
146
+ for (const v of await samples("firstName", "string")) {
136
147
  expect(v).toMatch(/^[A-Z]/);
137
148
  }
138
149
  });
139
150
 
140
- it("lastName starts with uppercase", () => {
141
- for (const v of samples("lastName", "string")) {
151
+ it("lastName starts with uppercase", async () => {
152
+ for (const v of await samples("lastName", "string")) {
142
153
  expect(v).toMatch(/^[A-Z]/);
143
154
  }
144
155
  });
145
156
 
146
- it("name (fullName) contains a space", () => {
147
- for (const v of samples("name", "string")) {
157
+ it("name (fullName) contains a space", async () => {
158
+ for (const v of await samples("name", "string")) {
148
159
  expect(v).toContain(" ");
149
160
  }
150
161
  });
151
162
  });
152
163
 
153
164
  describe("Contact", () => {
154
- it("phone contains digits", () => {
155
- for (const v of samples("phone", "string")) {
165
+ it("phone contains digits", async () => {
166
+ for (const v of await samples("phone", "string")) {
156
167
  expect(v).toMatch(/\d/);
157
168
  }
158
169
  });
159
170
  });
160
171
 
161
172
  describe("Address", () => {
162
- it("city is a capitalized string", () => {
163
- for (const v of samples("city", "string")) {
173
+ it("city is a capitalized string", async () => {
174
+ for (const v of await samples("city", "string")) {
164
175
  expect(v).toMatch(/^[A-Z]/);
165
176
  }
166
177
  });
167
178
 
168
- it("zipcode matches US format", () => {
169
- for (const v of samples("zipcode", "string")) {
179
+ it("zipcode matches US format", async () => {
180
+ for (const v of await samples("zipcode", "string")) {
170
181
  expect(v).toMatch(/^\d{5}/);
171
182
  }
172
183
  });
173
184
 
174
- it("latitude is a valid number", () => {
175
- for (const v of samples("latitude", "number")) {
185
+ it("latitude is a valid number", async () => {
186
+ for (const v of await samples("latitude", "number")) {
176
187
  expect(typeof v).toBe("number");
177
188
  }
178
189
  });
179
190
 
180
- it("longitude is a valid number", () => {
181
- for (const v of samples("longitude", "number")) {
191
+ it("longitude is a valid number", async () => {
192
+ for (const v of await samples("longitude", "number")) {
182
193
  expect(typeof v).toBe("number");
183
194
  }
184
195
  });
185
196
  });
186
197
 
187
198
  describe("Internet", () => {
188
- it("url starts with http", () => {
189
- for (const v of samples("url", "string")) {
199
+ it("url starts with http", async () => {
200
+ for (const v of await samples("url", "string")) {
190
201
  expect(v).toMatch(/^https?:\/\//);
191
202
  }
192
203
  });
193
204
 
194
- it("ip_address looks like an IP", () => {
195
- for (const v of samples("ip_address", "string")) {
205
+ it("ip_address looks like an IP", async () => {
206
+ for (const v of await samples("ip_address", "string")) {
196
207
  expect(v).toMatch(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
197
208
  }
198
209
  });
199
210
  });
200
211
 
201
212
  describe("IDs", () => {
202
- it("uuid matches UUID pattern", () => {
203
- for (const v of samples("uuid", "string")) {
213
+ it("uuid matches UUID pattern", async () => {
214
+ for (const v of await samples("uuid", "string")) {
204
215
  expect(v).toMatch(
205
216
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
206
217
  );
207
218
  }
208
219
  });
209
220
 
210
- it("userId (suffix rule) generates UUID", () => {
211
- for (const v of samples("userId", "string")) {
221
+ it("userId (suffix rule) generates UUID", async () => {
222
+ for (const v of await samples("userId", "string")) {
212
223
  expect(v).toMatch(
213
224
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
214
225
  );
215
226
  }
216
227
  });
217
228
 
218
- it("order_id (suffix rule) generates UUID", () => {
219
- for (const v of samples("order_id", "string")) {
229
+ it("order_id (suffix rule) generates UUID", async () => {
230
+ for (const v of await samples("order_id", "string")) {
220
231
  expect(v).toMatch(
221
232
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
222
233
  );
@@ -225,71 +236,71 @@ describe("Category quality checks", () => {
225
236
  });
226
237
 
227
238
  describe("Text/Content", () => {
228
- it("description is a paragraph (> 20 chars)", () => {
229
- for (const v of samples("description", "string")) {
239
+ it("description is a paragraph (> 20 chars)", async () => {
240
+ for (const v of await samples("description", "string")) {
230
241
  expect((v as string).length).toBeGreaterThan(20);
231
242
  }
232
243
  });
233
244
 
234
- it("title is sentence-length", () => {
235
- for (const v of samples("title", "string")) {
245
+ it("title is sentence-length", async () => {
246
+ for (const v of await samples("title", "string")) {
236
247
  expect((v as string).length).toBeGreaterThan(5);
237
248
  }
238
249
  });
239
250
  });
240
251
 
241
252
  describe("Business", () => {
242
- it("company generates a non-empty string", () => {
243
- for (const v of samples("company", "string")) {
253
+ it("company generates a non-empty string", async () => {
254
+ for (const v of await samples("company", "string")) {
244
255
  expect((v as string).length).toBeGreaterThan(0);
245
256
  }
246
257
  });
247
258
  });
248
259
 
249
260
  describe("Date/Time", () => {
250
- it("createdAt generates parseable dates", () => {
251
- for (const v of samples("createdAt", "string")) {
261
+ it("createdAt generates parseable dates", async () => {
262
+ for (const v of await samples("createdAt", "string")) {
252
263
  expect(Date.parse(v as string)).not.toBeNaN();
253
264
  }
254
265
  });
255
266
 
256
- it("updatedAt generates parseable dates", () => {
257
- for (const v of samples("updatedAt", "string")) {
267
+ it("updatedAt generates parseable dates", async () => {
268
+ for (const v of await samples("updatedAt", "string")) {
258
269
  expect(Date.parse(v as string)).not.toBeNaN();
259
270
  }
260
271
  });
261
272
 
262
- it("expires_at generates parseable dates", () => {
263
- for (const v of samples("expires_at", "string")) {
273
+ it("expires_at generates parseable dates", async () => {
274
+ for (const v of await samples("expires_at", "string")) {
264
275
  expect(Date.parse(v as string)).not.toBeNaN();
265
276
  }
266
277
  });
267
278
  });
268
279
 
269
280
  describe("Boolean weighting", () => {
270
- it("isActive is mostly true (~90%)", () => {
271
- const values = samples("isActive", "boolean", 200) as boolean[];
281
+ it("isActive is mostly true (~90%)", async () => {
282
+ const values = (await samples("isActive", "boolean", 200)) as boolean[];
272
283
  const trueCount = values.filter(Boolean).length;
273
284
  // With p=0.9 and n=200, expect 160-200 trues
274
285
  expect(trueCount).toBeGreaterThan(140);
275
286
  });
276
287
 
277
- it("isDeleted is mostly false (~5% true)", () => {
278
- const values = samples("isDeleted", "boolean", 200) as boolean[];
288
+ it("isDeleted is mostly false (~5% true)", async () => {
289
+ const values = (await samples("isDeleted", "boolean", 200)) as boolean[];
279
290
  const trueCount = values.filter(Boolean).length;
280
291
  // With p=0.05 and n=200, expect 0-30 trues
281
292
  expect(trueCount).toBeLessThan(40);
282
293
  });
283
294
 
284
- it("isVerified is mostly true (~80%)", () => {
285
- const values = samples("isVerified", "boolean", 200) as boolean[];
295
+ it("isVerified is mostly true (~80%)", async () => {
296
+ const values = (await samples("isVerified", "boolean", 200)) as boolean[];
286
297
  const trueCount = values.filter(Boolean).length;
287
298
  expect(trueCount).toBeGreaterThan(120);
288
299
  });
289
300
  });
290
301
 
291
302
  describe("Nullable post-processing", () => {
292
- it("schmockNullable fields are null ~5% of the time", () => {
303
+ it("schmockNullable fields are null ~5% of the time", async () => {
293
304
  const schema = {
294
305
  type: "object" as const,
295
306
  properties: {
@@ -303,7 +314,7 @@ describe("Category quality checks", () => {
303
314
  let nullCount = 0;
304
315
  const total = 500;
305
316
  for (let i = 0; i < total; i++) {
306
- const result = generateFromSchema({ schema });
317
+ const result = await generateFromSchema({ schema });
307
318
  if (result.value === null) nullCount++;
308
319
  }
309
320
 
@@ -314,7 +325,7 @@ describe("Category quality checks", () => {
314
325
  });
315
326
 
316
327
  describe("Composition recursion", () => {
317
- it("enhances fields inside allOf branches", () => {
328
+ it("enhances fields inside allOf branches", async () => {
318
329
  const schema = {
319
330
  type: "object" as const,
320
331
  properties: {
@@ -332,7 +343,7 @@ describe("Category quality checks", () => {
332
343
  },
333
344
  };
334
345
 
335
- const result = generateFromSchema({ schema });
346
+ const result = await generateFromSchema({ schema });
336
347
  expect(result.data.email).toMatch(/@/);
337
348
  expect(result.data.city).toMatch(/^[A-Z]/);
338
349
  });
@@ -109,8 +109,13 @@ export function findBestMapping(
109
109
  };
110
110
  }
111
111
 
112
- // Skip if schema already has pattern, enum, or faker constraint
113
- if (schemaAny.pattern || schemaAny.enum || schemaAny.faker) {
112
+ // Skip if schema already has pattern, enum, faker, or $ref constraint
113
+ if (
114
+ schemaAny.pattern ||
115
+ schemaAny.enum ||
116
+ schemaAny.faker ||
117
+ schemaAny.$ref
118
+ ) {
114
119
  return undefined;
115
120
  }
116
121
 
@@ -134,6 +139,11 @@ export function findBestMapping(
134
139
  continue;
135
140
  }
136
141
 
142
+ // Skip faker mappings for object/array schemas — they generate primitive values
143
+ if (schemaType === "object" || schemaType === "array") {
144
+ continue;
145
+ }
146
+
137
147
  // Check schema type constraint
138
148
  if (mapping.schemaType && schemaType && mapping.schemaType !== schemaType) {
139
149
  const isNumeric =