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