| 32 | * @param {number} port |
| 33 | */ |
| 34 | export async function createTestServer(port) { |
| 35 | /** @type {import('http').Server} */ |
| 36 | let server; |
| 37 | /** @type {{[path: string]: string | import('http').RequestListener}} */ |
| 38 | const paths = {}; |
| 39 | /** @type {Set<import('net').Socket>} */ |
| 40 | const sockets = new Set(); |
| 41 | |
| 42 | /** @type {import('http').RequestListener} */ |
| 43 | function handleRequest(req, res) { |
| 44 | const parsedURL = new URL(req.url, 'https://localhost'); |
| 45 | const pathName = parsedURL.pathname; |
| 46 | |
| 47 | if (!paths.hasOwnProperty(pathName)) { |
| 48 | res.statusCode = 404; |
| 49 | res.end('Not found'); |
| 50 | return; |
| 51 | } |
| 52 | |
| 53 | const contentOrListener = paths[pathName]; |
| 54 | |
| 55 | if (typeof contentOrListener === 'function') { |
| 56 | const listener = contentOrListener; |
| 57 | return listener(req, res); |
| 58 | } |
| 59 | |
| 60 | const content = contentOrListener; |
| 61 | const ext = pathName === '/' ? '.html' : path.extname(pathName); |
| 62 | const contentType = mimeTypes.get(ext) || 'text/plain'; |
| 63 | |
| 64 | res.statusCode = 200; |
| 65 | res.setHeader('Content-Type', contentType); |
| 66 | res.setHeader('Cache-Control', 'no-cache'); |
| 67 | res.end(content, 'utf8'); |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * @returns {Promise<void>} |
| 72 | */ |
| 73 | function start() { |
| 74 | return new Promise((resolve) => { |
| 75 | server = http |
| 76 | .createServer(handleRequest) |
| 77 | .listen(port, () => resolve()); |
| 78 | |
| 79 | server.on('connection', (socket) => { |
| 80 | sockets.add(socket); |
| 81 | socket.on('close', () => sockets.delete(socket)); |
| 82 | }); |
| 83 | }); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * @param {{[path: string]: string | import('http').RequestListener}} newPaths |
| 88 | */ |
| 89 | function setPaths(newPaths) { |
| 90 | Object.assign(paths, newPaths); |
| 91 | } |