@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
@@ -1,172 +0,0 @@
1
- import fc from "fast-check";
2
- import { describe, expect, it } from "vitest";
3
- import { applyOverrides, determineArrayCount } from "./overrides";
4
-
5
- describe("applyOverrides — property-based tests", () => {
6
- it("never adds keys not in data or overrides (shape preservation)", () => {
7
- // Use simple alphanumeric keys to avoid dot-path splitting edge cases,
8
- // since keys containing "." are treated as nested paths by applyOverrides.
9
- const safeKey = fc.stringMatching(/^[a-z][a-zA-Z0-9]{0,9}$/);
10
-
11
- fc.assert(
12
- fc.property(
13
- fc.dictionary(safeKey, fc.jsonValue()),
14
- fc.dictionary(safeKey, fc.jsonValue()),
15
- (data, overrides) => {
16
- const result = applyOverrides(data, overrides) as Record<
17
- string,
18
- unknown
19
- >;
20
-
21
- const allowedKeys = new Set([
22
- ...Object.keys(data),
23
- ...Object.keys(overrides),
24
- ]);
25
- for (const key of Object.keys(result)) {
26
- expect(allowedKeys.has(key)).toBe(true);
27
- }
28
- },
29
- ),
30
- { numRuns: 100 },
31
- );
32
- });
33
-
34
- it("with empty overrides returns structuredClone of data", () => {
35
- fc.assert(
36
- fc.property(fc.dictionary(fc.string(), fc.jsonValue()), (data) => {
37
- const result = applyOverrides(data, {});
38
-
39
- // Should be deep-equal but not the same reference
40
- expect(result).toEqual(data);
41
- if (Object.keys(data).length > 0) {
42
- expect(result).not.toBe(data);
43
- }
44
- }),
45
- { numRuns: 100 },
46
- );
47
- });
48
-
49
- it("processTemplate idempotency: processing an already-resolved template is a no-op", () => {
50
- // Resolved values contain no {{ }} markers, so re-applying overrides
51
- // with the same non-template values should be idempotent
52
- fc.assert(
53
- fc.property(
54
- fc.record({
55
- name: fc.string().filter((s) => !s.includes("{{")),
56
- age: fc.integer({ min: 0, max: 200 }),
57
- }),
58
- (data) => {
59
- const overrides = { name: "Alice", age: 42 };
60
-
61
- const first = applyOverrides(data, overrides);
62
- const second = applyOverrides(
63
- first as Record<string, unknown>,
64
- overrides,
65
- );
66
-
67
- expect(second).toEqual(first);
68
- },
69
- ),
70
- { numRuns: 100 },
71
- );
72
- });
73
-
74
- it("DANGEROUS_KEYS (__proto__, constructor, prototype) are always rejected in setNestedProperty", () => {
75
- const dangerousKeys = ["__proto__", "constructor", "prototype"];
76
-
77
- for (const dangerous of dangerousKeys) {
78
- // Flat key — dangerous key is skipped entirely, so no own property is set
79
- const flatResult = applyOverrides(
80
- { safe: "value" },
81
- { [dangerous]: "injected" },
82
- ) as Record<string, unknown>;
83
- expect(Object.hasOwn(flatResult, dangerous)).toBe(false);
84
-
85
- // Nested path with dangerous key at start — entire path is skipped
86
- const nestedStartResult = applyOverrides(
87
- { safe: "value" },
88
- { [`${dangerous}.nested`]: "injected" },
89
- ) as Record<string, unknown>;
90
- expect(Object.hasOwn(nestedStartResult, dangerous)).toBe(false);
91
-
92
- // Nested path with dangerous key in middle — traversal aborts
93
- const nestedMiddleResult = applyOverrides(
94
- { a: { b: "original" } },
95
- { [`a.${dangerous}.c`]: "injected" },
96
- ) as Record<string, unknown>;
97
- const inner = nestedMiddleResult.a as Record<string, unknown>;
98
- expect(Object.hasOwn(inner, dangerous)).toBe(false);
99
-
100
- // Nested path with dangerous key at end — final key set is blocked
101
- const nestedEndResult = applyOverrides(
102
- { a: { b: "original" } },
103
- { [`a.${dangerous}`]: "injected" },
104
- ) as Record<string, unknown>;
105
- const innerEnd = nestedEndResult.a as Record<string, unknown>;
106
- expect(Object.hasOwn(innerEnd, dangerous)).toBe(false);
107
- }
108
- });
109
- });
110
-
111
- describe("applyOverrides — edge cases", () => {
112
- it("deeply nested path 'a.b.c.d.e' creates intermediate objects", () => {
113
- const data = { existing: "value" };
114
- const result = applyOverrides(data, {
115
- "a.b.c.d.e": "deep",
116
- }) as Record<string, unknown>;
117
-
118
- expect(result.existing).toBe("value");
119
- const a = result.a as Record<string, unknown>;
120
- const b = a.b as Record<string, unknown>;
121
- const c = b.c as Record<string, unknown>;
122
- const d = c.d as Record<string, unknown>;
123
- expect(d.e).toBe("deep");
124
- });
125
-
126
- it("template '{{params.id}}' resolves correctly", () => {
127
- const data = { userId: "placeholder" };
128
- const result = applyOverrides(
129
- data,
130
- { userId: "{{params.id}}" },
131
- { id: "abc-123" },
132
- ) as Record<string, unknown>;
133
-
134
- expect(result.userId).toBe("abc-123");
135
- });
136
-
137
- it("mixed template 'Hello {{params.name}}!' resolves to string", () => {
138
- const data = { greeting: "placeholder" };
139
- const result = applyOverrides(
140
- data,
141
- { greeting: "Hello {{params.name}}!" },
142
- { name: "World" },
143
- ) as Record<string, unknown>;
144
-
145
- expect(result.greeting).toBe("Hello World!");
146
- expect(typeof result.greeting).toBe("string");
147
- });
148
-
149
- it("on non-object data (null) returns data unchanged", () => {
150
- expect(applyOverrides(null, { key: "value" })).toBe(null);
151
- });
152
-
153
- it("on non-object data (string) returns data unchanged", () => {
154
- expect(applyOverrides("hello", { key: "value" })).toBe("hello");
155
- });
156
-
157
- it("on non-object data (number) returns data unchanged", () => {
158
- expect(applyOverrides(42, { key: "value" })).toBe(42);
159
- });
160
- });
161
-
162
- describe("determineArrayCount — edge cases", () => {
163
- it("with explicitCount=0 returns 0", () => {
164
- expect(determineArrayCount({}, 0)).toBe(0);
165
- });
166
-
167
- it("with negative explicitCount returns 0", () => {
168
- expect(determineArrayCount({}, -1)).toBe(0);
169
- expect(determineArrayCount({}, -100)).toBe(0);
170
- expect(determineArrayCount({}, Number.NEGATIVE_INFINITY)).toBe(0);
171
- });
172
- });
@@ -1,38 +0,0 @@
1
- import type { JSONSchema7 } from "json-schema";
2
- import { describe, expect, it } from "vitest";
3
- import { determineArrayCount } from "./overrides";
4
-
5
- describe("determineArrayCount", () => {
6
- it("returns explicit count when provided", () => {
7
- expect(determineArrayCount({}, 5)).toBe(5);
8
- });
9
-
10
- it("returns 0 for negative explicit count", () => {
11
- expect(determineArrayCount({}, -3)).toBe(0);
12
- });
13
-
14
- it("returns value within minItems/maxItems range", () => {
15
- const schema: JSONSchema7 = { minItems: 2, maxItems: 5 };
16
- for (let i = 0; i < 50; i++) {
17
- const count = determineArrayCount(schema);
18
- expect(count).toBeGreaterThanOrEqual(2);
19
- expect(count).toBeLessThanOrEqual(5);
20
- }
21
- });
22
-
23
- it("handles minItems greater than maxItems without producing garbage", () => {
24
- const schema: JSONSchema7 = { minItems: 10, maxItems: 3 };
25
- const count = determineArrayCount(schema);
26
-
27
- // Should not produce negative numbers or unreasonably large values
28
- expect(count).toBeGreaterThanOrEqual(0);
29
- expect(Number.isFinite(count)).toBe(true);
30
- expect(Number.isNaN(count)).toBe(false);
31
- });
32
-
33
- it("handles minItems equal to maxItems", () => {
34
- const schema: JSONSchema7 = { minItems: 5, maxItems: 5 };
35
- const count = determineArrayCount(schema);
36
- expect(count).toBe(5);
37
- });
38
- });
package/src/overrides.ts DELETED
@@ -1,176 +0,0 @@
1
- import type { JSONSchema7 } from "json-schema";
2
- import { DEFAULT_ARRAY_COUNT } from "./constants.js";
3
-
4
- const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
5
-
6
- /**
7
- * Determine number of items to generate for array schema
8
- * Prefers explicit count, then schema minItems/maxItems, with sane defaults
9
- * @param schema - Array schema with optional minItems/maxItems
10
- * @param explicitCount - Explicit count override from plugin options
11
- * @returns Number of array items to generate
12
- */
13
- export function determineArrayCount(
14
- schema: JSONSchema7,
15
- explicitCount?: number,
16
- random: () => number = Math.random,
17
- ): number {
18
- if (explicitCount !== undefined) {
19
- // Handle negative or invalid counts
20
- if (explicitCount < 0) {
21
- return 0;
22
- }
23
- return explicitCount;
24
- }
25
-
26
- if (schema.minItems !== undefined && schema.maxItems !== undefined) {
27
- const min = schema.minItems;
28
- const max = Math.max(min, schema.maxItems);
29
- return Math.floor(random() * (max - min + 1)) + min;
30
- }
31
-
32
- if (schema.minItems !== undefined) {
33
- return Math.max(schema.minItems, DEFAULT_ARRAY_COUNT);
34
- }
35
-
36
- if (schema.maxItems !== undefined) {
37
- return Math.min(schema.maxItems, DEFAULT_ARRAY_COUNT);
38
- }
39
-
40
- return DEFAULT_ARRAY_COUNT;
41
- }
42
-
43
- /**
44
- * Apply overrides to generated data with support for templates
45
- * Supports nested paths (dot notation), templates with {{params.id}}, and state access
46
- * @param data - Generated data to apply overrides to
47
- * @param overrides - Override values (can use templates)
48
- * @param params - Route parameters for template expansion
49
- * @param state - Plugin state for template expansion
50
- * @param query - Query parameters for template expansion
51
- * @returns Data with overrides applied
52
- */
53
- export function applyOverrides(
54
- data: unknown,
55
- overrides?: Record<string, unknown>,
56
- params?: Record<string, string>,
57
- state?: Record<string, unknown>,
58
- query?: Record<string, string>,
59
- ): unknown {
60
- if (!overrides) return data;
61
- if (typeof data !== "object" || data === null) return data;
62
-
63
- const result = structuredClone(data) as Record<string, unknown>;
64
-
65
- for (const [key, value] of Object.entries(overrides)) {
66
- if (DANGEROUS_KEYS.has(key)) continue;
67
- // Handle nested paths like "data.id" or "pagination.page"
68
- if (key.includes(".")) {
69
- setNestedProperty(result, key, value, { params, state, query });
70
- } else {
71
- // Handle flat keys and nested objects
72
- if (
73
- typeof value === "object" &&
74
- value !== null &&
75
- !Array.isArray(value)
76
- ) {
77
- const nested = value as Record<string, unknown>;
78
- // Recursively apply nested overrides
79
- if (result[key] && typeof result[key] === "object") {
80
- result[key] = applyOverrides(
81
- result[key],
82
- nested,
83
- params,
84
- state,
85
- query,
86
- );
87
- } else {
88
- result[key] = applyOverrides({}, nested, params, state, query);
89
- }
90
- } else if (typeof value === "string" && value.includes("{{")) {
91
- // Template processing
92
- result[key] = processTemplate(value, { params, state, query });
93
- } else {
94
- result[key] = value;
95
- }
96
- }
97
- }
98
-
99
- return result;
100
- }
101
-
102
- function setNestedProperty(
103
- obj: Record<string, unknown>,
104
- path: string,
105
- value: unknown,
106
- context: TemplateContext,
107
- ): void {
108
- const parts = path.split(".");
109
- let current: Record<string, unknown> = obj;
110
-
111
- // Navigate to the parent of the target property
112
- for (let i = 0; i < parts.length - 1; i++) {
113
- const part = parts[i];
114
- if (DANGEROUS_KEYS.has(part)) return;
115
- if (
116
- !(part in current) ||
117
- typeof current[part] !== "object" ||
118
- current[part] === null
119
- ) {
120
- current[part] = {};
121
- }
122
- current = current[part] as Record<string, unknown>;
123
- }
124
-
125
- // Set the final property
126
- const finalKey = parts[parts.length - 1];
127
- if (DANGEROUS_KEYS.has(finalKey)) return;
128
- if (typeof value === "string" && value.includes("{{")) {
129
- current[finalKey] = processTemplate(value, context);
130
- } else {
131
- current[finalKey] = value;
132
- }
133
- }
134
-
135
- interface TemplateContext {
136
- params?: Record<string, string>;
137
- state?: Record<string, unknown>;
138
- query?: Record<string, string>;
139
- }
140
-
141
- function resolveTemplatePath(
142
- context: TemplateContext,
143
- expression: string,
144
- ): unknown {
145
- const parts = expression.trim().split(".");
146
- let result: unknown = context;
147
-
148
- for (const part of parts) {
149
- if (result && typeof result === "object") {
150
- result = (result as Record<string, unknown>)[part];
151
- } else {
152
- return undefined;
153
- }
154
- }
155
-
156
- return result;
157
- }
158
-
159
- function processTemplate(template: string, context: TemplateContext): unknown {
160
- // Check if the template is just a single template expression
161
- const singleTemplateMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
162
- if (singleTemplateMatch) {
163
- // For single templates, return the actual value without string conversion
164
- const result = resolveTemplatePath(context, singleTemplateMatch[1]);
165
- return result !== undefined ? result : template;
166
- }
167
-
168
- // For templates mixed with other text, do string replacement
169
- return template.replace(
170
- /\{\{\s*([^}]+)\s*\}\}/g,
171
- (match, expression: string) => {
172
- const result = resolveTemplatePath(context, expression);
173
- return result !== undefined ? String(result) : match;
174
- },
175
- );
176
- }