@prism-engineer/router
Version:
Type-safe Express.js router with automatic client generation
152 lines • 7.14 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const supertest_1 = __importDefault(require("supertest"));
const typebox_1 = require("@sinclair/typebox");
const createApiRoute_1 = require("../../createApiRoute");
const router_1 = require("../../router");
(0, vitest_1.describe)('Response Header Validation', () => {
let router;
let app;
(0, vitest_1.beforeEach)(() => {
router = (0, router_1.createRouter)();
app = router.app;
});
(0, vitest_1.it)('should validate response headers match the schema', async () => {
const responseHeaderSchema = typebox_1.Type.Object({
'x-rate-limit': typebox_1.Type.String({ pattern: '^\\d+$' }),
'x-rate-limit-remaining': typebox_1.Type.String({ pattern: '^\\d+$' }),
'x-rate-limit-reset': typebox_1.Type.String(),
'cache-control': typebox_1.Type.Optional(typebox_1.Type.String()),
'x-custom-header': typebox_1.Type.String({ minLength: 5 })
});
const route = (0, createApiRoute_1.createApiRoute)({
path: '/api/with-headers',
method: 'GET',
response: {
200: {
contentType: 'application/json',
body: typebox_1.Type.Object({
message: typebox_1.Type.String()
}),
headers: responseHeaderSchema
}
},
handler: async (_req) => {
return {
status: 200,
body: { message: 'Success' },
headers: {
'x-rate-limit': '1000',
'x-rate-limit-remaining': '999',
'x-rate-limit-reset': '2023-01-01T00:00:00Z',
'cache-control': 'no-cache',
'x-custom-header': 'valid-value'
}
};
}
});
router.registerRoute(route);
const response = await (0, supertest_1.default)(app)
.get('/api/with-headers')
.expect(200);
(0, vitest_1.expect)(response.headers['x-rate-limit']).toBe('1000');
(0, vitest_1.expect)(response.headers['x-rate-limit-remaining']).toBe('999');
(0, vitest_1.expect)(response.headers['x-rate-limit-reset']).toBe('2023-01-01T00:00:00Z');
(0, vitest_1.expect)(response.headers['cache-control']).toBe('no-cache');
(0, vitest_1.expect)(response.headers['x-custom-header']).toBe('valid-value');
});
(0, vitest_1.it)('should handle missing optional response headers', async () => {
const responseHeaderSchema = typebox_1.Type.Object({
'x-required-header': typebox_1.Type.String(),
'x-optional-header': typebox_1.Type.Optional(typebox_1.Type.String()),
'x-version': typebox_1.Type.Union([typebox_1.Type.Literal('v1'), typebox_1.Type.Literal('v2')])
});
const route = (0, createApiRoute_1.createApiRoute)({
path: '/api/partial-headers',
method: 'GET',
response: {
200: {
contentType: 'application/json',
body: typebox_1.Type.Object({
data: typebox_1.Type.String()
}),
headers: responseHeaderSchema
}
},
handler: async (_req) => {
return {
status: 200,
body: { data: 'test' },
headers: {
'x-required-header': 'present',
'x-version': 'v2'
// x-optional-header is intentionally omitted
}
};
}
});
router.registerRoute(route);
const response = await (0, supertest_1.default)(app)
.get('/api/partial-headers')
.expect(200);
(0, vitest_1.expect)(response.headers['x-required-header']).toBe('present');
(0, vitest_1.expect)(response.headers['x-version']).toBe('v2');
(0, vitest_1.expect)(response.headers['x-optional-header']).toBeUndefined();
});
(0, vitest_1.it)('should validate response headers with complex constraints', async () => {
const complexHeaderSchema = typebox_1.Type.Object({
'content-range': typebox_1.Type.String({ pattern: '^bytes \\d+-\\d+/\\d+$' }),
'x-api-version': typebox_1.Type.Union([typebox_1.Type.Literal('v1'), typebox_1.Type.Literal('v2'), typebox_1.Type.Literal('v3')]),
'x-request-id': typebox_1.Type.String({ format: 'uuid' }),
'expires': typebox_1.Type.String(),
'x-total-count': typebox_1.Type.String({ pattern: '^\\d+$' }),
'link': typebox_1.Type.Optional(typebox_1.Type.String())
});
const route = (0, createApiRoute_1.createApiRoute)({
path: '/api/paginated',
method: 'GET',
response: {
206: {
contentType: 'application/json',
body: typebox_1.Type.Array(typebox_1.Type.Object({
id: typebox_1.Type.Number(),
name: typebox_1.Type.String()
})),
headers: complexHeaderSchema
}
},
handler: async (_req) => {
return {
status: 206,
body: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
],
headers: {
'content-range': 'bytes 0-99/200',
'x-api-version': 'v2',
'x-request-id': '123e4567-e89b-12d3-a456-426614174000',
'expires': 'Thu, 01 Dec 2023 16:00:00 GMT',
'x-total-count': '200',
'link': '<https://api.example.com/items?page=2>; rel="next"'
}
};
}
});
router.registerRoute(route);
const response = await (0, supertest_1.default)(app)
.get('/api/paginated')
.expect(206);
(0, vitest_1.expect)(response.headers['content-range']).toBe('bytes 0-99/200');
(0, vitest_1.expect)(response.headers['x-api-version']).toBe('v2');
(0, vitest_1.expect)(response.headers['x-request-id']).toBe('123e4567-e89b-12d3-a456-426614174000');
(0, vitest_1.expect)(response.headers['expires']).toBe('Thu, 01 Dec 2023 16:00:00 GMT');
(0, vitest_1.expect)(response.headers['x-total-count']).toBe('200');
(0, vitest_1.expect)(response.headers['link']).toBe('<https://api.example.com/items?page=2>; rel="next"');
});
});
//# sourceMappingURL=response-header-validation.test.js.map