@schmock/faker 2.3.0 → 2.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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/constants.d.ts +82 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/field-name-matcher.d.ts +19 -0
- package/dist/field-name-matcher.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/jsf-config.d.ts +19 -4
- package/dist/jsf-config.d.ts.map +1 -1
- package/dist/output-limits.d.ts +3 -0
- package/dist/output-limits.d.ts.map +1 -0
- package/dist/overrides.d.ts +2 -1
- package/dist/overrides.d.ts.map +1 -1
- package/dist/schema-children.d.ts +14 -0
- package/dist/schema-children.d.ts.map +1 -0
- package/dist/schema-enhancement.d.ts +1 -0
- package/dist/schema-enhancement.d.ts.map +1 -1
- package/dist/validation.d.ts +24 -5
- package/dist/validation.d.ts.map +1 -1
- package/package.json +21 -7
- package/dist/constants.js +0 -8
- package/dist/field-mappings.js +0 -1114
- package/dist/field-name-matcher.js +0 -148
- package/dist/jsf-config.js +0 -117
- package/dist/overrides.js +0 -137
- package/dist/schema-enhancement.js +0 -104
- package/dist/test-utils.d.ts +0 -57
- package/dist/test-utils.d.ts.map +0 -1
- package/dist/test-utils.js +0 -284
- package/dist/validation.js +0 -300
- package/src/advanced-features.test.ts +0 -912
- package/src/audit-faker-args.test.ts +0 -97
- package/src/constants.d.ts.map +0 -1
- package/src/constants.ts +0 -9
- package/src/data-quality.test.ts +0 -415
- package/src/error-handling.test.ts +0 -504
- package/src/field-mappings.test.ts +0 -351
- package/src/field-mappings.ts +0 -1148
- package/src/field-name-matcher.test.ts +0 -292
- package/src/field-name-matcher.ts +0 -187
- package/src/index.d.ts.map +0 -1
- package/src/index.test.ts +0 -1320
- package/src/index.ts +0 -185
- package/src/integration.test.ts +0 -634
- package/src/jsf-config.d.ts.map +0 -1
- package/src/jsf-config.test.ts +0 -65
- package/src/jsf-config.ts +0 -156
- package/src/overrides.d.ts.map +0 -1
- package/src/overrides.property.test.ts +0 -172
- package/src/overrides.test.ts +0 -38
- package/src/overrides.ts +0 -179
- package/src/performance.test.ts +0 -441
- package/src/plugin-integration.test.ts +0 -575
- package/src/post-process.test.ts +0 -177
- package/src/real-world.test.ts +0 -636
- package/src/schema-enhancement.d.ts.map +0 -1
- package/src/schema-enhancement.test.ts +0 -222
- package/src/schema-enhancement.ts +0 -144
- package/src/steps/deterministic-seeds.steps.ts +0 -112
- package/src/steps/faker-plugin.steps.ts +0 -301
- package/src/test-utils.ts +0 -385
- package/src/validation.d.ts.map +0 -1
- package/src/validation.ts +0 -417
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { ALL_FIELD_MAPPINGS } from "./field-mappings";
|
|
3
|
-
import { findBestMapping } from "./field-name-matcher";
|
|
4
|
-
import { generateFromSchema } from "./index";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Generate a value for a field by placing it inside an object schema
|
|
8
|
-
* and calling generateFromSchema. Returns the generated value.
|
|
9
|
-
*/
|
|
10
|
-
async function generateField(
|
|
11
|
-
fieldName: string,
|
|
12
|
-
schemaType: string,
|
|
13
|
-
): Promise<unknown> {
|
|
14
|
-
const schema = {
|
|
15
|
-
type: "object" as const,
|
|
16
|
-
properties: {
|
|
17
|
-
[fieldName]: { type: schemaType as "string" | "number" | "boolean" },
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
const result = await generateFromSchema({ schema });
|
|
21
|
-
return result[fieldName];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ─── Matcher coverage: every mapping's first keyword resolves ───────
|
|
25
|
-
|
|
26
|
-
describe("Field mapping matcher coverage", () => {
|
|
27
|
-
for (const mapping of ALL_FIELD_MAPPINGS) {
|
|
28
|
-
const keyword = mapping.keywords[0];
|
|
29
|
-
const type = mapping.schemaType ?? "string";
|
|
30
|
-
|
|
31
|
-
it(`"${keyword}" → ${mapping.fakerMethod}`, () => {
|
|
32
|
-
const match = findBestMapping(keyword, {
|
|
33
|
-
type: type as "string" | "number" | "boolean",
|
|
34
|
-
});
|
|
35
|
-
expect(match).toBeDefined();
|
|
36
|
-
expect(match?.mapping.fakerMethod).toBe(mapping.fakerMethod);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// ─── Generation: every mapping produces a value of the right type ──
|
|
42
|
-
|
|
43
|
-
describe("Field mapping generation", () => {
|
|
44
|
-
// Group mappings by category for readable output
|
|
45
|
-
const stringMappings = ALL_FIELD_MAPPINGS.filter(
|
|
46
|
-
(m) => !m.schemaType || m.schemaType === "string",
|
|
47
|
-
);
|
|
48
|
-
const numberMappings = ALL_FIELD_MAPPINGS.filter(
|
|
49
|
-
(m) => m.schemaType === "number",
|
|
50
|
-
);
|
|
51
|
-
const booleanMappings = ALL_FIELD_MAPPINGS.filter(
|
|
52
|
-
(m) => m.schemaType === "boolean",
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
describe("string fields", () => {
|
|
56
|
-
for (const mapping of stringMappings) {
|
|
57
|
-
const keyword = mapping.keywords[0];
|
|
58
|
-
|
|
59
|
-
it(`"${keyword}" generates a non-empty string`, async () => {
|
|
60
|
-
const value = await generateField(keyword, "string");
|
|
61
|
-
expect(typeof value).toBe("string");
|
|
62
|
-
expect((value as string).length).toBeGreaterThan(0);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("number fields", () => {
|
|
68
|
-
for (const mapping of numberMappings) {
|
|
69
|
-
const keyword = mapping.keywords[0];
|
|
70
|
-
|
|
71
|
-
it(`"${keyword}" generates a number`, async () => {
|
|
72
|
-
const value = await generateField(keyword, "number");
|
|
73
|
-
expect(typeof value).toBe("number");
|
|
74
|
-
expect(Number.isFinite(value as number)).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe("boolean fields", () => {
|
|
80
|
-
for (const mapping of booleanMappings) {
|
|
81
|
-
const keyword = mapping.keywords[0];
|
|
82
|
-
|
|
83
|
-
it(`"${keyword}" generates a boolean`, async () => {
|
|
84
|
-
const value = await generateField(keyword, "boolean");
|
|
85
|
-
expect(typeof value).toBe("boolean");
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ─── All keyword variants resolve to the same faker method ─────────
|
|
92
|
-
|
|
93
|
-
describe("All keyword variants match", () => {
|
|
94
|
-
for (const mapping of ALL_FIELD_MAPPINGS) {
|
|
95
|
-
if (mapping.keywords.length <= 1) continue;
|
|
96
|
-
|
|
97
|
-
describe(`${mapping.fakerMethod}`, () => {
|
|
98
|
-
for (const keyword of mapping.keywords) {
|
|
99
|
-
it(`"${keyword}" matches`, () => {
|
|
100
|
-
const match = findBestMapping(keyword, {
|
|
101
|
-
type: (mapping.schemaType ?? "string") as
|
|
102
|
-
| "string"
|
|
103
|
-
| "number"
|
|
104
|
-
| "boolean",
|
|
105
|
-
});
|
|
106
|
-
expect(match).toBeDefined();
|
|
107
|
-
expect(match?.mapping.fakerMethod).toBe(mapping.fakerMethod);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// ─── Category-specific quality checks ──────────────────────────────
|
|
115
|
-
|
|
116
|
-
describe("Category quality checks", () => {
|
|
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;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
describe("Identity & Auth", () => {
|
|
130
|
-
it("email contains @ and .", async () => {
|
|
131
|
-
for (const v of await samples("email", "string")) {
|
|
132
|
-
expect(v).toMatch(/@.*\./);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("username is a non-empty string", async () => {
|
|
137
|
-
for (const v of await samples("username", "string")) {
|
|
138
|
-
expect(typeof v).toBe("string");
|
|
139
|
-
expect((v as string).length).toBeGreaterThan(0);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("Person", () => {
|
|
145
|
-
it("firstName starts with uppercase", async () => {
|
|
146
|
-
for (const v of await samples("firstName", "string")) {
|
|
147
|
-
expect(v).toMatch(/^[A-Z]/);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("lastName starts with uppercase", async () => {
|
|
152
|
-
for (const v of await samples("lastName", "string")) {
|
|
153
|
-
expect(v).toMatch(/^[A-Z]/);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("name (fullName) contains a space", async () => {
|
|
158
|
-
for (const v of await samples("name", "string")) {
|
|
159
|
-
expect(v).toContain(" ");
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe("Contact", () => {
|
|
165
|
-
it("phone contains digits", async () => {
|
|
166
|
-
for (const v of await samples("phone", "string")) {
|
|
167
|
-
expect(v).toMatch(/\d/);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe("Address", () => {
|
|
173
|
-
it("city is a capitalized string", async () => {
|
|
174
|
-
for (const v of await samples("city", "string")) {
|
|
175
|
-
expect(v).toMatch(/^[A-Z]/);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("zipcode matches US format", async () => {
|
|
180
|
-
for (const v of await samples("zipcode", "string")) {
|
|
181
|
-
expect(v).toMatch(/^\d{5}/);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("latitude is a valid number", async () => {
|
|
186
|
-
for (const v of await samples("latitude", "number")) {
|
|
187
|
-
expect(typeof v).toBe("number");
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("longitude is a valid number", async () => {
|
|
192
|
-
for (const v of await samples("longitude", "number")) {
|
|
193
|
-
expect(typeof v).toBe("number");
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe("Internet", () => {
|
|
199
|
-
it("url starts with http", async () => {
|
|
200
|
-
for (const v of await samples("url", "string")) {
|
|
201
|
-
expect(v).toMatch(/^https?:\/\//);
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("ip_address looks like an IP", async () => {
|
|
206
|
-
for (const v of await samples("ip_address", "string")) {
|
|
207
|
-
expect(v).toMatch(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe("IDs", () => {
|
|
213
|
-
it("uuid matches UUID pattern", async () => {
|
|
214
|
-
for (const v of await samples("uuid", "string")) {
|
|
215
|
-
expect(v).toMatch(
|
|
216
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("userId (suffix rule) generates UUID", async () => {
|
|
222
|
-
for (const v of await samples("userId", "string")) {
|
|
223
|
-
expect(v).toMatch(
|
|
224
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("order_id (suffix rule) generates UUID", async () => {
|
|
230
|
-
for (const v of await samples("order_id", "string")) {
|
|
231
|
-
expect(v).toMatch(
|
|
232
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe("Text/Content", () => {
|
|
239
|
-
it("description is a paragraph (> 20 chars)", async () => {
|
|
240
|
-
for (const v of await samples("description", "string")) {
|
|
241
|
-
expect((v as string).length).toBeGreaterThan(20);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("title is sentence-length", async () => {
|
|
246
|
-
for (const v of await samples("title", "string")) {
|
|
247
|
-
expect((v as string).length).toBeGreaterThan(5);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe("Business", () => {
|
|
253
|
-
it("company generates a non-empty string", async () => {
|
|
254
|
-
for (const v of await samples("company", "string")) {
|
|
255
|
-
expect((v as string).length).toBeGreaterThan(0);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
describe("Date/Time", () => {
|
|
261
|
-
it("createdAt generates parseable dates", async () => {
|
|
262
|
-
for (const v of await samples("createdAt", "string")) {
|
|
263
|
-
expect(Date.parse(v as string)).not.toBeNaN();
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("updatedAt generates parseable dates", async () => {
|
|
268
|
-
for (const v of await samples("updatedAt", "string")) {
|
|
269
|
-
expect(Date.parse(v as string)).not.toBeNaN();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("expires_at generates parseable dates", async () => {
|
|
274
|
-
for (const v of await samples("expires_at", "string")) {
|
|
275
|
-
expect(Date.parse(v as string)).not.toBeNaN();
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe("Boolean weighting", () => {
|
|
281
|
-
it("isActive is mostly true (~90%)", async () => {
|
|
282
|
-
const values = (await samples("isActive", "boolean", 200)) as boolean[];
|
|
283
|
-
const trueCount = values.filter(Boolean).length;
|
|
284
|
-
// With p=0.9 and n=200, expect 160-200 trues
|
|
285
|
-
expect(trueCount).toBeGreaterThan(140);
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("isDeleted is mostly false (~5% true)", async () => {
|
|
289
|
-
const values = (await samples("isDeleted", "boolean", 200)) as boolean[];
|
|
290
|
-
const trueCount = values.filter(Boolean).length;
|
|
291
|
-
// With p=0.05 and n=200, expect 0-30 trues
|
|
292
|
-
expect(trueCount).toBeLessThan(40);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("isVerified is mostly true (~80%)", async () => {
|
|
296
|
-
const values = (await samples("isVerified", "boolean", 200)) as boolean[];
|
|
297
|
-
const trueCount = values.filter(Boolean).length;
|
|
298
|
-
expect(trueCount).toBeGreaterThan(120);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe("Nullable post-processing", () => {
|
|
303
|
-
it("schmockNullable fields are null ~5% of the time", async () => {
|
|
304
|
-
const schema = {
|
|
305
|
-
type: "object" as const,
|
|
306
|
-
properties: {
|
|
307
|
-
value: {
|
|
308
|
-
type: "string" as const,
|
|
309
|
-
schmockNullable: true,
|
|
310
|
-
} as any,
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
let nullCount = 0;
|
|
315
|
-
const total = 500;
|
|
316
|
-
for (let i = 0; i < total; i++) {
|
|
317
|
-
const result = await generateFromSchema({ schema });
|
|
318
|
-
if (result.value === null) nullCount++;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// With p=0.05 and n=500, expect 10-45 nulls (generous bounds)
|
|
322
|
-
expect(nullCount).toBeGreaterThan(5);
|
|
323
|
-
expect(nullCount).toBeLessThan(60);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe("Composition recursion", () => {
|
|
328
|
-
it("enhances fields inside allOf branches", async () => {
|
|
329
|
-
const schema = {
|
|
330
|
-
type: "object" as const,
|
|
331
|
-
properties: {
|
|
332
|
-
data: {
|
|
333
|
-
allOf: [
|
|
334
|
-
{
|
|
335
|
-
type: "object" as const,
|
|
336
|
-
properties: {
|
|
337
|
-
email: { type: "string" as const },
|
|
338
|
-
city: { type: "string" as const },
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
],
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
const result = await generateFromSchema({ schema });
|
|
347
|
-
expect(result.data.email).toMatch(/@/);
|
|
348
|
-
expect(result.data.city).toMatch(/^[A-Z]/);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
});
|