wirepig
Version:
Better testing through the power of sockets.
938 lines (925 loc) • 28.5 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.js
var src_exports = {};
__export(src_exports, {
errors: () => errors_exports,
helpers: () => helpers_default,
http: () => http_default,
tcp: () => tcp_default
});
module.exports = __toCommonJS(src_exports);
// src/http/index.js
var import_node_http = require("node:http");
var import_node_url = require("node:url");
var import_node_buffer2 = require("node:buffer");
// src/validate.js
var import_node_assert = __toESM(require("node:assert"), 1);
var import_node_util2 = require("node:util");
// src/lib.js
var import_node_buffer = require("node:buffer");
var import_node_util = require("node:util");
var isFunction = (v) => typeof v === "function";
var isString = (v) => typeof v === "string";
var isBuffer = (v) => import_node_buffer.Buffer.isBuffer(v);
var isBoolean = (v) => typeof v === "boolean";
var isPlainObject = (v) => v !== null && v !== void 0 && v.constructor === Object;
var isArray = (v) => Array.isArray(v);
var isInteger = (v) => Number.isInteger(v);
var isRegExp = (v) => v instanceof RegExp;
var isUndefined = (v) => v === void 0;
var D = (0, import_node_util.debuglog)("wirepig");
var DM = (0, import_node_util.debuglog)("wirepig.match");
var valueToString = (v, opts) => (0, import_node_util.inspect)(v, { depth: 3, breakLength: Infinity, ...opts });
var safeInvoke = (f, defaultValue, ...args) => {
if (!isFunction(f)) {
return f;
}
try {
return f(...args);
} catch (e) {
D("Unexpected exception thrown by %O:\n%s", f, e);
}
return defaultValue;
};
var _compare = (desired, actual) => {
if (desired === void 0) {
return true;
}
if (isFunction(desired)) {
return safeInvoke(desired, false, actual);
}
if (isPlainObject(desired) && isPlainObject(actual)) {
for (const [k, v] of Object.entries(desired)) {
if (!compare(v, actual[k])) {
return false;
}
}
return true;
}
if (Array.isArray(desired) && Array.isArray(actual)) {
for (const [i, v] of desired.entries()) {
if (!compare(v, actual[i])) {
return false;
}
}
return true;
}
if (isBuffer(desired) && isBuffer(actual)) {
return desired.equals(actual);
}
if (isBuffer(desired) && isString(actual)) {
return desired.toString("utf8") === actual;
}
if (isString(desired) && isBuffer(actual)) {
return desired === actual.toString("utf8");
}
if (isString(desired) && isString(actual)) {
return desired === actual;
}
if (isRegExp(desired) && isString(actual)) {
return desired.test(actual);
}
if (isRegExp(desired) && isBuffer(actual)) {
return desired.test(actual.toString("utf8"));
}
return false;
};
var compare = (desired, actual) => {
const res3 = _compare(desired, actual);
if (!res3) {
DM(
"actual value %s did not satisfy mock %s",
...[actual, desired].map((v) => valueToString(v, { colors: true }))
);
}
return res3;
};
var toStatusCode = (value, ...args) => {
value = safeInvoke(value, void 0, ...args);
return isInteger(value) ? value : 200;
};
var toHeaders = (value, ...args) => {
value = safeInvoke(value, void 0, ...args);
if (isPlainObject(value)) {
return mapObj(value, ([k, v]) => [k, toBuffer(v, ...args)]);
}
return {};
};
var toBuffer = (value, ...args) => {
value = safeInvoke(value, void 0, ...args);
if (isBuffer(value)) {
return value;
}
if (isString(value)) {
return import_node_buffer.Buffer.from(value, "utf8");
}
return import_node_buffer.Buffer.from([]);
};
var toDelay = (value, ...args) => {
value = safeInvoke(value, void 0, ...args);
return isInteger(value) ? value : 0;
};
var toDestroySocket = (value, ...args) => {
value = safeInvoke(value, void 0, ...args);
return isBoolean(value) ? value : false;
};
var toHTTPRes = (res3, req2, reqBody) => {
res3 = safeInvoke(res3, void 0, req2, reqBody);
return {
body: toBuffer(res3?.body, req2, reqBody),
statusCode: toStatusCode(res3?.statusCode, req2, reqBody),
headers: toHeaders(res3?.headers, req2, reqBody),
headerDelay: toDelay(res3?.headerDelay, req2, reqBody),
bodyDelay: toDelay(res3?.bodyDelay, req2, reqBody),
destroySocket: toDestroySocket(res3?.destroySocket, req2, reqBody)
};
};
var toTCPRes = (res3, req2) => {
res3 = safeInvoke(res3, void 0, req2);
if (!isPlainObject(res3)) {
res3 = { body: res3 };
}
return {
body: toBuffer(res3?.body, req2),
bodyDelay: toDelay(res3?.bodyDelay, req2),
destroySocket: toDestroySocket(res3?.destroySocket, req2)
};
};
var mapObj = (o, m) => Object.fromEntries(Object.entries(o).map(m));
var wait = (m) => new Promise((r2) => setTimeout(() => r2(), m));
var printMock = (mockType) => (obj2) => {
const parts = Object.entries(obj2).filter(([, v]) => !isUndefined(v)).map(([k, v]) => `${k}=${valueToString(v)}`);
return `${mockType}{${parts.join(" ")}}`;
};
// src/errors.js
var errors_exports = {};
__export(errors_exports, {
PendingMockError: () => PendingMockError,
ValidationError: () => ValidationError
});
var ValidationError = class extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
};
var PendingMockError = class extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
};
// src/validate.js
var arrayOr = (a) => {
const formatter = new Intl.ListFormat("en", {
style: "long",
type: "disjunction"
});
return formatter.format(a);
};
var r = (value, errors) => [value, errors];
var assertPredicate = (predicate) => (0, import_node_assert.default)(isFunction(predicate), "must pass a predicate");
var assertPredicates = (predicates) => {
(0, import_node_assert.default)(
Array.isArray(predicates) && predicates.length > 0,
"must pass at least one predicate"
);
(0, import_node_assert.default)(predicates.every(isFunction), "predicates must be functions");
};
var error = (path, message, value) => [
`\`${path.join(".")}\` ${message} (got ${(0, import_node_util2.inspect)(value)})`
];
var always = (value) => r(value, []);
var isString2 = (value, path = []) => {
return r(value, isString(value) ? [] : error(path, "must be string", value));
};
var isPlainObject2 = (value, path = []) => {
return r(
value,
isPlainObject(value) ? [] : error(path, "must be plain object", value)
);
};
var isBuffer2 = (value, path = []) => {
return r(value, isBuffer(value) ? [] : error(path, "must be buffer", value));
};
var isBoolean2 = (value, path = []) => {
return r(
value,
isBoolean(value) ? [] : error(path, "must be boolean", value)
);
};
var isRegExp2 = (value, path = []) => {
return r(
value,
isRegExp(value) ? [] : error(path, "must be regular expression", value)
);
};
var isUndefined2 = (value, path = []) => {
return r(
value,
isUndefined(value) ? [] : error(path, "must be undefined", value)
);
};
var isInteger2 = (value, path = []) => {
return r(
value,
isInteger(value) ? [] : error(path, "must be integer", value)
);
};
var isArray2 = (value, path = []) => {
return r(value, isArray(value) ? [] : error(path, "must be array", value));
};
var obj = (o) => {
(0, import_node_assert.default)(isPlainObject(o), "schema must be plain object");
return (value, path = []) => {
if (!isPlainObject(value)) {
return r(value, error(path, "must be plain object", value));
}
assertPredicates(Object.values(o));
let errors = [];
const conformed = {};
for (const [k, v] of Object.entries(o)) {
const [c, keyErrors] = v(value[k], [...path, k]);
if (c !== void 0) {
conformed[k] = c;
}
errors = [...errors, ...keyErrors];
}
return r(conformed, errors);
};
};
var arr = (valuePredicate) => {
assertPredicate(valuePredicate);
return (value, path = []) => {
if (!isArray(value)) {
return r(value, error(path, "must be array", value));
}
let errors = [];
const conformed = [];
for (const [i, v] of value.entries()) {
const [c, elementErrors] = valuePredicate(v, [...path, i]);
conformed.push(c);
errors = [...errors, ...elementErrors];
}
return r(conformed, errors);
};
};
var keyvals = (keyPredicate, valuePredicate) => {
assertPredicate(keyPredicate);
assertPredicate(valuePredicate);
return (value, path = []) => {
if (!isPlainObject(value)) {
return r(value, error(path, "must be plain object", value));
}
let errors = [];
const conformed = {};
for (const [k, v] of Object.entries(value)) {
const [cK, keyErrors] = keyPredicate(k, [...path, "$key"]);
const [cV, valueErrors] = valuePredicate(v, [...path, k]);
conformed[cK] = cV;
errors = [...errors, ...keyErrors, ...valueErrors];
}
return r(conformed, errors);
};
};
var or = (...predicates) => {
assertPredicates(predicates);
return (value, path = []) => {
for (const p of predicates) {
const res3 = p(value, path);
if (res3[1].length === 0) {
return res3;
}
}
return r(
value,
error(
path,
`must satisfy one of {${predicates.map((p) => p.name).join(", ")}}`,
value
)
);
};
};
var and = (...predicates) => {
assertPredicates(predicates);
return (value, path = []) => {
let conformed = value;
for (const p of predicates) {
const [c, errors] = p(conformed, path);
conformed = c;
if (errors.length !== 0) {
return r(value, errors);
}
}
return r(conformed, []);
};
};
var alias = (p, message) => {
assertPredicate(p);
(0, import_node_assert.default)(isString(message), "must pass string alias");
return (value, path = []) => {
const [conformed, errors] = p(value, path);
return r(conformed, errors.length === 0 ? [] : error(path, message, value));
};
};
var branch = (branchPredicates, nextPredicates, branchMessage) => {
assertPredicates(branchPredicates);
assertPredicates(nextPredicates);
(0, import_node_assert.default)(
branchPredicates.length === nextPredicates.length,
"each branch must have a next predicate"
);
(0, import_node_assert.default)(isString(branchMessage), "must pass string branch message");
return (value, path = []) => {
for (const i in branchPredicates) {
const [conformed, branchErrors] = branchPredicates[i](value, path);
if (branchErrors.length === 0) {
return nextPredicates[i](conformed, path);
}
}
return r(value, error(path, branchMessage, value));
};
};
var branchWithFunction = (branchPredicates, nextPredicates, branchMessage) => {
const valuePredicate = branch(
branchPredicates,
nextPredicates,
branchMessage
);
return branch(
[...branchPredicates, isFunction2(valuePredicate)],
[...nextPredicates, always],
`${branchMessage} or function returning same`
);
};
var isFunction2 = (predicate) => {
if (predicate !== void 0) {
assertPredicates([predicate]);
}
return (value, path = []) => {
if (!isFunction(value)) {
return r(value, error(path, "must be function", value));
}
if (predicate === void 0) {
return r(value, []);
}
const lateValidator = (...args) => {
const funcPath = [...path.slice(0, -1), `${path.slice(-1)[0] ?? ""}()`];
return conform(predicate(value(...args), funcPath));
};
lateValidator[import_node_util2.inspect.custom] = () => `[Function: ${value.name}]`;
return r(lateValidator, []);
};
};
var exclusive = (groupA, groupB) => {
[groupA, groupB].forEach((group) => {
(0, import_node_assert.default)(Array.isArray(group), "groups must be arrays");
(0, import_node_assert.default)(group.length > 0, "groups must have at least 1 element");
(0, import_node_assert.default)(group.every(isString), "group elements must be strings");
});
return (value, path = []) => {
if (!isPlainObject(value)) {
return r(value, error(path, "must be plain object", value));
}
const groupADefined = groupA.filter((e) => !isUndefined(value[e]));
const groupBDefined = groupB.filter((e) => !isUndefined(value[e]));
if (groupADefined.length > 0 && groupBDefined.length > 0) {
const message = `${arrayOr(
groupADefined
)} cannot be defined at the same time as ${arrayOr(groupBDefined)}`;
return r(value, error(path, message, value));
}
return r(value, []);
};
};
var conform = ([conformed, errors]) => {
if (errors.length === 0) {
return conformed;
}
throw new ValidationError(
`Validation failed. Resolve the following issues:
${errors.map((r2) => ` * ${r2}`).join("\n")}`
);
};
// src/http/schema.js
var optComparable = alias(
or(isString2, isBuffer2, isRegExp2, isFunction2(isBoolean2), isUndefined2),
"if defined must be string, buffer, regular expression, or function"
);
var headerComparable = branch(
[optComparable, isArray2],
[always, arr(optComparable)],
"must be string, buffer, regular expresson, function, or array of same"
);
var reqHeaders = branch(
[isPlainObject2, isFunction2(isBoolean2), isUndefined2],
[keyvals(always, headerComparable), always, always],
"if defined must be plain object or function"
);
var optBufferable = alias(
or(isString2, isBuffer2, isUndefined2),
"if defined must be string or buffer"
);
var funcOptBufferable = alias(
or(optBufferable, isFunction2(optBufferable)),
"if defined must be string, buffer, or function returning same"
);
var isValidResHeaders = keyvals(always, funcOptBufferable);
var resHeaders = branchWithFunction(
[isPlainObject2, isUndefined2],
[isValidResHeaders, always],
"if defined must be plain object"
);
var isValidStatusCode = (value, path = []) => [
value,
value >= 100 && value <= 599 ? [] : error(path, "must be valid status code", value)
];
var statusCode = branchWithFunction(
[and(isInteger2, isValidStatusCode), isUndefined2],
[always, always],
"if defined must be valid HTTP status code"
);
var isPositive = (value, path = []) => [
value,
value >= 0 ? [] : error(path, "must be postive", value)
];
var isPositiveInt = and(isInteger2, isPositive);
var delay = branchWithFunction(
[isPositiveInt, isUndefined2],
[always, always],
"if defined must be positive integer"
);
var destroySocket = branchWithFunction(
[isBoolean2, isUndefined2],
[always, always],
"if defined must be boolean"
);
var reqObj = obj({
method: optComparable,
pathname: optComparable,
query: optComparable,
headers: reqHeaders,
body: optComparable
});
var req = branch(
[isPlainObject2, isFunction2(isBoolean2), isUndefined2],
[reqObj, always, always],
"if defined must be plain object or function"
);
var resObj = obj({
body: funcOptBufferable,
headers: resHeaders,
statusCode,
headerDelay: delay,
bodyDelay: delay,
destroySocket
});
var res = branchWithFunction(
[isPlainObject2, isUndefined2],
[resObj, always],
"if defined must be plain object"
);
var mockSchema = branch(
[isPlainObject2, isUndefined2],
[obj({ req, res }), always],
"if defined must be plain object"
);
var port = alias(
or(isPositiveInt, isUndefined2),
"if defined must be positive integer"
);
var httpSchema = branch(
[isPlainObject2, isUndefined2],
[obj({ port }), always],
"if defined must be plain object"
);
// src/http/index.js
var printHTTP = printMock("HTTP");
var headers = (req2) => {
const res3 = {};
let currentKey;
for (const [i, v] of req2.rawHeaders.entries()) {
if (i % 2 === 0) {
currentKey = v;
} else {
if (isUndefined(res3[currentKey])) {
res3[currentKey] = v;
} else if (isString(res3[currentKey])) {
res3[currentKey] = [res3[currentKey], v];
} else {
res3[currentKey] = [...res3[currentKey], v];
}
}
}
return res3;
};
var parseReq = (req2) => {
const parsed = new import_node_url.URL(req2.url, `http://${req2.headers.host}`);
return {
method: req2.method,
pathname: parsed.pathname,
query: parsed.search,
headers: headers(req2)
};
};
var printReq = (req2) => `[${req2.method} ${req2.url} HTTP/${req2.httpVersion}]`;
var readBody = (req2) => new Promise((resolve) => {
const body = [];
req2.on("data", (c) => body.push(c));
req2.on("end", () => resolve(import_node_buffer2.Buffer.concat(body)));
});
var Mock = (o) => {
const options = conform(mockSchema(o, ["options"])) ?? {};
let done = false;
const match = () => {
done = true;
};
const toString = () => printHTTP({ req: o?.req, res: o?.res });
const isMatch = (req2, body) => {
if (!isPending()) {
return false;
}
const { method, pathname, query, headers: headers2 } = parseReq(req2);
return compare(options.req, {
method,
pathname,
query,
headers: headers2,
body
});
};
const isPending = () => done === false;
const assertDone = () => {
if (isPending()) {
throw new PendingMockError(`Mock is still pending: ${toString()}`);
}
};
return {
options,
match,
toString,
isMatch,
isPending,
assertDone
};
};
var MockSet = () => {
let mocks = [];
const reset = ({ throwOnPending = true } = {}) => {
const pending = mocks.filter((m) => m.isPending());
mocks = [];
if (pending.length !== 0) {
if (throwOnPending) {
throw new PendingMockError(
`The following mocks are still pending: ${pending.join(", ")}`
);
}
D("discarding the following mocks: %s", pending.join(", "));
}
};
const add = (o) => {
const m = Mock(o);
mocks.push(m);
D("registering mock %s", m);
return m;
};
const handler = async (req2, res3) => {
try {
const reqBody = await readBody(req2);
D("received request %s", printReq(req2));
const m = mocks.find((m2) => m2.isMatch(req2, reqBody));
if (m !== void 0) {
D("found matching mock %s", m);
m.match();
const r2 = toHTTPRes(m.options.res, req2, reqBody);
if (r2.headerDelay > 0) {
D("delaying writing headers by %dms", r2.headerDelay);
await wait(r2.headerDelay);
}
D("writing status code %d", r2.statusCode);
D("writing headers %s", r2.headers);
res3.writeHead(r2.statusCode, r2.headers);
if (r2.bodyDelay > 0) {
D("delaying writing body by %dms", r2.bodyDelay);
res3.flushHeaders();
await wait(r2.bodyDelay);
}
if (r2.destroySocket) {
D("purposefully destroying socket");
res3.destroy();
return;
}
D("writing body %s", r2.body);
res3.end(r2.body);
} else {
D("no matching mock was found for %s", printReq(req2));
res3.writeHead(404, { "Content-Type": "text/plain" });
res3.end(`No matching mock was found for ${printReq(req2)}`);
}
} catch (e) {
console.error(e);
}
};
return { reset, add, handler };
};
var http = (o) => {
const options = conform(httpSchema(o, ["options"])) ?? {};
const { port: port2 = 0 } = options;
const connections = [];
return new Promise((resolve) => {
const ms = MockSet();
const server = (0, import_node_http.createServer)(ms.handler);
D("launching http server");
server.listen({ port: port2 });
server.on("listening", () => {
D("http server listening on port %d", server.address().port);
resolve({
port: server.address().port,
teardown: () => {
D("closing http server");
connections.forEach((c) => c.destroy());
return new Promise((r2) => server.close(() => r2()));
},
reset: (o2) => ms.reset(o2),
mock: (o2) => ms.add(o2)
});
});
server.on("connection", (c) => connections.push(c));
server.on("close", () => D("http server closed"));
});
};
var http_default = http;
// src/tcp/index.js
var import_node_net = require("node:net");
var import_node_buffer3 = require("node:buffer");
// src/tcp/schema.js
var resObj2 = obj({
body: funcOptBufferable,
bodyDelay: delay,
destroySocket
});
var res2 = branchWithFunction(
[isPlainObject2, optBufferable],
[resObj2, always],
"if defined must be object, string, or buffer"
);
var connectionPinned = alias(
exclusive(["init"], ["_pinnedTo"]),
"init not supported on a connection-pinned mock"
);
var mockSchemaObj = and(
obj({
_pinnedTo: or(isUndefined2, isPlainObject2),
init: optBufferable,
req: optComparable,
res: res2
}),
exclusive(["init"], ["req", "res"]),
connectionPinned
);
var mockSchema2 = branch(
[isPlainObject2, isUndefined2],
[mockSchemaObj, always],
"if defined must be plain object"
);
var tcpSchema = branch(
[isPlainObject2, isUndefined2],
[obj({ port }), always],
"if defined must be plain object"
);
// src/tcp/index.js
var printTCP = printMock("TCP");
var Mock2 = (o, addToMockSet) => {
const options = conform(mockSchema2(o, ["options"])) ?? {};
let done = false;
let [isHeadMock, pinnedTo] = options._pinnedTo === void 0 ? [true, { connection: null }] : [false, options._pinnedTo];
const match = (connection) => {
done = true;
if (isHeadMock) {
D("pinning head mock %s to port %s", toString(), connection.remotePort);
pinnedTo.connection = connection;
}
};
const toString = () => printTCP({ init: o?.init, req: o?.req, res: o?.res });
const isMatch = (bytes, connection) => {
if (isInit() || !isPending() || !compare(options.req, bytes)) {
return false;
}
if (isHeadMock) {
return true;
}
const matchesPinnedConnection = pinnedTo.connection === connection;
if (!matchesPinnedConnection) {
DM(
"%s is pinned to connection on port %s but request was on port %s",
toString(),
pinnedTo.connection?.remotePort ?? "PENDING",
connection.remotePort
);
}
return matchesPinnedConnection;
};
const isInit = () => options.init !== void 0;
const isPending = () => done === false;
const assertDone = () => {
if (isPending()) {
throw new PendingMockError(`Mock is still pending: ${toString()}`);
}
};
const mock = (o2) => addToMockSet({ ...o2, _pinnedTo: pinnedTo });
return {
options,
match,
toString,
isMatch,
isInit,
isPending,
assertDone,
mock
};
};
var MockSet2 = () => {
let mocks = [];
let connections = [];
const reset = ({ throwOnPending = true } = {}) => {
const pending = mocks.filter((m) => m.isPending());
mocks = [];
if (pending.length !== 0) {
if (throwOnPending) {
throw new PendingMockError(
`The following mocks are still pending: ${pending.join(", ")}`
);
}
D("discarding the following mocks: %s", pending.join(", "));
}
};
const add = (o) => {
const m = Mock2(o, add);
mocks.push(m);
D("registering mock %s", m);
return m;
};
const handler = (conn) => {
const DPort = (message, ...args) => D(`[port=${conn.remotePort}] ${message}`, ...args);
DPort("new connection established");
connections.push(conn);
let recv = import_node_buffer3.Buffer.from([]);
const iM = mocks.find((m) => m.isInit() && m.isPending());
if (iM !== void 0) {
DPort("found matching init mock %s", iM);
iM.match(conn);
DPort('writing "%s"', iM.options.init);
conn.write(iM.options.init);
}
conn.on("data", async (b) => {
try {
recv = import_node_buffer3.Buffer.concat([recv, b]);
DPort('received data "%s"', b);
DPort('internal receive buffer is now "%s"', recv);
const m = mocks.find((m2) => m2.isMatch(recv, conn));
if (m !== void 0) {
DPort("found matching mock %s", m);
m.match(conn);
const r2 = toTCPRes(m.options.res, recv);
recv = import_node_buffer3.Buffer.from("");
if (r2.bodyDelay > 0) {
DPort("delaying write by %dms", r2.bodyDelay);
await wait(r2.bodyDelay);
}
if (r2.destroySocket) {
DPort("purposefully destroying socket");
conn.destroy();
return;
}
DPort('writing "%s"', r2.body);
conn.write(r2.body);
} else {
DPort('no matching mock was found for "%s"', recv);
}
} catch (e) {
console.error(e);
}
});
conn.on("close", () => DPort("connection closed"));
conn.on("error", (e) => DPort("received error %s", e));
};
const teardown = () => {
for (const c of connections) {
c.destroy();
}
};
return {
reset,
add,
handler,
teardown
};
};
var tcp = (o) => {
const options = conform(tcpSchema(o, ["options"])) ?? {};
const { port: port2 = 0 } = options;
return new Promise((resolve, reject) => {
const ms = MockSet2();
const server = (0, import_node_net.createServer)({ noDelay: true }, ms.handler);
D("launching tcp server");
server.listen({ port: port2 });
server.on("listening", () => {
D("tcp server listening on port %d", server.address().port);
resolve({
port: server.address().port,
teardown: () => {
D("closing tcp server");
return new Promise((r2) => {
ms.teardown();
server.close(r2);
});
},
reset: (o2) => ms.reset(o2),
mock: (o2) => ms.add(o2)
});
});
server.on("error", (e) => {
reject(e);
D("received error %s", e);
});
server.on("close", () => D("tcp server closed"));
});
};
var tcp_default = tcp;
// src/helpers.js
var import_node_util3 = require("node:util");
var import_node_querystring = require("node:querystring");
var import_node_buffer4 = require("node:buffer");
var jsonMatch = (desired) => (actual) => (0, import_node_util3.isDeepStrictEqual)(JSON.parse(actual), desired);
var formMatch = (desired) => (actual) => {
const parsed = {};
for (const [k, v] of Object.entries((0, import_node_querystring.parse)(actual.toString("utf8")))) {
parsed[k] = v;
}
return (0, import_node_util3.isDeepStrictEqual)(parsed, desired);
};
var queryMatch = (desired) => (actual) => formMatch(desired)(actual.slice(1));
var textRes = (body, { statusCode: statusCode2 = 200, headers: headers2 = {} } = {}) => {
const bodyBuffer = import_node_buffer4.Buffer.from(body, "utf8");
return {
body: bodyBuffer,
statusCode: statusCode2,
headers: {
"content-type": "text/plain",
"content-length": bodyBuffer.length.toString(10),
...headers2
}
};
};
var jsonRes = (body, options = {}) => textRes(JSON.stringify(body), {
...options,
headers: {
"content-type": "application/json",
...options.headers
}
});
var helpers_default = {
match: {
json: jsonMatch,
form: formMatch,
query: queryMatch
},
res: {
text: textRes,
json: jsonRes
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
errors,
helpers,
http,
tcp
});