/** * Unit tests for type utilities and type safety. * Tests ReturnType and ArgType utility types with various API patterns. */ import { describe, it, expectTypeOf } from 'vitest' import { z } from 'zod' import { defineAPI } from '../src/def' import { ReturnType, ArgType } from '../src/types' describe('Type Utilities', () => { describe('ReturnType utility', () => { it('should extract return type from RetAPI', () => { const voSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email() }) const api = defineAPI({ path: '/users', voSchema }) type ApiReturnType = ReturnType // Should match the schema output type expectTypeOf().toEqualTypeOf<{ id: string name: string email: string }>() }) it('should extract return type from StdAPI', () => { const dtoSchema = z.object({ id: z.string() }) const voSchema = z.object({ id: z.string(), data: z.array(z.string()), count: z.number() }) const api = defineAPI({ path: '/data', dtoSchema, voSchema }) type ApiReturnType = ReturnType expectTypeOf().toEqualTypeOf<{ id: string data: string[] count: number }>() }) it('should return void for PlainAPI', () => { const api = defineAPI({ path: '/health' }) type ApiReturnType = ReturnType expectTypeOf().toEqualTypeOf() }) it('should return void for ArgAPI', () => { const dtoSchema = z.object({ message: z.string() }) const api = defineAPI({ path: '/log', dtoSchema }) type ApiReturnType = ReturnType expectTypeOf().toEqualTypeOf() }) it('should handle complex nested return types', () => { const complexSchema = z.object({ user: z.object({ id: z.string().uuid(), profile: z.object({ name: z.string(), age: z.number().optional(), preferences: z.record(z.boolean()) }), roles: z.array(z.enum(['user', 'admin', 'moderator'])) }), metadata: z.object({ timestamp: z.number(), version: z.string(), flags: z.array(z.string()).optional() }), settings: z.record(z.any()).optional() }) const api = defineAPI({ path: '/complex', voSchema: complexSchema }) type ApiReturnType = ReturnType expectTypeOf().toEqualTypeOf<{ user: { id: string profile: { name: string age?: number preferences: Record } roles: ('user' | 'admin' | 'moderator')[] } metadata: { timestamp: number version: string flags?: string[] } settings?: Record }>() }) }) describe('ArgType utility', () => { it('should extract argument type from ArgAPI', () => { const dtoSchema = z.object({ title: z.string().min(1), content: z.string(), tags: z.array(z.string()).optional(), published: z.boolean().default(false) }) const api = defineAPI({ path: '/posts', dtoSchema }) type ApiArgType = ArgType expectTypeOf().toEqualTypeOf<{ title: string content: string tags?: string[] published: boolean }>() }) it('should extract argument type from StdAPI', () => { const dtoSchema = z.object({ query: z.string(), filters: z.object({ status: z.enum(['active', 'inactive']), dateRange: z.object({ from: z.string(), to: z.string() }).optional() }).optional() }) const voSchema = z.object({ results: z.array(z.any()), total: z.number() }) const api = defineAPI({ path: '/search', dtoSchema, voSchema }) type ApiArgType = ArgType expectTypeOf().toEqualTypeOf<{ query: string filters?: { status: 'active' | 'inactive' dateRange?: { from: string to: string } } }>() }) it('should return void for PlainAPI', () => { const api = defineAPI({ path: '/ping' }) type ApiArgType = ArgType expectTypeOf().toEqualTypeOf() }) it('should return void for RetAPI', () => { const voSchema = z.object({ timestamp: z.number() }) const api = defineAPI({ path: '/time', voSchema }) type ApiArgType = ArgType expectTypeOf().toEqualTypeOf() }) it('should handle complex nested argument types', () => { const complexDtoSchema = z.object({ operation: z.enum(['create', 'update', 'delete']), target: z.discriminatedUnion('type', [ z.object({ type: z.literal('user'), userId: z.string(), userData: z.object({ name: z.string(), email: z.string().email(), age: z.number().int().min(0).max(150).optional() }) }), z.object({ type: z.literal('post'), postId: z.string().optional(), postData: z.object({ title: z.string(), content: z.string(), categories: z.array(z.string()) }) }) ]), options: z.object({ validateOnly: z.boolean().default(false), bypassRules: z.array(z.string()).optional(), metadata: z.record(z.string(), z.unknown()).optional() }).optional() }) const api = defineAPI({ path: '/complex-operation', dtoSchema: complexDtoSchema }) type ApiArgType = ArgType // This is a complex discriminated union type expectTypeOf().toEqualTypeOf<{ operation: 'create' | 'update' | 'delete' target: { type: 'user' userId: string userData: { name: string email: string age?: number } } | { type: 'post' postId?: string postData: { title: string content: string categories: string[] } } options?: { validateOnly: boolean bypassRules?: string[] metadata?: Record } }>() }) }) describe('Type transformation consistency', () => { it('should maintain type consistency between schema and extracted types', () => { const userSchema = z.object({ id: z.string().uuid(), name: z.string().min(1), email: z.string().email(), age: z.number().int().min(18), roles: z.array(z.enum(['user', 'admin'])), settings: z.record(z.boolean()).optional(), lastLogin: z.date().optional() }) const api = defineAPI({ path: '/user-profile', dtoSchema: userSchema, voSchema: userSchema }) type SchemaOutput = z.output type ApiArgType = ArgType type ApiReturnType = ReturnType // All should be the same type expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() }) it('should work with refined and transformed schemas', () => { const inputSchema = z.object({ email: z.string().email().toLowerCase(), password: z.string().min(8), confirmPassword: z.string() }).refine(data => data.password === data.confirmPassword, { message: "Passwords don't match", path: ["confirmPassword"] }) const outputSchema = z.object({ userId: z.string().uuid(), email: z.string().email(), createdAt: z.date(), verified: z.boolean() }) const api = defineAPI({ path: '/register', dtoSchema: inputSchema, voSchema: outputSchema }) type ApiArgType = ArgType type ApiReturnType = ReturnType expectTypeOf().toEqualTypeOf<{ email: string password: string confirmPassword: string }>() expectTypeOf().toEqualTypeOf<{ userId: string email: string createdAt: Date verified: boolean }>() }) it('should handle optional and default values correctly', () => { const schemaWithDefaults = z.object({ required: z.string(), optional: z.string().optional(), withDefault: z.string().default('default-value'), optionalWithDefault: z.string().optional().default('optional-default'), nullish: z.string().nullish(), array: z.array(z.string()).default([]) }) const api = defineAPI({ path: '/defaults-test', dtoSchema: schemaWithDefaults }) type ApiArgType = ArgType expectTypeOf().toEqualTypeOf<{ required: string optional?: string withDefault: string optionalWithDefault: string nullish?: string | null array: string[] }>() }) }) describe('API pattern type inference', () => { it('should infer correct types for all API patterns in same context', () => { // Define all patterns const plainApi = defineAPI({ path: '/plain' }) const argApi = defineAPI({ path: '/arg', dtoSchema: z.object({ input: z.string() }) }) const retApi = defineAPI({ path: '/ret', voSchema: z.object({ output: z.string() }) }) const stdApi = defineAPI({ path: '/std', dtoSchema: z.object({ input: z.number() }), voSchema: z.object({ output: z.number() }) }) // Extract types type PlainArg = ArgType type PlainRet = ReturnType type ArgArg = ArgType type ArgRet = ReturnType type RetArg = ArgType type RetRet = ReturnType type StdArg = ArgType type StdRet = ReturnType // Verify all types expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf<{ input: string }>() expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf<{ output: string }>() expectTypeOf().toEqualTypeOf<{ input: number }>() expectTypeOf().toEqualTypeOf<{ output: number }>() }) }) })