test.deploy/core/adapters/site.adapter.test.ts
test.deploy/core/adapters/site.adapter.test.ts is a file in Codebase Testing. 95 lines of code and 0 definitions.
import { SERVED_CERTIFICATE, SERVED_KEY_DIGEST } from "./site.fixture.ts";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { fetchProbe, readDane } from "@banes-lab/deploy/core/adapters/site.adapter.ts";
import { tlsMissing, tlsTimeout } from "@banes-lab/deploy/configuration/strings/deployment.strings.ts";
import { Buffer } from "node:buffer";
import { TLSSocket } from "node:tls";
import { X509Certificate } from "node:crypto";
const SITE = "https://x.test";
const PORT = 443;
type Handler = (error?: Error) => void;
interface Exchange {
readonly queried: string[];
plaintext: boolean;
stalls: boolean;
}
const exchange: Exchange = { plaintext: false, queried: [], stalls: false };
const tlsSocket = function tlsSocket(): TLSSocket {
const socket: TLSSocket = Object.create(TLSSocket.prototype);
Object.defineProperty(socket, "remotePort", { value: PORT });
Object.defineProperty(socket, "getPeerCertificate", {
value: () => ({ raw: new X509Certificate(SERVED_CERTIFICATE).raw }),
});
return socket;
};
vi.mock("node:dns/promises", () => ({
resolveTlsa: async (name: string) => {
exchange.queried.push(name);
return Promise.resolve([
{ certUsage: 3, data: Uint8Array.from(Buffer.from(SERVED_KEY_DIGEST, "hex")).buffer, match: 1, selector: 1 },
{ certUsage: 2, data: Uint8Array.of(1, 2).buffer, match: 1, selector: 1 },
]);
},
}));
vi.mock("node:https", () => ({
request: (_site: string, _options: unknown, answer: (response: unknown) => void) => {
const handlers = new Map<string, Handler>();
return {
destroy: (error: Error) => handlers.get("error")?.(error),
end: () => {
if (exchange.stalls) {
handlers.get("timeout")?.();
return;
}
const socket = exchange.plaintext ? {} : tlsSocket();
queueMicrotask(() => {
answer({ resume: () => undefined, socket });
});
},
on: (event: string, handler: Handler) => {
handlers.set(event, handler);
},
};
},
}));
beforeEach(() => {
exchange.queried.length = 0;
exchange.plaintext = false;
exchange.stalls = false;
});
afterEach(() => {
vi.unstubAllGlobals();
});
describe("readDane", () => {
it("digests the served key and keeps only the published 3 1 1 records at the served port", async () => {
expect(await readDane(SITE)).toStrictEqual({
name: "_443._tcp.x.test",
published: [SERVED_KEY_DIGEST],
served: SERVED_KEY_DIGEST,
});
expect(exchange.queried).toStrictEqual(["_443._tcp.x.test"]);
});
it("refuses an answer without TLS and a handshake that times out", async () => {
exchange.plaintext = true;
await expect(readDane(SITE)).rejects.toThrow(tlsMissing(SITE));
exchange.plaintext = false;
exchange.stalls = true;
await expect(readDane(SITE)).rejects.toThrow(tlsTimeout(SITE));
});
});
describe("fetchProbe", () => {
it("reads the status and type without following a redirect, and the body only for a GET", async () => {
const fetch = vi.fn(async () => {
await Promise.resolve();
return new Response("body", { headers: { "content-type": "text/markdown" }, status: 200 });
});
vi.stubGlobal("fetch", fetch);
expect(await fetchProbe("https://x.test/a.md", "GET")).toStrictEqual({
body: "body",
status: 200,
type: "text/markdown",
});
expect(await fetchProbe("https://x.test/a.md", "HEAD")).toMatchObject({ body: "", status: 200 });
expect(fetch).toHaveBeenCalledWith("https://x.test/a.md", expect.objectContaining({ redirect: "manual" }));
});
});