| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- /** Shared lifecycle for snapshot HTTP fixtures that bind an ephemeral loopback port. */
- import { createServer } from 'node:http'
- function listen(server) {
- return new Promise((resolve, reject) => {
- const onError = (error) => {
- server.off('error', onError)
- reject(error)
- }
- server.once('error', onError)
- try {
- server.listen(0, '127.0.0.1', () => {
- server.off('error', onError)
- resolve(undefined)
- })
- } catch (error) {
- server.off('error', onError)
- reject(error)
- }
- })
- }
- async function close(server) {
- if (!server.listening) return
- await new Promise((resolve, reject) => {
- server.close(error => error ? reject(error) : resolve(undefined))
- server.closeAllConnections()
- })
- }
- async function cleanup(server, onCleanup, label) {
- const errors = []
- try {
- onCleanup()
- } catch (error) {
- errors.push(error)
- }
- try {
- await close(server)
- } catch (error) {
- errors.push(error)
- }
- if (errors.length === 1) throw errors[0]
- if (errors.length > 1) throw new AggregateError(errors, `${label}: cleanup failed`)
- }
- /**
- * Start a loopback server as a Cordis effect and join cleanup with its setup.
- * @param ctx - Cordis context that owns the listener effect.
- * @param options - Fixture callbacks and the effect label used in diagnostics.
- */
- export async function applyLoopbackServerEffect(ctx, options) {
- const { label, onCleanup, onListening, requestListener } = options
- await ctx.effect(async () => {
- const server = createServer(requestListener)
- try {
- await listen(server)
- const address = server.address()
- if (address === null || typeof address === 'string') {
- throw new Error(`${label}: loopback listener has no TCP address`)
- }
- onListening(address)
- // Snapshot fixtures must never hold the process open past protocol shutdown.
- server.unref()
- return () => cleanup(server, onCleanup, label)
- } catch (cause) {
- try {
- await cleanup(server, onCleanup, label)
- } catch (cleanupError) {
- throw new AggregateError([cause, cleanupError], `${label}: setup and cleanup failed`)
- }
- throw cause
- }
- }, label)
- }
|