Files
Termix/plugins/acme-ssl/tests/backend/transport.test.ts
T

103 lines
3.4 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { PluginFetchInit } from "@termix/plugin-sdk/backend";
import { createFetchAdapter } from "../../src/backend/acme.js";
import { createTxtRecord } from "../../src/backend/cloudflare.js";
describe("createFetchAdapter", () => {
it("sends acme-client requests through ctx.fetch", async () => {
const fetch = vi.fn(
async (_url: string, _init?: PluginFetchInit) =>
new Response('{"ok":true}', {
status: 201,
headers: { "replay-nonce": "n1", location: "https://ca/acct/1" },
}),
);
const adapter = createFetchAdapter(fetch, ["ca.internal"]);
const result = await adapter({
url: "https://ca.internal/new-acct",
method: "post",
headers: { "Content-Type": "application/jose+json" },
data: '{"payload":"x"}',
validateStatus: null,
});
expect(fetch).toHaveBeenCalledWith(
"https://ca.internal/new-acct",
expect.objectContaining({
method: "POST",
body: '{"payload":"x"}',
allowPrivateHosts: ["ca.internal"],
headers: { "Content-Type": "application/jose+json" },
}),
);
expect(result.status).toBe(201);
expect(result.data).toBe('{"ok":true}');
expect(result.headers["replay-nonce"]).toBe("n1");
});
it("rejects with the response attached when validateStatus says no", async () => {
const adapter = createFetchAdapter(
async () => new Response("slow down", { status: 429 }),
);
await expect(
adapter({ url: "https://ca/x", validateStatus: () => false }),
).rejects.toMatchObject({
code: "ERR_BAD_REQUEST",
response: { status: 429, data: "slow down" },
});
});
});
describe("createTxtRecord", () => {
it("finds the zone by parent domain, creates the record and deletes it", async () => {
const calls: Array<{ url: string; method: string; body?: string }> = [];
const fetch = vi.fn(async (url: string, init?: PluginFetchInit) => {
calls.push({ url, method: init?.method ?? "GET", body: init?.body });
if (url.includes("/zones?name=example.com")) {
return Response.json({
success: true,
result: [{ id: "zone-1", name: "example.com" }],
});
}
if (url.includes("/zones?name=")) {
return Response.json({ success: true, result: [] });
}
if (init?.method === "POST") {
return Response.json({ success: true, result: { id: "rec-1" } });
}
return Response.json({ success: true, result: {} });
});
const remove = await createTxtRecord(
fetch,
"token",
"_acme-challenge.termix.example.com",
"digest",
);
const post = calls.find((call) => call.method === "POST")!;
expect(post.url).toContain("/zones/zone-1/dns_records");
expect(JSON.parse(post.body!)).toMatchObject({
type: "TXT",
name: "_acme-challenge.termix.example.com",
content: "digest",
});
await remove();
expect(calls.at(-1)).toMatchObject({
method: "DELETE",
url: expect.stringContaining("/zones/zone-1/dns_records/rec-1"),
});
});
it("surfaces the Cloudflare error message", async () => {
const fetch = async () =>
Response.json(
{ success: false, errors: [{ message: "Invalid token" }] },
{ status: 403 },
);
await expect(
createTxtRecord(fetch, "bad", "_acme-challenge.a.example", "d"),
).rejects.toThrow(/Invalid token/);
});
});