Files
Thingport/backend/tests/setup.ts
T

48 lines
1.9 KiB
TypeScript

import type { Server } from "node:http";
import { Test } from "supertest";
// supertest's request(app) listens on port 0 on every interface, then connects to 127.0.0.1 on the
// port it got. On macOS a wildcard bind succeeds even when another program already holds that port
// on 127.0.0.1 alone (VS Code helpers and limactl do), and that program takes the connection: the
// test then gets someone else's reply -- "Parse Error: Expected HTTP/", or a body without the field
// it expects -- in whichever test happens to draw the port.
//
// Binding to 127.0.0.1 itself means the port it's given is free there. That bind is asynchronous,
// though, and supertest reads the port the moment the request is built, so the bind waits until
// the request is sent and the port is filled into its URL then.
const PLACEHOLDER = "127.0.0.1:0";
type DeferredTest = InstanceType<typeof Test> & {
url: string;
_server?: Server;
unboundApp?: Server;
};
const proto = Test.prototype as unknown as {
serverAddress(this: DeferredTest, app: Server, path: string): string;
end(this: DeferredTest, fn?: (err: unknown, res: unknown) => void): DeferredTest;
};
const { serverAddress, end } = proto;
proto.serverAddress = function (app, path) {
if (app.address()) return serverAddress.call(this, app, path);
this.unboundApp = app;
return `http://${PLACEHOLDER}${path}`;
};
proto.end = function (fn) {
const app = this.unboundApp;
if (!app) return end.call(this, fn);
this.unboundApp = undefined;
app.once("error", (err) => fn?.(err, undefined));
app.listen(0, "127.0.0.1", () => {
const { port } = app.address() as { port: number };
// supertest's own field: it closes this server once the response is in.
// eslint-disable-next-line no-underscore-dangle
this._server = app;
this.url = this.url.replace(PLACEHOLDER, `127.0.0.1:${port}`);
end.call(this, fn);
});
return this;
};