UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

426 lines 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const router_1 = require("../../router"); const createApiRoute_1 = require("../../createApiRoute"); const createAuthScheme_1 = require("../../createAuthScheme"); const typebox_1 = require("@sinclair/typebox"); (0, vitest_1.describe)('Router System - Route Registration', () => { let router; (0, vitest_1.beforeEach)(() => { router = (0, router_1.createRouter)(); vitest_1.vi.clearAllMocks(); }); (0, vitest_1.it)('should register Express app instance', () => { (0, vitest_1.expect)(router.app).toBeDefined(); (0, vitest_1.expect)(router.app).toBeInstanceOf(Function); // Express app is a function (0, vitest_1.expect)(typeof router.app.get).toBe('function'); (0, vitest_1.expect)(typeof router.app.post).toBe('function'); (0, vitest_1.expect)(typeof router.app.put).toBe('function'); (0, vitest_1.expect)(typeof router.app.delete).toBe('function'); }); (0, vitest_1.it)('should have registerRoute method', () => { (0, vitest_1.expect)(typeof router.registerRoute).toBe('function'); }); (0, vitest_1.it)('should register a basic GET route', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/test', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { message: 'test' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register a POST route with request body', () => { 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() }) }, 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 } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with path parameters', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{id}', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ id: typebox_1.Type.String(), name: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { id: req.params.id, name: 'John Doe' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with authentication middleware', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer', validate: async (req) => { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { throw new Error('Invalid authorization header'); } return { user: { id: '1', name: 'John' } }; } }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/protected', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String(), user: typebox_1.Type.Object({ id: typebox_1.Type.String(), name: typebox_1.Type.String() }) }) } }, handler: async (req) => { return { status: 200, body: { message: 'Protected resource', user: req.auth.context.user } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with multiple authentication schemes', () => { const bearerAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'bearer', validate: async (req) => ({ user: { id: '1', type: 'user' } }) }); const apiKeyAuth = (0, createAuthScheme_1.createAuthScheme)({ name: 'apiKey', validate: async (req) => ({ client: { id: '1', type: 'client' } }) }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/flexible-auth', method: 'GET', auth: [bearerAuth, apiKeyAuth], response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String(), authType: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { message: 'Authenticated', authType: req.auth.name } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with query parameters', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/search', method: 'GET', request: { query: typebox_1.Type.Object({ q: typebox_1.Type.String(), page: 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(), title: typebox_1.Type.String() })) } }, handler: async (req) => { return { status: 200, body: [{ id: 1, title: `Results for: ${req.query.q}` }] }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with headers', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/with-headers', method: 'GET', request: { headers: typebox_1.Type.Object({ 'x-api-version': typebox_1.Type.String(), 'accept': typebox_1.Type.Optional(typebox_1.Type.String()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ version: typebox_1.Type.String(), message: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { version: req.headers['x-api-version'], message: 'Headers received' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with custom response handlers', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/download', method: 'GET', response: { 200: { contentType: 'text/plain' } }, handler: async () => { return { status: 200, custom: (res) => { res.setHeader('Content-Disposition', 'attachment; filename="test.txt"'); res.send('File content'); } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register multiple routes with same path but different methods', () => { const getRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/resource', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { message: 'GET' } }; } }); const postRoute = (0, createApiRoute_1.createApiRoute)({ path: '/api/resource', method: 'POST', request: { body: typebox_1.Type.Object({ data: typebox_1.Type.String() }) }, response: { 201: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async () => { return { status: 201, body: { message: 'POST' } }; } }); (0, vitest_1.expect)(() => { router.registerRoute(getRoute); router.registerRoute(postRoute); }).not.toThrow(); }); (0, vitest_1.it)('should register routes with different HTTP methods', () => { const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD']; methods.forEach(method => { const route = (0, createApiRoute_1.createApiRoute)({ path: `/api/${method.toLowerCase()}`, 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({ method: typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { method } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); }); (0, vitest_1.it)('should handle route registration errors gracefully', () => { const invalidRoute = { path: '/invalid', method: 'GET', handler: 'not a function' // Invalid handler }; // Should not throw during registration, but might fail at runtime (0, vitest_1.expect)(() => router.registerRoute(invalidRoute)).not.toThrow(); }); (0, vitest_1.it)('should convert path parameters from {param} to :param format', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{userId}/posts/{postId}', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), postId: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { userId: req.params.userId, postId: req.params.postId } }; } }); // The router should internally convert {userId} to :userId for Express (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with response headers', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/with-response-headers', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }), headers: typebox_1.Type.Object({ 'x-custom-header': typebox_1.Type.String(), 'cache-control': typebox_1.Type.String() }) } }, handler: async () => { return { status: 200, body: { message: 'Success' }, headers: { 'x-custom-header': 'custom-value', 'cache-control': 'no-cache' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should register routes with multiple response status codes', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/multi-status', method: 'GET', request: { query: typebox_1.Type.Object({ error: typebox_1.Type.Optional(typebox_1.Type.String()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String() }) }, 500: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String() }) } }, handler: async (req) => { const shouldError = req.query.error; if (shouldError === 'not-found') { return { status: 404, body: { error: 'Not found' } }; } if (shouldError === 'server-error') { return { status: 500, body: { error: 'Server error' } }; } return { status: 200, body: { message: 'Success' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); (0, vitest_1.it)('should maintain middleware order for authenticated routes', () => { const authScheme = (0, createAuthScheme_1.createAuthScheme)({ name: 'test-auth', validate: async (req) => ({ user: { id: '1' } }) }); const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/middleware-order', method: 'GET', auth: authScheme, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ message: typebox_1.Type.String() }) } }, handler: async (req) => { return { status: 200, body: { message: 'Middleware order preserved' } }; } }); (0, vitest_1.expect)(() => router.registerRoute(route)).not.toThrow(); }); }); //# sourceMappingURL=route-registration.test.js.map