UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

577 lines 22.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const typebox_1 = require("@sinclair/typebox"); const createApiRoute_1 = require("../../../createApiRoute"); (0, vitest_1.describe)('createApiRoute - HTTP Methods', () => { (0, vitest_1.it)('should create GET route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users', method: 'GET', request: { query: typebox_1.Type.Object({ page: typebox_1.Type.Optional(typebox_1.Type.Number()), limit: typebox_1.Type.Optional(typebox_1.Type.Number()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Array(typebox_1.Type.Object({ id: typebox_1.Type.Number(), name: typebox_1.Type.String() })) } }, handler: async (req) => { const page = req.query.page || 1; const limit = req.query.limit || 10; return { status: 200, body: [{ id: 1, name: 'John Doe' }] }; } }); (0, vitest_1.expect)(route.method).toBe('GET'); (0, vitest_1.expect)(route.path).toBe('/api/users'); (0, vitest_1.expect)(route.request?.query).toBeDefined(); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); }); (0, vitest_1.it)('should create POST route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users', method: 'POST', request: { body: typebox_1.Type.Object({ name: typebox_1.Type.String(), email: typebox_1.Type.String(), age: typebox_1.Type.Number() }) }, response: { 201: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.Number(), name: typebox_1.Type.String(), email: typebox_1.Type.String(), created: typebox_1.Type.Boolean() }) }, 400: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String(), validationErrors: typebox_1.Type.Array(typebox_1.Type.String()) }) } }, handler: async (req) => { const { name, email, age } = req.body; if (!name || !email || age < 0) { return { status: 400, body: { error: 'Validation failed', validationErrors: ['Name and email are required', 'Age must be positive'] } }; } return { status: 201, body: { id: 1, name, email, created: true } }; } }); (0, vitest_1.expect)(route.method).toBe('POST'); (0, vitest_1.expect)(route.path).toBe('/api/users'); (0, vitest_1.expect)(route.request?.body).toBeDefined(); (0, vitest_1.expect)(route.response?.[201]).toBeDefined(); (0, vitest_1.expect)(route.response?.[400]).toBeDefined(); }); (0, vitest_1.it)('should create PUT route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'PUT', request: { body: typebox_1.Type.Object({ name: typebox_1.Type.String(), email: typebox_1.Type.String(), age: typebox_1.Type.Number() }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.String(), name: typebox_1.Type.String(), email: typebox_1.Type.String(), updated: typebox_1.Type.Boolean() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String(), id: typebox_1.Type.String() }) } }, handler: async (req) => { const id = req.params.id; const { name, email, age } = req.body; if (id === '999') { return { status: 404, body: { error: 'User not found', id } }; } return { status: 200, body: { id, name, email, updated: true } }; } }); (0, vitest_1.expect)(route.method).toBe('PUT'); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(route.request?.body).toBeDefined(); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[404]).toBeDefined(); }); (0, vitest_1.it)('should create DELETE route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'DELETE', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.String(), deleted: typebox_1.Type.Boolean(), message: typebox_1.Type.String() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String(), id: typebox_1.Type.String() }) } }, handler: async (req) => { const id = req.params.id; if (id === '999') { return { status: 404, body: { error: 'User not found', id } }; } return { status: 200, body: { id, deleted: true, message: 'User deleted successfully' } }; } }); (0, vitest_1.expect)(route.method).toBe('DELETE'); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[404]).toBeDefined(); }); (0, vitest_1.it)('should create PATCH route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'PATCH', request: { body: typebox_1.Type.Object({ name: typebox_1.Type.Optional(typebox_1.Type.String()), email: typebox_1.Type.Optional(typebox_1.Type.String()), age: typebox_1.Type.Optional(typebox_1.Type.Number()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.String(), name: typebox_1.Type.String(), email: typebox_1.Type.String(), age: typebox_1.Type.Number(), updated: typebox_1.Type.Boolean() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String(), id: typebox_1.Type.String() }) } }, handler: async (req) => { const id = req.params.id; const updates = req.body; if (id === '999') { return { status: 404, body: { error: 'User not found', id } }; } // Simulate partial update const existingUser = { name: 'John Doe', email: 'john@example.com', age: 25 }; const updatedUser = { ...existingUser, ...updates }; return { status: 200, body: { id, ...updatedUser, updated: true } }; } }); (0, vitest_1.expect)(route.method).toBe('PATCH'); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(route.request?.body).toBeDefined(); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[404]).toBeDefined(); }); (0, vitest_1.it)('should create OPTIONS route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'OPTIONS', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ methods: typebox_1.Type.Array(typebox_1.Type.String()), headers: typebox_1.Type.Array(typebox_1.Type.String()) }), headers: typebox_1.Type.Object({ 'Access-Control-Allow-Methods': typebox_1.Type.String(), 'Access-Control-Allow-Headers': typebox_1.Type.String(), 'Access-Control-Max-Age': typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], headers: ['Content-Type', 'Authorization', 'X-API-Key'] }, headers: { 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key', 'Access-Control-Max-Age': '86400' } }; } }); (0, vitest_1.expect)(route.method).toBe('OPTIONS'); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[200]?.headers).toBeDefined(); }); (0, vitest_1.it)('should create HEAD route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'HEAD', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ exists: typebox_1.Type.Boolean() }), headers: typebox_1.Type.Object({ 'Content-Length': typebox_1.Type.String(), 'Last-Modified': typebox_1.Type.String(), 'ETag': typebox_1.Type.String() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String() }) } }, handler: async (req) => { const id = req.params.id; if (id === '999') { return { status: 404, body: { error: 'User not found' } }; } return { status: 200, body: { exists: true }, headers: { 'Content-Length': '0', 'Last-Modified': new Date().toUTCString(), 'ETag': `"${id}-${Date.now()}"` } }; } }); (0, vitest_1.expect)(route.method).toBe('HEAD'); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[404]).toBeDefined(); }); (0, vitest_1.it)('should handle method-specific request requirements', () => { const postRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/posts', method: 'POST', request: { body: typebox_1.Type.Object({ title: typebox_1.Type.String(), content: typebox_1.Type.String() }), headers: typebox_1.Type.Object({ 'content-type': typebox_1.Type.Literal('application/json') }) }, response: { 201: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.Number(), title: typebox_1.Type.String(), created: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 201, body: { id: 1, title: req.body.title, created: true } }; } }); const getRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/posts', method: 'GET', request: { query: typebox_1.Type.Object({ search: typebox_1.Type.Optional(typebox_1.Type.String()), category: typebox_1.Type.Optional(typebox_1.Type.String()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Array(typebox_1.Type.Object({ id: typebox_1.Type.Number(), title: typebox_1.Type.String() })) } }, handler: async (req) => { return { status: 200, body: [{ id: 1, title: 'Sample Post' }] }; } }); (0, vitest_1.expect)(postRoute.method).toBe('POST'); (0, vitest_1.expect)(postRoute.request?.body).toBeDefined(); (0, vitest_1.expect)(postRoute.request?.headers).toBeDefined(); (0, vitest_1.expect)(postRoute.request?.query).toBeUndefined(); (0, vitest_1.expect)(getRoute.method).toBe('GET'); (0, vitest_1.expect)(getRoute.request?.query).toBeDefined(); (0, vitest_1.expect)(getRoute.request?.body).toBeUndefined(); }); (0, vitest_1.it)('should handle all HTTP methods with same path', () => { const path = '/api/resource/{id}'; const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD']; methods.forEach(method => { const route = (0, createApiRoute_1.createApiRoute)({ path, method, request: ['POST', 'PUT', 'PATCH'].includes(method) ? { body: typebox_1.Type.Object({ data: typebox_1.Type.String() }) } : undefined, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.String(), method: typebox_1.Type.String(), success: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 200, body: { id: req.params.id, method, success: true } }; } }); (0, vitest_1.expect)(route.method).toBe(method); (0, vitest_1.expect)(route.path).toBe(path); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); if (['POST', 'PUT', 'PATCH'].includes(method)) { (0, vitest_1.expect)(route.request?.body).toBeDefined(); } }); }); (0, vitest_1.it)('should handle custom response codes for different methods', () => { const postRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/items', method: 'POST', request: { body: typebox_1.Type.Object({ name: typebox_1.Type.String() }) }, response: { 201: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.Number(), name: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 201, body: { id: 1, name: req.body.name } }; } }); const deleteRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/items/{id}', method: 'DELETE', response: { 204: { contentType: 'application/json', body: typebox_1.Type.Object({ deleted: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 204, body: { deleted: true } }; } }); (0, vitest_1.expect)(postRoute.method).toBe('POST'); (0, vitest_1.expect)(postRoute.response?.[201]).toBeDefined(); (0, vitest_1.expect)(postRoute.response?.[204]).toBeUndefined(); (0, vitest_1.expect)(deleteRoute.method).toBe('DELETE'); (0, vitest_1.expect)(deleteRoute.response?.[204]).toBeDefined(); (0, vitest_1.expect)(deleteRoute.response?.[201]).toBeUndefined(); }); (0, vitest_1.it)('should handle method-specific content types', () => { const fileUploadRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/files', method: 'POST', request: { headers: typebox_1.Type.Object({ 'content-type': typebox_1.Type.String(), 'content-length': typebox_1.Type.String() }) }, response: { 201: { contentType: 'application/json', body: typebox_1.Type.Object({ fileId: typebox_1.Type.String(), uploaded: typebox_1.Type.Boolean() }) } }, handler: async (req) => { return { status: 201, body: { fileId: 'file-123', uploaded: true } }; } }); const fileDownloadRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/files/{id}', method: 'GET', response: { 200: { contentType: 'application/octet-stream' }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String() }) } }, handler: async (req) => { const id = req.params.id; if (id === 'nonexistent') { return { status: 404, body: { error: 'File not found' } }; } return { status: 200, custom: (res) => { res.setHeader('Content-Disposition', `attachment; filename="${id}"`); res.send('File content'); } }; } }); (0, vitest_1.expect)(fileUploadRoute.method).toBe('POST'); (0, vitest_1.expect)(fileUploadRoute.response?.[201]?.contentType).toBe('application/json'); (0, vitest_1.expect)(fileDownloadRoute.method).toBe('GET'); (0, vitest_1.expect)(fileDownloadRoute.response?.[200]?.contentType).toBe('application/octet-stream'); (0, vitest_1.expect)(fileDownloadRoute.response?.[404]?.contentType).toBe('application/json'); }); }); //# sourceMappingURL=http-methods.test.js.map