@typokit/testing 0.1.4

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.
@@ -0,0 +1,65 @@
1
+ import type { HttpMethod, SchemaTypeMap } from "@typokit/types";
2
+ /** A route definition used for contract test generation */
3
+ export interface ContractTestRoute {
4
+ /** HTTP method */
5
+ method: HttpMethod;
6
+ /** Route path (e.g. "/users/:id") */
7
+ path: string;
8
+ /** Handler reference */
9
+ handlerRef: string;
10
+ /** Validator schema references */
11
+ validators?: {
12
+ params?: string;
13
+ query?: string;
14
+ body?: string;
15
+ };
16
+ /** Response schema name (for toMatchSchema assertions) */
17
+ responseSchema?: string;
18
+ /** Expected success status code (default: 200) */
19
+ expectedStatus?: number;
20
+ }
21
+ /** Supported test runners */
22
+ export type TestRunner = "jest" | "vitest" | "rstest";
23
+ /** Options for contract test generation */
24
+ export interface ContractTestOptions {
25
+ /** Test runner to generate imports for */
26
+ runner: TestRunner;
27
+ /** Import path for the app module (e.g. "../src/app") */
28
+ appImport: string;
29
+ /** Routes to generate tests for */
30
+ routes: ContractTestRoute[];
31
+ /** Schema type metadata for validators */
32
+ schemas: SchemaTypeMap;
33
+ }
34
+ /** A generated contract test file */
35
+ export interface ContractTestOutput {
36
+ /** Relative file path (e.g. "__generated__/users.contract.test.ts") */
37
+ filePath: string;
38
+ /** Generated file content */
39
+ content: string;
40
+ }
41
+ /**
42
+ * Generate contract test files from route schemas.
43
+ *
44
+ * Groups routes by path prefix and produces one test file per group.
45
+ * Each file tests: valid input → expected status, missing required
46
+ * fields → 400, invalid field formats → 400.
47
+ *
48
+ * ```ts
49
+ * const outputs = generateContractTests({
50
+ * runner: "vitest",
51
+ * appImport: "../src/app",
52
+ * routes: [{ method: "POST", path: "/users", ... }],
53
+ * schemas: { CreateUserInput: { ... } },
54
+ * });
55
+ * ```
56
+ */
57
+ export declare function generateContractTests(options: ContractTestOptions): ContractTestOutput[];
58
+ /**
59
+ * Detect the test runner used in a project by checking for known
60
+ * config files or dependencies.
61
+ *
62
+ * Returns the detected runner or "vitest" as default.
63
+ */
64
+ export declare function detectTestRunner(packageJson: Record<string, unknown>): TestRunner;
65
+ //# sourceMappingURL=contract-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-generator.d.ts","sourceRoot":"","sources":["../src/contract-generator.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAgB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI9E,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,kBAAkB;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,UAAU,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6BAA6B;AAC7B,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEtD,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,MAAM,EAAE,UAAU,CAAC;IACnB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,0CAA0C;IAC1C,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAuHD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,EAAE,CAgBtB;AAoLD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,UAAU,CAoBZ"}
@@ -0,0 +1,325 @@
1
+ // @typokit/testing — Contract Test Generation
2
+ //
3
+ // Auto-generates baseline contract tests from route schemas.
4
+ // Output is test-runner-agnostic (Jest, Vitest, Rstest).
5
+ // ─── Helpers ──────────────────────────────────────────────────
6
+ /** Deterministic seed-based data generator for inline test values */
7
+ function generateSampleValue(type, jsdoc) {
8
+ // Check for format constraints
9
+ const format = jsdoc?.["format"] ?? jsdoc?.["@format"];
10
+ if (format === "email")
11
+ return "test@example.com";
12
+ if (format === "url")
13
+ return "https://example.com";
14
+ if (format === "uuid")
15
+ return "550e8400-e29b-41d4-a716-446655440000";
16
+ if (format === "date-time")
17
+ return "2026-01-01T00:00:00.000Z";
18
+ // Check for string unions
19
+ if (type.includes("|") && type.includes('"')) {
20
+ const values = type.split("|").map((v) => v.trim().replace(/^"|"$/g, ""));
21
+ return values[0];
22
+ }
23
+ if (type === "string")
24
+ return "test-value";
25
+ if (type === "number")
26
+ return 42;
27
+ if (type === "boolean")
28
+ return true;
29
+ if (type === "string[]")
30
+ return ["test-item"];
31
+ if (type === "number[]")
32
+ return [1];
33
+ if (type.endsWith("[]"))
34
+ return [];
35
+ return "test-value";
36
+ }
37
+ /** Generate an invalid value for a given type */
38
+ function generateInvalidSampleValue(type, jsdoc) {
39
+ const format = jsdoc?.["format"] ?? jsdoc?.["@format"];
40
+ if (format === "email")
41
+ return "not-an-email";
42
+ if (format === "url")
43
+ return "not-a-url";
44
+ if (format === "uuid")
45
+ return "not-a-uuid";
46
+ if (format === "date-time")
47
+ return "not-a-date";
48
+ if (type === "number")
49
+ return "not-a-number";
50
+ if (type === "boolean")
51
+ return "not-a-boolean";
52
+ // String unions — use a value not in the set
53
+ if (type.includes("|") && type.includes('"')) {
54
+ return "__invalid_enum_value__";
55
+ }
56
+ return 12345;
57
+ }
58
+ /** Get the test runner import statement */
59
+ function getImportStatement(runner) {
60
+ switch (runner) {
61
+ case "jest":
62
+ return 'import { describe, it, expect } from "@jest/globals";';
63
+ case "vitest":
64
+ return 'import { describe, it, expect } from "vitest";';
65
+ case "rstest":
66
+ return 'import { describe, it, expect } from "@rstest/core";';
67
+ }
68
+ }
69
+ /** Group routes by path prefix for file organization */
70
+ function groupRoutesByPrefix(routes) {
71
+ const groups = {};
72
+ for (const route of routes) {
73
+ // Extract first path segment as group name
74
+ const segments = route.path.split("/").filter(Boolean);
75
+ const prefix = segments.length > 0 ? segments[0] : "root";
76
+ if (!groups[prefix]) {
77
+ groups[prefix] = [];
78
+ }
79
+ groups[prefix].push(route);
80
+ }
81
+ // Sort routes within each group deterministically
82
+ for (const key of Object.keys(groups)) {
83
+ groups[key].sort((a, b) => {
84
+ const methodOrder = a.method.localeCompare(b.method);
85
+ if (methodOrder !== 0)
86
+ return methodOrder;
87
+ return a.path.localeCompare(b.path);
88
+ });
89
+ }
90
+ return groups;
91
+ }
92
+ /** Escape a value for use in generated TypeScript code */
93
+ function toCodeLiteral(value) {
94
+ if (typeof value === "string")
95
+ return JSON.stringify(value);
96
+ if (typeof value === "number")
97
+ return String(value);
98
+ if (typeof value === "boolean")
99
+ return String(value);
100
+ if (Array.isArray(value)) {
101
+ return `[${value.map(toCodeLiteral).join(", ")}]`;
102
+ }
103
+ return JSON.stringify(value);
104
+ }
105
+ /** Build a valid body object literal as code string */
106
+ function buildBodyLiteral(schema, indent) {
107
+ const entries = [];
108
+ for (const [key, prop] of Object.entries(schema.properties)) {
109
+ if (prop.optional)
110
+ continue;
111
+ const value = generateSampleValue(prop.type, prop.jsdoc);
112
+ entries.push(`${indent} ${key}: ${toCodeLiteral(value)},`);
113
+ }
114
+ if (entries.length === 0)
115
+ return "{}";
116
+ return `{\n${entries.join("\n")}\n${indent}}`;
117
+ }
118
+ // ─── Generator ────────────────────────────────────────────────
119
+ /**
120
+ * Generate contract test files from route schemas.
121
+ *
122
+ * Groups routes by path prefix and produces one test file per group.
123
+ * Each file tests: valid input → expected status, missing required
124
+ * fields → 400, invalid field formats → 400.
125
+ *
126
+ * ```ts
127
+ * const outputs = generateContractTests({
128
+ * runner: "vitest",
129
+ * appImport: "../src/app",
130
+ * routes: [{ method: "POST", path: "/users", ... }],
131
+ * schemas: { CreateUserInput: { ... } },
132
+ * });
133
+ * ```
134
+ */
135
+ export function generateContractTests(options) {
136
+ const { runner, appImport, routes, schemas } = options;
137
+ const groups = groupRoutesByPrefix(routes);
138
+ const outputs = [];
139
+ for (const [prefix, groupRoutes] of Object.entries(groups).sort(([a], [b]) => a.localeCompare(b))) {
140
+ const content = generateTestFile(runner, appImport, groupRoutes, schemas);
141
+ outputs.push({
142
+ filePath: `__generated__/${prefix}.contract.test.ts`,
143
+ content,
144
+ });
145
+ }
146
+ return outputs;
147
+ }
148
+ /** Generate a single contract test file for a group of routes */
149
+ function generateTestFile(runner, appImport, routes, schemas) {
150
+ const lines = [];
151
+ // Header
152
+ lines.push("// DO NOT EDIT — regenerated on schema change");
153
+ lines.push(`// Generated by @typokit/testing contract-generator`);
154
+ lines.push("");
155
+ // Imports
156
+ lines.push(getImportStatement(runner));
157
+ lines.push(`import { createTestClient } from "@typokit/testing";`);
158
+ // Only import toMatchSchema if any route has a response schema
159
+ const hasResponseSchema = routes.some((r) => r.responseSchema);
160
+ if (hasResponseSchema) {
161
+ lines.push(`import { toMatchSchema } from "@typokit/testing";`);
162
+ }
163
+ lines.push(`import { app } from ${JSON.stringify(appImport)};`);
164
+ lines.push("");
165
+ // Setup
166
+ lines.push("let client: Awaited<ReturnType<typeof createTestClient>>;");
167
+ lines.push("");
168
+ lines.push("beforeAll(async () => {");
169
+ lines.push(" client = await createTestClient(app);");
170
+ lines.push("});");
171
+ lines.push("");
172
+ lines.push("afterAll(async () => {");
173
+ lines.push(" await client.close();");
174
+ lines.push("});");
175
+ lines.push("");
176
+ // Generate test blocks for each route
177
+ for (const route of routes) {
178
+ generateRouteTests(lines, route, schemas);
179
+ lines.push("");
180
+ }
181
+ return lines.join("\n");
182
+ }
183
+ /** Generate describe/it blocks for a single route */
184
+ function generateRouteTests(lines, route, schemas) {
185
+ const { method, path, validators, responseSchema, expectedStatus } = route;
186
+ const successStatus = expectedStatus ?? 200;
187
+ const methodLower = method.toLowerCase();
188
+ lines.push(`describe("${method} ${path}", () => {`);
189
+ // Resolve body schema if validators reference one
190
+ const bodySchemaName = validators?.body;
191
+ const bodySchema = bodySchemaName ? schemas[bodySchemaName] : undefined;
192
+ // Determine if the method typically has a body
193
+ const hasBody = ["POST", "PUT", "PATCH"].includes(method);
194
+ // ── Test 1: Valid input → expected status ──
195
+ if (hasBody && bodySchema) {
196
+ const bodyLiteral = buildBodyLiteral(bodySchema, " ");
197
+ lines.push(` it("accepts valid ${bodySchemaName}", async () => {`);
198
+ lines.push(` const res = await client.${methodLower}("${path}", {`);
199
+ lines.push(` body: ${bodyLiteral},`);
200
+ lines.push(` });`);
201
+ lines.push(` expect(res.status).toBe(${successStatus});`);
202
+ if (responseSchema) {
203
+ lines.push(` expect(res.body).toMatchSchema("${responseSchema}");`);
204
+ }
205
+ lines.push(` });`);
206
+ }
207
+ else {
208
+ lines.push(` it("responds with ${successStatus}", async () => {`);
209
+ lines.push(` const res = await client.${methodLower}("${path}");`);
210
+ lines.push(` expect(res.status).toBe(${successStatus});`);
211
+ if (responseSchema) {
212
+ lines.push(` expect(res.body).toMatchSchema("${responseSchema}");`);
213
+ }
214
+ lines.push(` });`);
215
+ }
216
+ // ── Test 2: Missing required fields → 400 ──
217
+ if (hasBody && bodySchema) {
218
+ const requiredFields = Object.entries(bodySchema.properties)
219
+ .filter(([, prop]) => !prop.optional)
220
+ .map(([key]) => key)
221
+ .sort();
222
+ if (requiredFields.length > 0) {
223
+ lines.push("");
224
+ lines.push(` it("rejects missing required fields", async () => {`);
225
+ lines.push(` const res = await client.${methodLower}("${path}", { body: {} });`);
226
+ lines.push(` expect(res.status).toBe(400);`);
227
+ lines.push(` });`);
228
+ // Individual field tests for more specific coverage
229
+ for (const field of requiredFields) {
230
+ lines.push("");
231
+ lines.push(` it("rejects missing '${field}' field", async () => {`);
232
+ // Build a body with all required fields except this one
233
+ const partialEntries = [];
234
+ for (const [key, prop] of Object.entries(bodySchema.properties)) {
235
+ if (prop.optional || key === field)
236
+ continue;
237
+ const value = generateSampleValue(prop.type, prop.jsdoc);
238
+ partialEntries.push(` ${key}: ${toCodeLiteral(value)},`);
239
+ }
240
+ const partialBody = partialEntries.length > 0
241
+ ? `{\n${partialEntries.join("\n")}\n }`
242
+ : "{}";
243
+ lines.push(` const res = await client.${methodLower}("${path}", {`);
244
+ lines.push(` body: ${partialBody},`);
245
+ lines.push(` });`);
246
+ lines.push(` expect(res.status).toBe(400);`);
247
+ lines.push(` });`);
248
+ }
249
+ }
250
+ }
251
+ // ── Test 3: Invalid field formats → 400 ──
252
+ if (hasBody && bodySchema) {
253
+ const fieldsWithFormats = Object.entries(bodySchema.properties)
254
+ .filter(([, prop]) => {
255
+ const jsdoc = prop.jsdoc;
256
+ if (!jsdoc)
257
+ return false;
258
+ return !!(jsdoc["format"] || jsdoc["@format"]);
259
+ })
260
+ .sort(([a], [b]) => a.localeCompare(b));
261
+ // Also include string unions and typed fields that can be invalidated
262
+ const fieldsWithTypes = Object.entries(bodySchema.properties)
263
+ .filter(([, prop]) => {
264
+ // Already covered by format
265
+ if (prop.jsdoc?.["format"] || prop.jsdoc?.["@format"])
266
+ return false;
267
+ return (prop.type === "number" ||
268
+ prop.type === "boolean" ||
269
+ (prop.type.includes("|") && prop.type.includes('"')));
270
+ })
271
+ .sort(([a], [b]) => a.localeCompare(b));
272
+ const invalidFields = [...fieldsWithFormats, ...fieldsWithTypes];
273
+ for (const [field, prop] of invalidFields) {
274
+ const invalidValue = generateInvalidSampleValue(prop.type, prop.jsdoc);
275
+ lines.push("");
276
+ lines.push(` it("rejects invalid ${field} format", async () => {`);
277
+ // Build a valid body, then replace the field with invalid value
278
+ const fullEntries = [];
279
+ for (const [key, p] of Object.entries(bodySchema.properties)) {
280
+ if (p.optional)
281
+ continue;
282
+ const value = key === field ? invalidValue : generateSampleValue(p.type, p.jsdoc);
283
+ fullEntries.push(` ${key}: ${toCodeLiteral(value)},`);
284
+ }
285
+ const fullBody = fullEntries.length > 0 ? `{\n${fullEntries.join("\n")}\n }` : "{}";
286
+ lines.push(` const res = await client.${methodLower}("${path}", {`);
287
+ lines.push(` body: ${fullBody},`);
288
+ lines.push(` });`);
289
+ lines.push(` expect(res.status).toBe(400);`);
290
+ lines.push(` });`);
291
+ }
292
+ }
293
+ lines.push(`});`);
294
+ }
295
+ /**
296
+ * Detect the test runner used in a project by checking for known
297
+ * config files or dependencies.
298
+ *
299
+ * Returns the detected runner or "vitest" as default.
300
+ */
301
+ export function detectTestRunner(packageJson) {
302
+ const deps = {
303
+ ...packageJson["dependencies"],
304
+ ...packageJson["devDependencies"],
305
+ };
306
+ if (deps["rstest"])
307
+ return "rstest";
308
+ if (deps["vitest"])
309
+ return "vitest";
310
+ if (deps["jest"] || deps["@jest/globals"])
311
+ return "jest";
312
+ // Check scripts for runner hints
313
+ const scripts = packageJson["scripts"];
314
+ if (scripts) {
315
+ const testScript = scripts["test"] ?? "";
316
+ if (testScript.includes("rstest"))
317
+ return "rstest";
318
+ if (testScript.includes("vitest"))
319
+ return "vitest";
320
+ if (testScript.includes("jest"))
321
+ return "jest";
322
+ }
323
+ return "vitest";
324
+ }
325
+ //# sourceMappingURL=contract-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-generator.js","sourceRoot":"","sources":["../src/contract-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { TypeMetadata } from "@typokit/types";
2
+ /** Options for creating a factory */
3
+ export interface FactoryOptions {
4
+ /** Seed for deterministic random generation */
5
+ seed?: number;
6
+ }
7
+ /** A test factory that produces typed instances */
8
+ export interface Factory<T> {
9
+ /** Build a single valid instance with optional field overrides */
10
+ build(overrides?: Partial<T>): T;
11
+ /** Build multiple valid instances */
12
+ buildMany(count: number, overrides?: Partial<T>): T[];
13
+ /** Build an instance with a specific field set to an invalid value */
14
+ buildInvalid(field: keyof T & string): T;
15
+ }
16
+ /**
17
+ * Create a type-safe test factory from TypeMetadata.
18
+ *
19
+ * ```ts
20
+ * const userFactory = createFactory<User>(userMetadata, { seed: 42 });
21
+ * const user = userFactory.build();
22
+ * const admin = userFactory.build({ role: "admin" });
23
+ * const invalid = userFactory.buildInvalid("email");
24
+ * ```
25
+ */
26
+ export declare function createFactory<T>(metadata: TypeMetadata, options?: FactoryOptions): Factory<T>;
27
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgBnD,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mDAAmD;AACnD,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,kEAAkE;IAClE,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACtD,sEAAsE;IACtE,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;CAC1C;AAuJD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,QAAQ,EAAE,YAAY,EACtB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAgDZ"}
@@ -0,0 +1,194 @@
1
+ // @typokit/testing — Test Factories
2
+ //
3
+ // Type-safe test factories that produce valid/invalid fixture data
4
+ // from TypeMetadata. Deterministic when seeded.
5
+ // ─── Seeded PRNG (mulberry32) ─────────────────────────────────
6
+ function mulberry32(seed) {
7
+ let s = seed | 0;
8
+ return () => {
9
+ s = (s + 0x6d2b79f5) | 0;
10
+ let t = Math.imul(s ^ (s >>> 15), 1 | s);
11
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
12
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
13
+ };
14
+ }
15
+ // ─── Random Data Generators ───────────────────────────────────
16
+ function randomString(rand, length) {
17
+ const chars = "abcdefghijklmnopqrstuvwxyz";
18
+ let result = "";
19
+ for (let i = 0; i < length; i++) {
20
+ result += chars[Math.floor(rand() * chars.length)];
21
+ }
22
+ return result;
23
+ }
24
+ function randomEmail(rand) {
25
+ return `${randomString(rand, 8)}@${randomString(rand, 5)}.com`;
26
+ }
27
+ function randomUrl(rand) {
28
+ return `https://${randomString(rand, 8)}.com/${randomString(rand, 4)}`;
29
+ }
30
+ function randomUuid(rand) {
31
+ const hex = "0123456789abcdef";
32
+ const segments = [8, 4, 4, 4, 12];
33
+ return segments
34
+ .map((len) => {
35
+ let s = "";
36
+ for (let i = 0; i < len; i++) {
37
+ s += hex[Math.floor(rand() * 16)];
38
+ }
39
+ return s;
40
+ })
41
+ .join("-");
42
+ }
43
+ function randomInt(rand, min, max) {
44
+ return Math.floor(rand() * (max - min + 1)) + min;
45
+ }
46
+ function randomDate(rand) {
47
+ const year = randomInt(rand, 2020, 2030);
48
+ const month = String(randomInt(rand, 1, 12)).padStart(2, "0");
49
+ const day = String(randomInt(rand, 1, 28)).padStart(2, "0");
50
+ return `${year}-${month}-${day}T00:00:00.000Z`;
51
+ }
52
+ // ─── Value Generator ──────────────────────────────────────────
53
+ function generateValue(type, jsdoc, rand) {
54
+ const format = jsdoc?.["format"];
55
+ const minLengthStr = jsdoc?.["minLength"];
56
+ const maxLengthStr = jsdoc?.["maxLength"];
57
+ const minStr = jsdoc?.["minimum"];
58
+ const maxStr = jsdoc?.["maximum"];
59
+ // Check for JSDoc format constraints first
60
+ if (format === "email")
61
+ return randomEmail(rand);
62
+ if (format === "url" || format === "uri")
63
+ return randomUrl(rand);
64
+ if (format === "uuid")
65
+ return randomUuid(rand);
66
+ if (format === "date" || format === "date-time")
67
+ return randomDate(rand);
68
+ // String union types like '"a" | "b" | "c"'
69
+ if (type.includes('" | "') || type.includes("' | '")) {
70
+ const values = type
71
+ .split("|")
72
+ .map((v) => v.trim().replace(/^["']|["']$/g, ""));
73
+ return values[Math.floor(rand() * values.length)];
74
+ }
75
+ // Handle base types
76
+ const baseType = type.replace(/\[\]$/, "");
77
+ const isArray = type.endsWith("[]");
78
+ const gen = () => {
79
+ switch (baseType) {
80
+ case "string": {
81
+ const minLen = minLengthStr ? parseInt(minLengthStr, 10) : 5;
82
+ const maxLen = maxLengthStr ? parseInt(maxLengthStr, 10) : 20;
83
+ const len = randomInt(rand, minLen, maxLen);
84
+ return randomString(rand, len);
85
+ }
86
+ case "number": {
87
+ const min = minStr ? parseInt(minStr, 10) : 1;
88
+ const max = maxStr ? parseInt(maxStr, 10) : 1000;
89
+ return randomInt(rand, min, max);
90
+ }
91
+ case "boolean":
92
+ return rand() > 0.5;
93
+ case "Date":
94
+ return randomDate(rand);
95
+ default:
96
+ return randomString(rand, 10);
97
+ }
98
+ };
99
+ if (isArray) {
100
+ const count = randomInt(rand, 1, 3);
101
+ return Array.from({ length: count }, gen);
102
+ }
103
+ return gen();
104
+ }
105
+ // ─── Invalid Value Generator ──────────────────────────────────
106
+ function generateInvalidValue(type, jsdoc) {
107
+ const format = jsdoc?.["format"];
108
+ const minLengthStr = jsdoc?.["minLength"];
109
+ const maxStr = jsdoc?.["maximum"];
110
+ const minStr = jsdoc?.["minimum"];
111
+ if (format === "email")
112
+ return "not-an-email";
113
+ if (format === "url" || format === "uri")
114
+ return "not a url";
115
+ if (format === "uuid")
116
+ return "not-a-uuid";
117
+ if (format === "date" || format === "date-time")
118
+ return "not-a-date";
119
+ if (type.includes('" | "') || type.includes("' | '")) {
120
+ return "__invalid_enum_value__";
121
+ }
122
+ const baseType = type.replace(/\[\]$/, "");
123
+ switch (baseType) {
124
+ case "string": {
125
+ if (minLengthStr) {
126
+ const minLen = parseInt(minLengthStr, 10);
127
+ return minLen > 1 ? "x" : "";
128
+ }
129
+ return "";
130
+ }
131
+ case "number": {
132
+ if (maxStr)
133
+ return parseInt(maxStr, 10) + 100;
134
+ if (minStr)
135
+ return parseInt(minStr, 10) - 100;
136
+ return null;
137
+ }
138
+ case "boolean":
139
+ return "not-a-boolean";
140
+ default:
141
+ return null;
142
+ }
143
+ }
144
+ // ─── createFactory ────────────────────────────────────────────
145
+ /**
146
+ * Create a type-safe test factory from TypeMetadata.
147
+ *
148
+ * ```ts
149
+ * const userFactory = createFactory<User>(userMetadata, { seed: 42 });
150
+ * const user = userFactory.build();
151
+ * const admin = userFactory.build({ role: "admin" });
152
+ * const invalid = userFactory.buildInvalid("email");
153
+ * ```
154
+ */
155
+ export function createFactory(metadata, options = {}) {
156
+ const seed = options.seed ?? 12345;
157
+ function buildOne(rand, overrides) {
158
+ const result = {};
159
+ for (const [key, prop] of Object.entries(metadata.properties)) {
160
+ if (prop.optional && rand() > 0.7) {
161
+ continue; // skip some optional fields
162
+ }
163
+ result[key] = generateValue(prop.type, prop.jsdoc, rand);
164
+ }
165
+ if (overrides) {
166
+ Object.assign(result, overrides);
167
+ }
168
+ return result;
169
+ }
170
+ return {
171
+ build(overrides) {
172
+ const rand = mulberry32(seed);
173
+ return buildOne(rand, overrides);
174
+ },
175
+ buildMany(count, overrides) {
176
+ const rand = mulberry32(seed);
177
+ const results = [];
178
+ for (let i = 0; i < count; i++) {
179
+ results.push(buildOne(rand, overrides));
180
+ }
181
+ return results;
182
+ },
183
+ buildInvalid(field) {
184
+ const rand = mulberry32(seed);
185
+ const instance = buildOne(rand);
186
+ const prop = metadata.properties[field];
187
+ if (prop) {
188
+ instance[field] = generateInvalidValue(prop.type, prop.jsdoc);
189
+ }
190
+ return instance;
191
+ },
192
+ };
193
+ }
194
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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"}