UNPKG

typescript-runtime-schemas

Version:

A TypeScript schema generation tool that extracts Zod schemas from TypeScript source files with runtime validation support. Generate validation schemas directly from your existing TypeScript types with support for computed types and constraint-based valid

391 lines (390 loc) 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const zod_1 = require("zod"); const zod_schema_generator_1 = require("./zod-schema-generator"); describe("ZodSchemaGenerator", () => { describe("generateSchemas", () => { it("should generate Zod schemas from extracted schemas", () => { const extractedSchemas = [ { typeName: "TestUser", schema: { id: { type: "number", required: true, constraints: { min: 1 }, }, name: { type: "string", required: true, constraints: { minLength: 2, maxLength: 50 }, }, email: { type: "string", required: true, constraints: { email: true }, }, age: { type: "number", required: false, constraints: { min: 18, max: 120 }, }, }, }, ]; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSchemas(extractedSchemas); expect(result).toHaveLength(1); expect(result[0].typeName).toBe("TestUser"); expect(result[0].zodSchema).toBeInstanceOf(zod_1.z.ZodObject); // Test the generated schema by parsing valid data const validData = { id: 123, name: "John Doe", email: "john@example.com", age: 25, }; const parsed = result[0].zodSchema.parse(validData); expect(parsed).toEqual(validData); }); it("should include source info when requested", () => { const extractedSchemas = [ { typeName: "TestType", schema: { value: { type: "string", required: true, constraints: {}, }, }, sourceInfo: { filePath: "/test/file.ts", line: 10, }, }, ]; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSchemas(extractedSchemas, true); expect(result[0].sourceInfo).toEqual({ filePath: "/test/file.ts", line: 10, }); }); it("should exclude source info when not requested", () => { const extractedSchemas = [ { typeName: "TestType", schema: { value: { type: "string", required: true, constraints: {}, }, }, sourceInfo: { filePath: "/test/file.ts", line: 10, }, }, ]; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSchemas(extractedSchemas, false); expect(result[0].sourceInfo).toBeUndefined(); }); }); describe("constraint mapping", () => { it("should handle numeric constraints", () => { const schema = { type: "number", required: true, constraints: { min: 10, max: 100, integer: true, positive: true, }, }; const extractedSchema = { typeName: "NumericTest", schema: { value: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid values expect(() => zodSchema.parse({ value: 50 })).not.toThrow(); expect(() => zodSchema.parse({ value: 10 })).not.toThrow(); expect(() => zodSchema.parse({ value: 100 })).not.toThrow(); // Test invalid values expect(() => zodSchema.parse({ value: 5 })).toThrow(); expect(() => zodSchema.parse({ value: 150 })).toThrow(); expect(() => zodSchema.parse({ value: -10 })).toThrow(); }); it("should handle string constraints", () => { const schema = { type: "string", required: true, constraints: { minLength: 3, maxLength: 20, email: true, }, }; const extractedSchema = { typeName: "StringTest", schema: { email: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid email expect(() => zodSchema.parse({ email: "test@example.com" })).not.toThrow(); // Test invalid emails expect(() => zodSchema.parse({ email: "ab" })).toThrow(); // Too short expect(() => zodSchema.parse({ email: "invalid-email" })).toThrow(); // Not email format }); it("should handle array constraints", () => { const schema = { type: "array", required: true, constraints: { minLength: 1, maxLength: 5, }, arrayElementType: { type: "string", required: true, constraints: {}, }, }; const extractedSchema = { typeName: "ArrayTest", schema: { tags: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid arrays expect(() => zodSchema.parse({ tags: ["tag1"] })).not.toThrow(); expect(() => zodSchema.parse({ tags: ["tag1", "tag2", "tag3"] })).not.toThrow(); // Test invalid arrays expect(() => zodSchema.parse({ tags: [] })).toThrow(); // Too short expect(() => zodSchema.parse({ tags: ["1", "2", "3", "4", "5", "6"] })).toThrow(); // Too long }); it("should handle nested objects", () => { const schema = { type: "object", required: true, constraints: {}, properties: { street: { type: "string", required: true, constraints: { minLength: 5 }, }, city: { type: "string", required: true, constraints: { minLength: 2 }, }, zipCode: { type: "string", required: false, constraints: { regex: "^[0-9]{5}$" }, }, }, }; const extractedSchema = { typeName: "NestedTest", schema: { address: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid nested object const validData = { address: { street: "123 Main Street", city: "New York", zipCode: "12345", }, }; expect(() => zodSchema.parse(validData)).not.toThrow(); // Test invalid nested object const invalidData = { address: { street: "123", // Too short city: "NY", zipCode: "invalid", // Invalid format }, }; expect(() => zodSchema.parse(invalidData)).toThrow(); }); it("should handle optional properties", () => { const schema = { type: "string", required: false, constraints: { minLength: 3 }, }; const extractedSchema = { typeName: "OptionalTest", schema: { optionalField: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test with optional field present expect(() => zodSchema.parse({ optionalField: "test" })).not.toThrow(); // Test with optional field missing expect(() => zodSchema.parse({})).not.toThrow(); // Test with invalid optional field expect(() => zodSchema.parse({ optionalField: "ab" })).toThrow(); }); it("should handle custom constraints with refinements", () => { const schema = { type: "string", required: true, constraints: { phone: true, }, }; const extractedSchema = { typeName: "PhoneTest", schema: { phoneNumber: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid phone numbers expect(() => zodSchema.parse({ phoneNumber: "+1234567890" })).not.toThrow(); expect(() => zodSchema.parse({ phoneNumber: "1234567890" })).not.toThrow(); // Test invalid phone numbers expect(() => zodSchema.parse({ phoneNumber: "invalid" })).toThrow(); expect(() => zodSchema.parse({ phoneNumber: "0123" })).toThrow(); }); it("should handle regex constraints", () => { const schema = { type: "string", required: true, constraints: { regex: "^[A-Z]{2}[0-9]{4}$", }, }; const extractedSchema = { typeName: "RegexTest", schema: { code: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid codes expect(() => zodSchema.parse({ code: "AB1234" })).not.toThrow(); expect(() => zodSchema.parse({ code: "XY9876" })).not.toThrow(); // Test invalid codes expect(() => zodSchema.parse({ code: "ab1234" })).toThrow(); // Lowercase expect(() => zodSchema.parse({ code: "ABC123" })).toThrow(); // Wrong format }); it("should handle oneOf constraints", () => { const schema = { type: "string", required: true, constraints: { oneOf: ["red", "green", "blue"], }, }; const extractedSchema = { typeName: "EnumTest", schema: { color: schema }, }; const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); const zodSchema = result.zodSchema; // Test valid enum values expect(() => zodSchema.parse({ color: "red" })).not.toThrow(); expect(() => zodSchema.parse({ color: "green" })).not.toThrow(); expect(() => zodSchema.parse({ color: "blue" })).not.toThrow(); // Test invalid enum values expect(() => zodSchema.parse({ color: "yellow" })).toThrow(); expect(() => zodSchema.parse({ color: "Red" })).toThrow(); // Case sensitive }); }); describe("integration with pipeline", () => { it("should work with the full pipeline", async () => { const sourceCode = ` // Constraint definitions type SupportsRuntimeValidation = {}; type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type Email = Constraint<string>; // Validated type type ValidatedUser = { id: number & Min<1>; username: string & MinLength<3> & MaxLength<20>; email: string & Email; tags: string[] & MinLength<1> & MaxLength<5>; profile?: { firstName: string & MinLength<1>; lastName: string & MinLength<1>; }; } & SupportsRuntimeValidation; `; const zodSchemas = await (0, zod_schema_generator_1.generateZodSchemasFromSource)(sourceCode); expect(zodSchemas).toHaveLength(1); expect(zodSchemas[0].typeName).toBe("ValidatedUser"); const schema = zodSchemas[0].zodSchema; // Test valid data const validUser = { id: 123, username: "johndoe", email: "john@example.com", tags: ["developer", "typescript"], profile: { firstName: "John", lastName: "Doe", }, }; expect(() => schema.parse(validUser)).not.toThrow(); // Test invalid data const invalidUser = { id: 0, // Below minimum username: "jo", // Too short email: "invalid-email", // Invalid format tags: [], // Empty array }; expect(() => schema.parse(invalidUser)).toThrow(); }); }); describe("error handling", () => { it("should handle unknown constraints gracefully", () => { const schema = { type: "string", required: true, constraints: { unknownConstraint: "someValue", }, }; const extractedSchema = { typeName: "UnknownConstraintTest", schema: { value: schema }, }; // Should not throw, but log a warning const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); expect(result.zodSchema).toBeInstanceOf(zod_1.z.ZodObject); expect(consoleSpy).toHaveBeenCalledWith("Unknown constraint: unknownConstraint with value:", "someValue"); consoleSpy.mockRestore(); }); it("should handle invalid regex patterns gracefully", () => { const schema = { type: "string", required: true, constraints: { regex: "[invalid regex", }, }; const extractedSchema = { typeName: "InvalidRegexTest", schema: { value: schema }, }; const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); const result = zod_schema_generator_1.ZodSchemaGenerator.generateSingle(extractedSchema); expect(result.zodSchema).toBeInstanceOf(zod_1.z.ZodObject); expect(consoleSpy).toHaveBeenCalledWith("Invalid regex pattern: [invalid regex", expect.any(Error)); consoleSpy.mockRestore(); }); }); }); //# sourceMappingURL=zod-schema-generator.test.js.map