web-fetch-fixture-server.mjs 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. /**
  2. * Deterministic loopback HTTP fixture for the web-fetch snapshot scenario: a
  3. * small HTML page (headings, named entities, a GFM table, nested formatting)
  4. * on a fixed port, so recording and keyless replay drive the REAL
  5. * `dsh-web-fetch-local` transport and `dsh-tool-web` markdown rendering
  6. * without external network. The port is fixed because the fetched URL is part
  7. * of the recorded model transcript.
  8. */
  9. import { createServer } from 'node:http'
  10. /** Fixed loopback port the scenario prompt points `web_fetch` at. */
  11. const PORT = 43117
  12. const PAGE = `<!doctype html>
  13. <html><head><title>Menu</title><style>.x{color:red}</style><script>ignored()</script></head>
  14. <body>
  15. <h1>Caf&eacute; menu</h1>
  16. <p>Prices include <strong>service &amp; <em>tax</em></strong> &mdash; updated daily.</p>
  17. <ul><li>Espresso</li><li>Flat white</li></ul>
  18. <table><thead><tr><th>Drink</th><th>Price</th></tr></thead><tbody><tr><td>Espresso</td><td>&euro;2</td></tr><tr><td>Flat white</td><td>&euro;3</td></tr></tbody></table>
  19. <p>See <a href="https://fixture.invalid/specials">today&rsquo;s specials</a>.</p>
  20. </body></html>
  21. `
  22. /** Cordis plugin name. */
  23. export const name = 'web-fetch-fixture-server'
  24. /**
  25. * Start the fixture server on 127.0.0.1 and register its shutdown.
  26. * @param ctx - Cordis context; the effect disposes the server with the fiber.
  27. */
  28. export async function apply(ctx) {
  29. const server = createServer((req, res) => {
  30. if (req.url === '/menu.html') {
  31. res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
  32. res.end(PAGE)
  33. return
  34. }
  35. res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' })
  36. res.end('not found')
  37. })
  38. await new Promise((resolve, reject) => {
  39. server.once('error', reject)
  40. server.listen(PORT, '127.0.0.1', () => resolve(undefined))
  41. })
  42. // The fixture must never hold the process open past protocol shutdown.
  43. server.unref()
  44. ctx.effect(() => async () => {
  45. await new Promise((resolve, reject) => {
  46. server.close(error => error ? reject(error) : resolve(undefined))
  47. // Stop accepting first so a connection cannot arrive after the forced close.
  48. server.closeAllConnections()
  49. })
  50. }, 'web-fetch-fixture-server')
  51. }