@schmock/faker 2.3.0 → 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.
- 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 +17 -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
package/src/integration.test.ts
DELETED
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
import { schmock } from "@schmock/core";
|
|
2
|
-
import type { JSONSchema7 } from "json-schema";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { fakerPlugin, generateFromSchema } from "./index";
|
|
5
|
-
import { generate, schemas, validators } from "./test-utils";
|
|
6
|
-
|
|
7
|
-
describe("Schema Generator Integration Tests", () => {
|
|
8
|
-
describe("End-to-End Scenarios", () => {
|
|
9
|
-
it("generates complete mock API response with relationships", async () => {
|
|
10
|
-
const schema: JSONSchema7 = {
|
|
11
|
-
type: "object",
|
|
12
|
-
properties: {
|
|
13
|
-
user: {
|
|
14
|
-
type: "object",
|
|
15
|
-
properties: {
|
|
16
|
-
id: { type: "string", format: "uuid" },
|
|
17
|
-
username: { type: "string", pattern: "^[a-z0-9_]{3,20}$" },
|
|
18
|
-
email: { type: "string", format: "email" },
|
|
19
|
-
profile: {
|
|
20
|
-
type: "object",
|
|
21
|
-
properties: {
|
|
22
|
-
firstName: { type: "string" },
|
|
23
|
-
lastName: { type: "string" },
|
|
24
|
-
avatar: { type: "string", format: "uri" },
|
|
25
|
-
bio: { type: "string", maxLength: 500 },
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
posts: {
|
|
29
|
-
type: "array",
|
|
30
|
-
items: {
|
|
31
|
-
type: "object",
|
|
32
|
-
properties: {
|
|
33
|
-
id: { type: "string", format: "uuid" },
|
|
34
|
-
title: { type: "string", minLength: 1, maxLength: 200 },
|
|
35
|
-
content: { type: "string" },
|
|
36
|
-
tags: {
|
|
37
|
-
type: "array",
|
|
38
|
-
items: { type: "string" },
|
|
39
|
-
maxItems: 5,
|
|
40
|
-
},
|
|
41
|
-
publishedAt: { type: "string", format: "date-time" },
|
|
42
|
-
comments: {
|
|
43
|
-
type: "array",
|
|
44
|
-
items: {
|
|
45
|
-
type: "object",
|
|
46
|
-
properties: {
|
|
47
|
-
id: { type: "string", format: "uuid" },
|
|
48
|
-
author: { type: "string" },
|
|
49
|
-
text: { type: "string" },
|
|
50
|
-
createdAt: { type: "string", format: "date-time" },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const result = await generateFromSchema({ schema });
|
|
63
|
-
|
|
64
|
-
// Verify complete structure
|
|
65
|
-
expect(result.user).toBeDefined();
|
|
66
|
-
expect(validators.appearsToBeFromCategory([result.user.id], "uuid")).toBe(
|
|
67
|
-
true,
|
|
68
|
-
);
|
|
69
|
-
expect(result.user.username).toMatch(/^[a-z0-9_]{3,20}$/);
|
|
70
|
-
expect(
|
|
71
|
-
validators.appearsToBeFromCategory([result.user.email], "email"),
|
|
72
|
-
).toBe(true);
|
|
73
|
-
|
|
74
|
-
expect(result.user.profile).toBeDefined();
|
|
75
|
-
expect(typeof result.user.profile.firstName).toBe("string");
|
|
76
|
-
expect(result.user.profile.firstName.length).toBeGreaterThan(0);
|
|
77
|
-
|
|
78
|
-
if (result.user.posts && result.user.posts.length > 0) {
|
|
79
|
-
const post = result.user.posts[0];
|
|
80
|
-
expect(validators.appearsToBeFromCategory([post.id], "uuid")).toBe(
|
|
81
|
-
true,
|
|
82
|
-
);
|
|
83
|
-
expect(post.title.length).toBeGreaterThan(0);
|
|
84
|
-
expect(
|
|
85
|
-
validators.appearsToBeFromCategory([post.publishedAt], "date"),
|
|
86
|
-
).toBe(true);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("generates consistent data across related fields", async () => {
|
|
91
|
-
const plugin = fakerPlugin({
|
|
92
|
-
schema: {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: {
|
|
95
|
-
order: {
|
|
96
|
-
type: "object",
|
|
97
|
-
properties: {
|
|
98
|
-
id: { type: "string", format: "uuid" },
|
|
99
|
-
customerId: { type: "string", format: "uuid" },
|
|
100
|
-
items: {
|
|
101
|
-
type: "array",
|
|
102
|
-
items: {
|
|
103
|
-
type: "object",
|
|
104
|
-
properties: {
|
|
105
|
-
productId: { type: "string", format: "uuid" },
|
|
106
|
-
quantity: { type: "integer", minimum: 1 },
|
|
107
|
-
unitPrice: { type: "number", minimum: 0 },
|
|
108
|
-
totalPrice: { type: "number", minimum: 0 },
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
subtotal: { type: "number", minimum: 0 },
|
|
113
|
-
tax: { type: "number", minimum: 0 },
|
|
114
|
-
total: { type: "number", minimum: 0 },
|
|
115
|
-
createdAt: { type: "string", format: "date-time" },
|
|
116
|
-
updatedAt: { type: "string", format: "date-time" },
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
overrides: {
|
|
122
|
-
"order.customerId": "{{params.customerId}}",
|
|
123
|
-
"order.createdAt": "{{state.timestamp}}",
|
|
124
|
-
"order.updatedAt": "{{state.timestamp}}",
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const context = {
|
|
129
|
-
method: "GET",
|
|
130
|
-
path: "/api/orders/123",
|
|
131
|
-
params: { orderId: "123", customerId: "customer-456" },
|
|
132
|
-
query: {},
|
|
133
|
-
state: new Map(),
|
|
134
|
-
routeState: { timestamp: "2024-01-01T10:00:00Z" },
|
|
135
|
-
headers: {},
|
|
136
|
-
body: null,
|
|
137
|
-
route: {},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const result = await plugin.process(context);
|
|
141
|
-
|
|
142
|
-
expect(result.response.order.customerId).toBe("customer-456");
|
|
143
|
-
expect(result.response.order.createdAt).toBe("2024-01-01T10:00:00Z");
|
|
144
|
-
expect(result.response.order.updatedAt).toBe("2024-01-01T10:00:00Z");
|
|
145
|
-
|
|
146
|
-
// All IDs should be valid UUIDs
|
|
147
|
-
expect(
|
|
148
|
-
validators.appearsToBeFromCategory([result.response.order.id], "uuid"),
|
|
149
|
-
).toBe(true);
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
result.response.order.items &&
|
|
153
|
-
result.response.order.items.length > 0
|
|
154
|
-
) {
|
|
155
|
-
result.response.order.items.forEach((item) => {
|
|
156
|
-
expect(
|
|
157
|
-
validators.appearsToBeFromCategory([item.productId], "uuid"),
|
|
158
|
-
).toBe(true);
|
|
159
|
-
expect(item.quantity).toBeGreaterThanOrEqual(1);
|
|
160
|
-
expect(item.unitPrice).toBeGreaterThanOrEqual(0);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe("Cross-Package Integration", () => {
|
|
167
|
-
it("works with complex nested schemas from multiple sources", async () => {
|
|
168
|
-
const addressSchema: JSONSchema7 = {
|
|
169
|
-
type: "object",
|
|
170
|
-
properties: {
|
|
171
|
-
street: { type: "string" },
|
|
172
|
-
city: { type: "string" },
|
|
173
|
-
state: { type: "string", pattern: "^[A-Z]{2}$" },
|
|
174
|
-
zipCode: { type: "string", pattern: "^\\d{5}(-\\d{4})?$" },
|
|
175
|
-
country: { type: "string", enum: ["US", "CA", "MX"] },
|
|
176
|
-
},
|
|
177
|
-
required: ["street", "city", "state", "zipCode", "country"],
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const customerSchema: JSONSchema7 = {
|
|
181
|
-
type: "object",
|
|
182
|
-
properties: {
|
|
183
|
-
id: { type: "string", format: "uuid" },
|
|
184
|
-
name: { type: "string" },
|
|
185
|
-
email: { type: "string", format: "email" },
|
|
186
|
-
phone: { type: "string" },
|
|
187
|
-
billingAddress: addressSchema,
|
|
188
|
-
shippingAddress: addressSchema,
|
|
189
|
-
sameAsBilling: { type: "boolean" },
|
|
190
|
-
},
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const result = await generateFromSchema({ schema: customerSchema });
|
|
194
|
-
|
|
195
|
-
// Both addresses should be valid structures
|
|
196
|
-
expect(result.billingAddress.street).toBeDefined();
|
|
197
|
-
expect(typeof result.billingAddress.street).toBe("string");
|
|
198
|
-
expect(result.billingAddress.state).toMatch(/^[A-Z]{2}$/);
|
|
199
|
-
expect(result.billingAddress.zipCode).toMatch(/^\d{5}(-\d{4})?$/);
|
|
200
|
-
|
|
201
|
-
expect(result.shippingAddress.street).toBeDefined();
|
|
202
|
-
expect(typeof result.shippingAddress.street).toBe("string");
|
|
203
|
-
expect(["US", "CA", "MX"]).toContain(result.shippingAddress.country);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("handles schema composition with allOf, anyOf, oneOf", async () => {
|
|
207
|
-
const baseSchema: JSONSchema7 = {
|
|
208
|
-
type: "object",
|
|
209
|
-
properties: {
|
|
210
|
-
id: { type: "string", format: "uuid" },
|
|
211
|
-
createdAt: { type: "string", format: "date-time" },
|
|
212
|
-
},
|
|
213
|
-
required: ["id", "createdAt"],
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const documentSchema: JSONSchema7 = {
|
|
217
|
-
allOf: [
|
|
218
|
-
baseSchema,
|
|
219
|
-
{
|
|
220
|
-
type: "object",
|
|
221
|
-
properties: {
|
|
222
|
-
title: { type: "string" },
|
|
223
|
-
content: {
|
|
224
|
-
anyOf: [
|
|
225
|
-
{ type: "string" },
|
|
226
|
-
{
|
|
227
|
-
type: "object",
|
|
228
|
-
properties: {
|
|
229
|
-
html: { type: "string" },
|
|
230
|
-
markdown: { type: "string" },
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
],
|
|
234
|
-
},
|
|
235
|
-
status: {
|
|
236
|
-
oneOf: [
|
|
237
|
-
{ const: "draft" },
|
|
238
|
-
{ const: "published" },
|
|
239
|
-
{ const: "archived" },
|
|
240
|
-
],
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
required: ["title", "content", "status"],
|
|
244
|
-
},
|
|
245
|
-
],
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const results = await generate.samples<any>(documentSchema, 10);
|
|
249
|
-
|
|
250
|
-
results.forEach((result) => {
|
|
251
|
-
// Should have base properties
|
|
252
|
-
expect(validators.appearsToBeFromCategory([result.id], "uuid")).toBe(
|
|
253
|
-
true,
|
|
254
|
-
);
|
|
255
|
-
expect(
|
|
256
|
-
validators.appearsToBeFromCategory([result.createdAt], "date"),
|
|
257
|
-
).toBe(true);
|
|
258
|
-
|
|
259
|
-
// Should have document properties
|
|
260
|
-
expect(result).toHaveProperty("title");
|
|
261
|
-
expect(result).toHaveProperty("content");
|
|
262
|
-
expect(["draft", "published", "archived"]).toContain(result.status);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe("State Management Integration", () => {
|
|
268
|
-
it("maintains state across multiple generations", async () => {
|
|
269
|
-
const _callCount = 0;
|
|
270
|
-
const plugin = fakerPlugin({
|
|
271
|
-
schema: {
|
|
272
|
-
type: "object",
|
|
273
|
-
properties: {
|
|
274
|
-
requestNumber: { type: "integer" },
|
|
275
|
-
sessionId: { type: "string" },
|
|
276
|
-
timestamp: { type: "string", format: "date-time" },
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
overrides: {
|
|
280
|
-
requestNumber: "{{state.requestCount}}",
|
|
281
|
-
sessionId: "{{state.sessionId}}",
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
const baseContext = {
|
|
286
|
-
method: "GET",
|
|
287
|
-
path: "/api/track",
|
|
288
|
-
params: {},
|
|
289
|
-
query: {},
|
|
290
|
-
headers: {},
|
|
291
|
-
body: null,
|
|
292
|
-
route: {},
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
// Simulate multiple requests with evolving state
|
|
296
|
-
const results: any[] = [];
|
|
297
|
-
for (let i = 0; i < 5; i++) {
|
|
298
|
-
const context = {
|
|
299
|
-
...baseContext,
|
|
300
|
-
state: new Map(),
|
|
301
|
-
routeState: {
|
|
302
|
-
requestCount: i + 1,
|
|
303
|
-
sessionId: "session-123",
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
const r = await plugin.process(context);
|
|
307
|
-
results.push(r.response);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Request numbers should increment
|
|
311
|
-
results.forEach((result, i) => {
|
|
312
|
-
expect(result.requestNumber).toBe(i + 1);
|
|
313
|
-
expect(result.sessionId).toBe("session-123");
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it("generates stateful mock data", async () => {
|
|
318
|
-
const cartSchema: JSONSchema7 = {
|
|
319
|
-
type: "object",
|
|
320
|
-
properties: {
|
|
321
|
-
id: { type: "string", format: "uuid" },
|
|
322
|
-
userId: { type: "string", format: "uuid" },
|
|
323
|
-
items: {
|
|
324
|
-
type: "array",
|
|
325
|
-
items: {
|
|
326
|
-
type: "object",
|
|
327
|
-
properties: {
|
|
328
|
-
productId: { type: "string", format: "uuid" },
|
|
329
|
-
quantity: { type: "integer", minimum: 1 },
|
|
330
|
-
addedAt: { type: "string", format: "date-time" },
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
lastModified: { type: "string", format: "date-time" },
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
// Generate initial cart
|
|
339
|
-
const cart1 = await generateFromSchema({ schema: cartSchema });
|
|
340
|
-
|
|
341
|
-
// Simulate adding items (would be done through state in real usage)
|
|
342
|
-
const cart2 = await generateFromSchema({
|
|
343
|
-
schema: cartSchema,
|
|
344
|
-
overrides: {
|
|
345
|
-
id: cart1.id, // Keep same cart ID
|
|
346
|
-
userId: cart1.userId, // Keep same user
|
|
347
|
-
lastModified: new Date().toISOString(),
|
|
348
|
-
},
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
expect(cart2.id).toBe(cart1.id);
|
|
352
|
-
expect(cart2.userId).toBe(cart1.userId);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe("Performance at Scale", () => {
|
|
357
|
-
it("generates large datasets efficiently", async () => {
|
|
358
|
-
const schema: JSONSchema7 = {
|
|
359
|
-
type: "array",
|
|
360
|
-
items: {
|
|
361
|
-
type: "object",
|
|
362
|
-
properties: {
|
|
363
|
-
id: { type: "integer" },
|
|
364
|
-
uuid: { type: "string", format: "uuid" },
|
|
365
|
-
name: { type: "string" },
|
|
366
|
-
email: { type: "string", format: "email" },
|
|
367
|
-
active: { type: "boolean" },
|
|
368
|
-
score: { type: "number", minimum: 0, maximum: 100 },
|
|
369
|
-
tags: {
|
|
370
|
-
type: "array",
|
|
371
|
-
items: { type: "string" },
|
|
372
|
-
maxItems: 3,
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const startTime = Date.now();
|
|
379
|
-
const result = await generateFromSchema({ schema, count: 100 });
|
|
380
|
-
const duration = Date.now() - startTime;
|
|
381
|
-
|
|
382
|
-
expect(result).toHaveLength(100);
|
|
383
|
-
expect(duration).toBeLessThan(1000); // Should complete in under 1 second
|
|
384
|
-
|
|
385
|
-
// Verify data quality at scale
|
|
386
|
-
const emails = result.map((item) => item.email);
|
|
387
|
-
const uniqueEmails = new Set(emails);
|
|
388
|
-
expect(uniqueEmails.size / emails.length).toBeGreaterThan(0.9); // High uniqueness
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("handles concurrent plugin processing", async () => {
|
|
392
|
-
const plugin = fakerPlugin({
|
|
393
|
-
schema: schemas.complex.user(),
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
const context = {
|
|
397
|
-
method: "GET",
|
|
398
|
-
path: "/api/user",
|
|
399
|
-
params: {},
|
|
400
|
-
query: {},
|
|
401
|
-
state: {},
|
|
402
|
-
headers: {},
|
|
403
|
-
body: null,
|
|
404
|
-
route: {},
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// Simulate concurrent requests
|
|
408
|
-
const promises = Array.from({ length: 20 }, () =>
|
|
409
|
-
plugin.process(context),
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
const results = await Promise.all(promises);
|
|
413
|
-
|
|
414
|
-
// All should complete successfully
|
|
415
|
-
expect(results).toHaveLength(20);
|
|
416
|
-
|
|
417
|
-
// Each should generate unique data
|
|
418
|
-
const emails = results.map((r) => r.response.email);
|
|
419
|
-
const uniqueEmails = new Set(emails);
|
|
420
|
-
expect(uniqueEmails.size).toBeGreaterThan(15); // Most should be unique
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
describe("Error Recovery and Resilience", () => {
|
|
425
|
-
it("recovers from validation errors gracefully", async () => {
|
|
426
|
-
// First, try invalid schema
|
|
427
|
-
try {
|
|
428
|
-
await generateFromSchema({
|
|
429
|
-
schema: {
|
|
430
|
-
type: "object",
|
|
431
|
-
properties: {
|
|
432
|
-
bad: { type: "invalid" as any },
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
} catch (_e) {
|
|
437
|
-
// Expected
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Should still work with valid schema
|
|
441
|
-
const result = await generateFromSchema({
|
|
442
|
-
schema: schemas.simple.object({ id: schemas.simple.number() }),
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
expect(result).toHaveProperty("id");
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it("handles partial template failures", async () => {
|
|
449
|
-
const plugin = fakerPlugin({
|
|
450
|
-
schema: {
|
|
451
|
-
type: "object",
|
|
452
|
-
properties: {
|
|
453
|
-
field1: { type: "string" },
|
|
454
|
-
field2: { type: "string" },
|
|
455
|
-
field3: { type: "string" },
|
|
456
|
-
field4: { type: "string" },
|
|
457
|
-
},
|
|
458
|
-
},
|
|
459
|
-
overrides: {
|
|
460
|
-
field1: "{{params.value1}}",
|
|
461
|
-
field2: "{{state.missing.nested.value}}",
|
|
462
|
-
field3: "static",
|
|
463
|
-
field4: "{{query.value4}}",
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
const result = await plugin.process({
|
|
468
|
-
method: "GET",
|
|
469
|
-
path: "/test",
|
|
470
|
-
params: { value1: "success" },
|
|
471
|
-
query: { value4: "works" },
|
|
472
|
-
state: new Map(),
|
|
473
|
-
routeState: {},
|
|
474
|
-
headers: {},
|
|
475
|
-
body: null,
|
|
476
|
-
route: {},
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// Successful templates should work
|
|
480
|
-
expect(result.response.field1).toBe("success");
|
|
481
|
-
expect(result.response.field3).toBe("static");
|
|
482
|
-
expect(result.response.field4).toBe("works");
|
|
483
|
-
|
|
484
|
-
// Failed template returns original
|
|
485
|
-
expect(result.response.field2).toBe("{{state.missing.nested.value}}");
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
describe("Advanced Integration Patterns", () => {
|
|
490
|
-
it("supports pagination patterns", async () => {
|
|
491
|
-
const paginatedSchema: JSONSchema7 = {
|
|
492
|
-
type: "object",
|
|
493
|
-
properties: {
|
|
494
|
-
items: {
|
|
495
|
-
type: "array",
|
|
496
|
-
items: {
|
|
497
|
-
type: "object",
|
|
498
|
-
properties: {
|
|
499
|
-
id: { type: "string", format: "uuid" },
|
|
500
|
-
name: { type: "string" },
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
pagination: {
|
|
505
|
-
type: "object",
|
|
506
|
-
properties: {
|
|
507
|
-
page: { type: "integer", minimum: 1 },
|
|
508
|
-
pageSize: { type: "integer", minimum: 1, maximum: 100 },
|
|
509
|
-
total: { type: "integer", minimum: 0 },
|
|
510
|
-
hasNext: { type: "boolean" },
|
|
511
|
-
hasPrev: { type: "boolean" },
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
const plugin = fakerPlugin({
|
|
518
|
-
schema: paginatedSchema,
|
|
519
|
-
overrides: {
|
|
520
|
-
"pagination.page": "{{query.page}}",
|
|
521
|
-
"pagination.pageSize": "{{query.limit}}",
|
|
522
|
-
"pagination.total": 150,
|
|
523
|
-
"pagination.hasNext": "{{state.hasNext}}",
|
|
524
|
-
"pagination.hasPrev": "{{state.hasPrev}}",
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
const result = await plugin.process({
|
|
529
|
-
method: "GET",
|
|
530
|
-
path: "/api/items",
|
|
531
|
-
params: {},
|
|
532
|
-
query: { page: "2", limit: "20" },
|
|
533
|
-
state: new Map(),
|
|
534
|
-
routeState: { hasNext: true, hasPrev: true },
|
|
535
|
-
headers: {},
|
|
536
|
-
body: null,
|
|
537
|
-
route: {},
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
expect(result.response.pagination.page).toBe("2");
|
|
541
|
-
expect(result.response.pagination.pageSize).toBe("20");
|
|
542
|
-
expect(result.response.pagination.total).toBe(150);
|
|
543
|
-
expect(result.response.pagination.hasNext).toBe(true);
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it("supports versioned API responses", async () => {
|
|
547
|
-
const v1Schema: JSONSchema7 = {
|
|
548
|
-
type: "object",
|
|
549
|
-
properties: {
|
|
550
|
-
version: { const: "1.0" },
|
|
551
|
-
data: {
|
|
552
|
-
type: "object",
|
|
553
|
-
properties: {
|
|
554
|
-
id: { type: "number" },
|
|
555
|
-
name: { type: "string" },
|
|
556
|
-
},
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
const v2Schema: JSONSchema7 = {
|
|
562
|
-
type: "object",
|
|
563
|
-
properties: {
|
|
564
|
-
version: { const: "2.0" },
|
|
565
|
-
data: {
|
|
566
|
-
type: "object",
|
|
567
|
-
properties: {
|
|
568
|
-
id: { type: "string", format: "uuid" },
|
|
569
|
-
name: { type: "string" },
|
|
570
|
-
metadata: { type: "object" },
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
const v1Result = await generateFromSchema({ schema: v1Schema });
|
|
577
|
-
const v2Result = await generateFromSchema({ schema: v2Schema });
|
|
578
|
-
|
|
579
|
-
expect(v1Result.version).toBe("1.0");
|
|
580
|
-
expect(typeof v1Result.data.id).toBe("number");
|
|
581
|
-
|
|
582
|
-
expect(v2Result.version).toBe("2.0");
|
|
583
|
-
expect(
|
|
584
|
-
validators.appearsToBeFromCategory([v2Result.data.id], "uuid"),
|
|
585
|
-
).toBe(true);
|
|
586
|
-
expect(v2Result.data).toHaveProperty("metadata");
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
describe("Schmock Handle Integration", () => {
|
|
591
|
-
it("resolves state overrides when using schmock.handle() with global state", async () => {
|
|
592
|
-
// This test verifies that state-driven template overrides work correctly
|
|
593
|
-
// when the schema plugin is used through schmock.handle() with global state
|
|
594
|
-
const userState = {
|
|
595
|
-
currentUser: {
|
|
596
|
-
id: "user-123",
|
|
597
|
-
name: "Test User",
|
|
598
|
-
},
|
|
599
|
-
settings: {
|
|
600
|
-
theme: "dark",
|
|
601
|
-
},
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
const mock = schmock({ state: userState });
|
|
605
|
-
|
|
606
|
-
const responseSchema: JSONSchema7 = {
|
|
607
|
-
type: "object",
|
|
608
|
-
properties: {
|
|
609
|
-
userId: { type: "string" },
|
|
610
|
-
userName: { type: "string" },
|
|
611
|
-
theme: { type: "string" },
|
|
612
|
-
timestamp: { type: "string" },
|
|
613
|
-
},
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
mock("GET /profile", null).pipe(
|
|
617
|
-
fakerPlugin({
|
|
618
|
-
schema: responseSchema,
|
|
619
|
-
overrides: {
|
|
620
|
-
userId: "{{state.currentUser.id}}",
|
|
621
|
-
userName: "{{state.currentUser.name}}",
|
|
622
|
-
theme: "{{state.settings.theme}}",
|
|
623
|
-
},
|
|
624
|
-
}),
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
const response = await mock.handle("GET", "/profile");
|
|
628
|
-
|
|
629
|
-
expect(response.body.userId).toBe("user-123");
|
|
630
|
-
expect(response.body.userName).toBe("Test User");
|
|
631
|
-
expect(response.body.theme).toBe("dark");
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
});
|
package/src/jsf-config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsf-config.d.ts","sourceRoot":"","sources":["jsf-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,GAAG,MAAM,mBAAmB,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,SAMhD;AAoBD,wBAAgB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,CAuBhD"}
|
package/src/jsf-config.test.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema7 } from "json-schema";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { normalizeSchemaForJsf } from "./jsf-config.js";
|
|
4
|
-
|
|
5
|
-
const tuple: JSONSchema7 = {
|
|
6
|
-
type: "array",
|
|
7
|
-
items: [{ type: "string" }, { type: "integer" }],
|
|
8
|
-
minItems: 2,
|
|
9
|
-
maxItems: 2,
|
|
10
|
-
additionalItems: false,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function expectNormalizedTuple(value: unknown): void {
|
|
14
|
-
expect(value).toMatchObject({
|
|
15
|
-
type: "array",
|
|
16
|
-
prefixItems: [{ type: "string" }, { type: "integer" }],
|
|
17
|
-
items: false,
|
|
18
|
-
});
|
|
19
|
-
expect(value).not.toHaveProperty("additionalItems");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("normalizeSchemaForJsf", () => {
|
|
23
|
-
it("recurses through Draft-7 schema-bearing keywords", () => {
|
|
24
|
-
const normalized = normalizeSchemaForJsf({
|
|
25
|
-
type: "object",
|
|
26
|
-
definitions: { legacy: tuple },
|
|
27
|
-
$defs: { modern: tuple },
|
|
28
|
-
patternProperties: { "^tuple$": tuple },
|
|
29
|
-
contains: tuple,
|
|
30
|
-
not: tuple,
|
|
31
|
-
if: tuple,
|
|
32
|
-
// biome-ignore lint/suspicious/noThenProperty: JSON Schema's conditional keyword is named "then"
|
|
33
|
-
then: tuple,
|
|
34
|
-
else: tuple,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const record: Record<string, unknown> = normalized;
|
|
38
|
-
const definitions = record.definitions;
|
|
39
|
-
const defs = record.$defs;
|
|
40
|
-
const patternProperties = record.patternProperties;
|
|
41
|
-
|
|
42
|
-
expect(definitions).toBeTypeOf("object");
|
|
43
|
-
expect(defs).toBeTypeOf("object");
|
|
44
|
-
expect(patternProperties).toBeTypeOf("object");
|
|
45
|
-
if (
|
|
46
|
-
definitions === null ||
|
|
47
|
-
typeof definitions !== "object" ||
|
|
48
|
-
defs === null ||
|
|
49
|
-
typeof defs !== "object" ||
|
|
50
|
-
patternProperties === null ||
|
|
51
|
-
typeof patternProperties !== "object"
|
|
52
|
-
) {
|
|
53
|
-
throw new Error("Expected normalized schema maps");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
expectNormalizedTuple(Reflect.get(definitions, "legacy"));
|
|
57
|
-
expectNormalizedTuple(Reflect.get(defs, "modern"));
|
|
58
|
-
expectNormalizedTuple(Reflect.get(patternProperties, "^tuple$"));
|
|
59
|
-
expectNormalizedTuple(record.contains);
|
|
60
|
-
expectNormalizedTuple(record.not);
|
|
61
|
-
expectNormalizedTuple(record.if);
|
|
62
|
-
expectNormalizedTuple(record.then);
|
|
63
|
-
expectNormalizedTuple(record.else);
|
|
64
|
-
});
|
|
65
|
-
});
|