@prism-engineer/router
Version:
Type-safe Express.js router with automatic client generation
426 lines • 16.9 kB
JavaScript
"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