UNPKG

@prism-engineer/router

Version:

Type-safe Express.js router with automatic client generation

415 lines 17.4 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 - Path Parameters', () => { (0, vitest_1.it)('should extract single path parameter', () => { 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) => { const id = req.params.id; return { status: 200, body: { id, name: 'John Doe' } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{id}'); (0, vitest_1.expect)(typeof route.handler).toBe('function'); }); (0, vitest_1.it)('should extract multiple path parameters', () => { 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(), title: typebox_1.Type.String() }) } }, handler: async (req) => { const userId = req.params.userId; const postId = req.params.postId; return { status: 200, body: { userId, postId, title: 'Sample Post' } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{userId}/posts/{postId}'); (0, vitest_1.expect)(typeof route.handler).toBe('function'); }); (0, vitest_1.it)('should handle complex path parameter patterns', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/organizations/{orgId}/projects/{projectId}/issues/{issueId}', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ orgId: typebox_1.Type.String(), projectId: typebox_1.Type.String(), issueId: typebox_1.Type.String(), issue: typebox_1.Type.Object({ title: typebox_1.Type.String(), status: typebox_1.Type.String() }) }) } }, handler: async (req) => { const orgId = req.params.orgId; const projectId = req.params.projectId; const issueId = req.params.issueId; return { status: 200, body: { orgId, projectId, issueId, issue: { title: 'Sample Issue', status: 'open' } } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/organizations/{orgId}/projects/{projectId}/issues/{issueId}'); (0, vitest_1.expect)(typeof route.handler).toBe('function'); }); (0, vitest_1.it)('should handle mixed path parameters with static segments', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/v1/users/{userId}/settings/profile/{profileId}', method: 'PUT', request: { body: typebox_1.Type.Object({ displayName: typebox_1.Type.String(), bio: typebox_1.Type.Optional(typebox_1.Type.String()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), profileId: typebox_1.Type.String(), updated: typebox_1.Type.Boolean() }) } }, handler: async (req) => { const userId = req.params.userId; const profileId = req.params.profileId; return { status: 200, body: { userId, profileId, updated: true } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/v1/users/{userId}/settings/profile/{profileId}'); (0, vitest_1.expect)(route.request?.body).toBeDefined(); }); (0, vitest_1.it)('should handle path parameters with query parameters', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{userId}/posts', 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()), status: typebox_1.Type.Optional(typebox_1.Type.Union([ typebox_1.Type.Literal('draft'), typebox_1.Type.Literal('published'), typebox_1.Type.Literal('archived') ])) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), posts: typebox_1.Type.Array(typebox_1.Type.Object({ id: typebox_1.Type.String(), title: typebox_1.Type.String(), status: typebox_1.Type.String() })), pagination: typebox_1.Type.Object({ page: typebox_1.Type.Number(), limit: typebox_1.Type.Number(), total: typebox_1.Type.Number() }) }) } }, handler: async (req) => { const userId = req.params.userId; const page = req.query.page; const limit = req.query.limit; const status = req.query.status; return { status: 200, body: { userId, posts: [{ id: '1', title: 'Sample Post', status: status || 'published' }], pagination: { page: page || 1, limit: limit || 10, total: 1 } } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{userId}/posts'); (0, vitest_1.expect)(route.request?.query).toBeDefined(); }); (0, vitest_1.it)('should handle path parameters with request body', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{userId}/posts/{postId}', method: 'PUT', request: { body: typebox_1.Type.Object({ title: typebox_1.Type.String(), content: typebox_1.Type.String(), tags: typebox_1.Type.Array(typebox_1.Type.String()), metadata: typebox_1.Type.Optional(typebox_1.Type.Object({ category: typebox_1.Type.String(), priority: typebox_1.Type.Number() })) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), postId: typebox_1.Type.String(), updated: typebox_1.Type.Boolean(), updatedAt: typebox_1.Type.String() }) } }, handler: async (req) => { const userId = req.params.userId; const postId = req.params.postId; const title = req.body.title; const content = req.body.content; return { status: 200, body: { userId, postId, updated: true, updatedAt: new Date().toISOString() } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{userId}/posts/{postId}'); (0, vitest_1.expect)(route.request?.body).toBeDefined(); }); (0, vitest_1.it)('should handle path parameters with headers', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{userId}/avatar', method: 'POST', request: { headers: typebox_1.Type.Object({ 'content-type': typebox_1.Type.String(), 'content-length': typebox_1.Type.String(), 'x-upload-session': typebox_1.Type.Optional(typebox_1.Type.String()) }) }, response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), avatarUrl: typebox_1.Type.String(), uploaded: typebox_1.Type.Boolean() }) } }, handler: async (req) => { const userId = req.params.userId; const contentType = req.headers['content-type']; const contentLength = req.headers['content-length']; return { status: 200, body: { userId, avatarUrl: `https://example.com/avatars/${userId}.jpg`, uploaded: true } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{userId}/avatar'); (0, vitest_1.expect)(route.request?.headers).toBeDefined(); }); (0, vitest_1.it)('should handle path parameters with different HTTP methods', () => { const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']; methods.forEach(method => { const route = (0, createApiRoute_1.createApiRoute)({ path: `/api/resources/{id}`, method, request: method === 'GET' || method === 'DELETE' ? undefined : { body: typebox_1.Type.Object({ name: typebox_1.Type.String(), value: typebox_1.Type.String() }) }, 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) => { const id = req.params.id; return { status: 200, body: { id, method, success: true } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/resources/{id}'); (0, vitest_1.expect)(route.method).toBe(method); }); }); (0, vitest_1.it)('should handle numeric-looking path parameters as strings', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/users/{userId}/orders/{orderId}', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ userId: typebox_1.Type.String(), orderId: typebox_1.Type.String(), userIdAsNumber: typebox_1.Type.Number(), orderIdAsNumber: typebox_1.Type.Number() }) } }, handler: async (req) => { // Path parameters are always strings, but can be converted const userId = req.params.userId; const orderId = req.params.orderId; const userIdAsNumber = parseInt(userId); const orderIdAsNumber = parseInt(orderId); return { status: 200, body: { userId, orderId, userIdAsNumber, orderIdAsNumber } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/users/{userId}/orders/{orderId}'); (0, vitest_1.expect)(typeof route.handler).toBe('function'); }); (0, vitest_1.it)('should handle UUID-style path parameters', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/sessions/{sessionId}/tokens/{tokenId}', method: 'DELETE', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ sessionId: typebox_1.Type.String(), tokenId: typebox_1.Type.String(), revoked: typebox_1.Type.Boolean() }) }, 404: { contentType: 'application/json', body: typebox_1.Type.Object({ error: typebox_1.Type.String(), sessionId: typebox_1.Type.String(), tokenId: typebox_1.Type.String() }) } }, handler: async (req) => { const sessionId = req.params.sessionId; const tokenId = req.params.tokenId; // Simulate UUID validation const isValidUUID = (str) => str.length === 36 && str.includes('-'); if (!isValidUUID(sessionId) || !isValidUUID(tokenId)) { return { status: 404, body: { error: 'Invalid ID format', sessionId, tokenId } }; } return { status: 200, body: { sessionId, tokenId, revoked: true } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/sessions/{sessionId}/tokens/{tokenId}'); (0, vitest_1.expect)(route.response?.[200]).toBeDefined(); (0, vitest_1.expect)(route.response?.[404]).toBeDefined(); }); (0, vitest_1.it)('should handle path parameters with special characters in route names', () => { const route = (0, createApiRoute_1.createApiRoute)({ path: '/api/files/{fileId}/versions/{versionId}', method: 'GET', response: { 200: { contentType: 'application/json', body: typebox_1.Type.Object({ fileId: typebox_1.Type.String(), versionId: typebox_1.Type.String(), fileName: typebox_1.Type.String(), version: typebox_1.Type.String() }) } }, handler: async (req) => { const fileId = req.params.fileId; const versionId = req.params.versionId; return { status: 200, body: { fileId, versionId, fileName: `file-${fileId}`, version: `v${versionId}` } }; } }); (0, vitest_1.expect)(route.path).toBe('/api/files/{fileId}/versions/{versionId}'); (0, vitest_1.expect)(typeof route.handler).toBe('function'); }); }); //# sourceMappingURL=path-parameters.test.js.map