@schmock/faker 2.0.0 → 2.0.2
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/dist/index.d.ts +3 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/overrides.d.ts +1 -1
- package/dist/overrides.d.ts.map +1 -1
- package/dist/overrides.js +24 -27
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +7 -3
- package/package.json +6 -6
- package/src/index.test.ts +2 -1
- package/src/index.ts +23 -36
- package/src/overrides.property.test.ts +172 -0
- package/src/overrides.test.ts +38 -0
- package/src/overrides.ts +46 -55
- package/src/plugin-integration.test.ts +2 -1
- package/src/post-process.test.ts +152 -0
- package/src/schema-enhancement.test.ts +216 -0
- package/src/steps/deterministic-seeds.steps.ts +0 -21
- package/src/steps/faker-plugin.steps.ts +1 -100
- package/src/test-utils.ts +7 -3
- package/src/validation.ts +2 -2
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { enhanceSchemaWithSmartMapping } from "./schema-enhancement";
|
|
4
|
+
|
|
5
|
+
describe("enhanceSchemaWithSmartMapping", () => {
|
|
6
|
+
it("does NOT mutate the original schema", () => {
|
|
7
|
+
const original: JSONSchema7 = {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
email: { type: "string" },
|
|
11
|
+
firstName: { type: "string" },
|
|
12
|
+
createdAt: { type: "string" },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Deep snapshot before enhancement
|
|
17
|
+
const snapshot = JSON.parse(JSON.stringify(original));
|
|
18
|
+
|
|
19
|
+
enhanceSchemaWithSmartMapping(original);
|
|
20
|
+
|
|
21
|
+
// Original should be unchanged
|
|
22
|
+
expect(original).toEqual(snapshot);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("enhancement of object with properties adds faker methods to appropriate fields", () => {
|
|
26
|
+
const schema: JSONSchema7 = {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
email: { type: "string" },
|
|
30
|
+
firstName: { type: "string" },
|
|
31
|
+
lastName: { type: "string" },
|
|
32
|
+
phone: { type: "string" },
|
|
33
|
+
uuid: { type: "string" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema) as Record<
|
|
38
|
+
string,
|
|
39
|
+
unknown
|
|
40
|
+
>;
|
|
41
|
+
const props = enhanced.properties as Record<
|
|
42
|
+
string,
|
|
43
|
+
JSONSchema7 & { faker?: string }
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
// Well-known fields should receive faker methods
|
|
47
|
+
expect(props.email.faker).toBeDefined();
|
|
48
|
+
expect(props.firstName.faker).toBeDefined();
|
|
49
|
+
expect(props.lastName.faker).toBeDefined();
|
|
50
|
+
expect(props.phone.faker).toBeDefined();
|
|
51
|
+
expect(props.uuid.faker).toBeDefined();
|
|
52
|
+
|
|
53
|
+
// Verify they are plausible faker method strings (namespace.method format)
|
|
54
|
+
for (const field of ["email", "firstName", "lastName", "phone", "uuid"]) {
|
|
55
|
+
const fakerMethod = props[field].faker as string;
|
|
56
|
+
expect(fakerMethod).toContain(".");
|
|
57
|
+
expect(fakerMethod.split(".").length).toBeGreaterThanOrEqual(2);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("enhancement recurses into allOf branches", () => {
|
|
62
|
+
const schema: JSONSchema7 = {
|
|
63
|
+
allOf: [
|
|
64
|
+
{
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
email: { type: "string" },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
phone: { type: "string" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
80
|
+
const allOf = enhanced.allOf as Array<
|
|
81
|
+
JSONSchema7 & { properties: Record<string, { faker?: string }> }
|
|
82
|
+
>;
|
|
83
|
+
|
|
84
|
+
expect(allOf).toHaveLength(2);
|
|
85
|
+
expect(allOf[0].properties.email.faker).toBeDefined();
|
|
86
|
+
expect(allOf[1].properties.phone.faker).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("enhancement recurses into anyOf branches", () => {
|
|
90
|
+
const schema: JSONSchema7 = {
|
|
91
|
+
anyOf: [
|
|
92
|
+
{
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
email: { type: "string" },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
102
|
+
const anyOf = enhanced.anyOf as Array<
|
|
103
|
+
JSONSchema7 & { properties: Record<string, { faker?: string }> }
|
|
104
|
+
>;
|
|
105
|
+
|
|
106
|
+
expect(anyOf).toHaveLength(1);
|
|
107
|
+
expect(anyOf[0].properties.email.faker).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("enhancement recurses into oneOf branches", () => {
|
|
111
|
+
const schema: JSONSchema7 = {
|
|
112
|
+
oneOf: [
|
|
113
|
+
{
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
firstName: { type: "string" },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
123
|
+
const oneOf = enhanced.oneOf as Array<
|
|
124
|
+
JSONSchema7 & { properties: Record<string, { faker?: string }> }
|
|
125
|
+
>;
|
|
126
|
+
|
|
127
|
+
expect(oneOf).toHaveLength(1);
|
|
128
|
+
expect(oneOf[0].properties.firstName.faker).toBeDefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("enhancement recurses into array items", () => {
|
|
132
|
+
const schema: JSONSchema7 = {
|
|
133
|
+
type: "array",
|
|
134
|
+
items: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
email: { type: "string" },
|
|
138
|
+
createdAt: { type: "string" },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
144
|
+
const items = enhanced.items as JSONSchema7 & {
|
|
145
|
+
properties: Record<string, { faker?: string }>;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
expect(items.properties.email.faker).toBeDefined();
|
|
149
|
+
expect(items.properties.createdAt.faker).toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("fields with existing faker property are not overridden", () => {
|
|
153
|
+
const schema: JSONSchema7 = {
|
|
154
|
+
type: "object",
|
|
155
|
+
properties: {
|
|
156
|
+
email: {
|
|
157
|
+
type: "string",
|
|
158
|
+
faker: "lorem.word",
|
|
159
|
+
} as JSONSchema7 & { faker: string },
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
164
|
+
const props = enhanced.properties as Record<
|
|
165
|
+
string,
|
|
166
|
+
JSONSchema7 & { faker?: string }
|
|
167
|
+
>;
|
|
168
|
+
|
|
169
|
+
// Should keep the explicit faker method, not replace with email generator
|
|
170
|
+
expect(props.email.faker).toBe("lorem.word");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("handles schema without properties gracefully", () => {
|
|
174
|
+
const schema: JSONSchema7 = { type: "string" };
|
|
175
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
176
|
+
expect(enhanced).toEqual({ type: "string" });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("handles null/undefined schema gracefully", () => {
|
|
180
|
+
expect(enhanceSchemaWithSmartMapping(null as unknown as JSONSchema7)).toBe(
|
|
181
|
+
null,
|
|
182
|
+
);
|
|
183
|
+
expect(
|
|
184
|
+
enhanceSchemaWithSmartMapping(undefined as unknown as JSONSchema7),
|
|
185
|
+
).toBe(undefined);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("enhancement recurses into array items when items is a tuple (array)", () => {
|
|
189
|
+
const schema: JSONSchema7 = {
|
|
190
|
+
type: "array",
|
|
191
|
+
items: [
|
|
192
|
+
{
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
email: { type: "string" },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
phone: { type: "string" },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const enhanced = enhanceSchemaWithSmartMapping(schema);
|
|
208
|
+
const items = enhanced.items as Array<
|
|
209
|
+
JSONSchema7 & { properties: Record<string, { faker?: string }> }
|
|
210
|
+
>;
|
|
211
|
+
|
|
212
|
+
expect(Array.isArray(items)).toBe(true);
|
|
213
|
+
expect(items[0].properties.email.faker).toBeDefined();
|
|
214
|
+
expect(items[1].properties.phone.faker).toBeDefined();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -32,25 +32,4 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
Scenario(
|
|
36
|
-
"Different seeds produce different output",
|
|
37
|
-
({ Given, And, When, Then }) => {
|
|
38
|
-
Given("a schema plugin with seed 42", () => {
|
|
39
|
-
// setup done during generation
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
And("a schema plugin with seed 99", () => {
|
|
43
|
-
// setup done during generation
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
When("I generate data from each", async () => {
|
|
47
|
-
result1 = await generateFromSchema({ schema: testSchema, seed: 42 });
|
|
48
|
-
result2 = await generateFromSchema({ schema: testSchema, seed: 99 });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Then("the outputs are different", () => {
|
|
52
|
-
expect(result1).not.toEqual(result2);
|
|
53
|
-
});
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
35
|
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
|
|
2
2
|
import { expect } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import { generateFromSchema } from "../index";
|
|
4
4
|
|
|
5
5
|
const feature = await loadFeature("../../features/faker-plugin.feature");
|
|
6
6
|
|
|
7
7
|
describeFeature(feature, ({ Scenario }) => {
|
|
8
8
|
let generated: any;
|
|
9
|
-
let error: Error | null = null;
|
|
10
9
|
|
|
11
10
|
Scenario(
|
|
12
11
|
"Generate object from simple schema",
|
|
@@ -107,102 +106,4 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
107
106
|
},
|
|
108
107
|
);
|
|
109
108
|
|
|
110
|
-
Scenario(
|
|
111
|
-
"Multiple schema plugin instances do not share state",
|
|
112
|
-
({ Given, When, Then }) => {
|
|
113
|
-
let plugin1: any;
|
|
114
|
-
let plugin2: any;
|
|
115
|
-
let data1: any;
|
|
116
|
-
let data2: any;
|
|
117
|
-
|
|
118
|
-
Given("I create two separate schema plugin instances", () => {
|
|
119
|
-
const schema = {
|
|
120
|
-
type: "object" as const,
|
|
121
|
-
properties: {
|
|
122
|
-
name: { type: "string" as const },
|
|
123
|
-
},
|
|
124
|
-
required: ["name"],
|
|
125
|
-
};
|
|
126
|
-
plugin1 = fakerPlugin({ schema });
|
|
127
|
-
plugin2 = fakerPlugin({ schema });
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
When("I generate data from both instances", async () => {
|
|
131
|
-
const ctx = {
|
|
132
|
-
path: "/test",
|
|
133
|
-
route: {},
|
|
134
|
-
method: "GET" as const,
|
|
135
|
-
params: {},
|
|
136
|
-
query: {},
|
|
137
|
-
headers: {},
|
|
138
|
-
state: new Map(),
|
|
139
|
-
};
|
|
140
|
-
const result1 = await plugin1.process(ctx);
|
|
141
|
-
const result2 = await plugin2.process(ctx);
|
|
142
|
-
data1 = result1.response;
|
|
143
|
-
data2 = result2.response;
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
Then(
|
|
147
|
-
"the data from each instance should be independently generated",
|
|
148
|
-
() => {
|
|
149
|
-
expect(data1).toBeDefined();
|
|
150
|
-
expect(data2).toBeDefined();
|
|
151
|
-
expect(typeof data1.name).toBe("string");
|
|
152
|
-
expect(typeof data2.name).toBe("string");
|
|
153
|
-
},
|
|
154
|
-
);
|
|
155
|
-
},
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
Scenario(
|
|
159
|
-
"Invalid schema is rejected at plugin creation time",
|
|
160
|
-
({ Given, Then }) => {
|
|
161
|
-
Given("I attempt to create a schema plugin with invalid schema", () => {
|
|
162
|
-
error = null;
|
|
163
|
-
try {
|
|
164
|
-
fakerPlugin({ schema: {} as any });
|
|
165
|
-
} catch (e) {
|
|
166
|
-
error = e as Error;
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
Then("it should throw a SchemaValidationError", () => {
|
|
171
|
-
expect(error).not.toBeNull();
|
|
172
|
-
expect(error?.name).toBe("SchemaValidationError");
|
|
173
|
-
});
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
Scenario(
|
|
178
|
-
"Faker method validation checks actual method existence",
|
|
179
|
-
({ Given, Then }) => {
|
|
180
|
-
Given(
|
|
181
|
-
"I attempt to create a schema with faker method {string}",
|
|
182
|
-
(_, fakerMethod: string) => {
|
|
183
|
-
error = null;
|
|
184
|
-
try {
|
|
185
|
-
const schema = {
|
|
186
|
-
type: "object" as const,
|
|
187
|
-
properties: {
|
|
188
|
-
field: { type: "string" as const, faker: fakerMethod },
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
fakerPlugin({ schema: schema as any });
|
|
192
|
-
} catch (e) {
|
|
193
|
-
error = e as Error;
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
Then(
|
|
199
|
-
"it should throw a SchemaValidationError with message {string}",
|
|
200
|
-
(_, message: string) => {
|
|
201
|
-
expect(error).not.toBeNull();
|
|
202
|
-
expect(error?.name).toBe("SchemaValidationError");
|
|
203
|
-
expect(error?.message).toContain(message);
|
|
204
|
-
},
|
|
205
|
-
);
|
|
206
|
-
},
|
|
207
|
-
);
|
|
208
109
|
});
|
package/src/test-utils.ts
CHANGED
|
@@ -119,13 +119,17 @@ export const validators = {
|
|
|
119
119
|
// Generate multiple samples to check for patterns
|
|
120
120
|
const mappedSamples: any[] = [];
|
|
121
121
|
for (let i = 0; i < 10; i++) {
|
|
122
|
-
const result = await generateFromSchema({
|
|
122
|
+
const result = (await generateFromSchema({
|
|
123
|
+
schema: mappedSchema,
|
|
124
|
+
})) as Record<string, unknown>;
|
|
123
125
|
mappedSamples.push(result[fieldName]);
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
const unmappedSamples: any[] = [];
|
|
127
129
|
for (let i = 0; i < 10; i++) {
|
|
128
|
-
const result = await generateFromSchema({
|
|
130
|
+
const result = (await generateFromSchema({
|
|
131
|
+
schema: unmappedSchema,
|
|
132
|
+
})) as Record<string, unknown>;
|
|
129
133
|
unmappedSamples.push(result.unmappedRandomField12345);
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -254,7 +258,7 @@ export const generate = {
|
|
|
254
258
|
): Promise<T[]> => {
|
|
255
259
|
const results: T[] = [];
|
|
256
260
|
for (let i = 0; i < count; i++) {
|
|
257
|
-
results.push(await generateFromSchema({ schema, ...options }));
|
|
261
|
+
results.push((await generateFromSchema({ schema, ...options })) as T);
|
|
258
262
|
}
|
|
259
263
|
return results;
|
|
260
264
|
},
|
package/src/validation.ts
CHANGED
|
@@ -392,10 +392,10 @@ export function validateFakerMethod(fakerMethod: string): void {
|
|
|
392
392
|
validationFaker = createFakerInstance();
|
|
393
393
|
}
|
|
394
394
|
const faker = validationFaker;
|
|
395
|
-
let current:
|
|
395
|
+
let current: unknown = faker;
|
|
396
396
|
for (const part of parts) {
|
|
397
397
|
if (current && typeof current === "object" && part in current) {
|
|
398
|
-
current = current[part];
|
|
398
|
+
current = (current as Record<string, unknown>)[part];
|
|
399
399
|
} else {
|
|
400
400
|
throw new SchemaValidationError(
|
|
401
401
|
"$.faker",
|