Files
ihasmail/web/src/lib/__tests__/adminDomains.test.ts
T

70 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains";
/**
* Written the way Stalwart's BIND serializer writes it (dns-update's
* `BindSerializer`): `name IN TYPE value`, and a TXT over 255 bytes as a
* parenthesized run of quoted chunks.
*/
const long = "v=DKIM1; k=rsa; h=sha256; p=" + "A".repeat(400);
const zone = [
"example.com. IN MX 10 mail.example.com.",
'example.com. IN TXT "v=spf1 mx ra=postmaster -all"',
"v1-rsa-20260601._domainkey.example.com. IN TXT (",
...(long.match(/.{1,255}/g) ?? []).map((c) => ` "${c}"`),
")",
'_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:\\"postmaster\\"@example.com"',
"_jmap._tcp.example.com. IN SRV 0 1 443 mail.example.com.",
'example.com. IN CAA 0 issue "letsencrypt.org"',
"",
].join("\n");
describe("reading the zone file", () => {
const records = parseZoneFile(zone);
it("gives one row per record, without the root dot", () => {
expect(records.map((r) => r.type)).toEqual(["MX", "TXT", "TXT", "TXT", "SRV", "CAA"]);
expect(records[0]).toMatchObject({ name: "example.com", value: "10 mail.example.com." });
});
it("joins a split TXT record back into the value a DNS form wants", () => {
expect(records[2]!.name).toBe("v1-rsa-20260601._domainkey.example.com");
expect(records[2]!.value).toBe(long);
expect(records[2]!.line).toContain("(");
});
it("unquotes and unescapes TXT values, and leaves other types as written", () => {
expect(records[1]!.value).toBe("v=spf1 mx ra=postmaster -all");
expect(records[3]!.value).toBe('v=DMARC1; p=reject; rua=mailto:"postmaster"@example.com');
expect(records[5]!.value).toBe('0 issue "letsencrypt.org"');
});
it("keeps a line it cannot read rather than dropping it", () => {
expect(parseZoneFile("something unexpected")).toEqual([{ name: "", type: "", value: "something unexpected", line: "something unexpected" }]);
});
});
describe("domain names", () => {
it("are written back lower-case without the root dot", () => {
expect(normalizeDomain(" Example.COM. ")).toBe("example.com");
});
it("are checked loosely before the server decides", () => {
expect(looksLikeDomain("mail.example.co.uk")).toBe(true);
expect(looksLikeDomain("example")).toBe(false);
expect(looksLikeDomain("exa mple.com")).toBe(false);
expect(looksLikeDomain("-bad.example.com")).toBe(false);
});
});
describe("explaining what still uses a domain", () => {
it("counts by kind", () => {
expect(describeLinked(["Account", "Account", "DkimSignature", "MailingList", "Whatever"])).toBe("2 accounts, 1 DKIM key, 1 mailing list, 1 other item");
});
it("names a key's algorithm from its type", () => {
expect(dkimAlgorithm("Dkim1Ed25519Sha256")).toBe("Ed25519 · DKIM1");
expect(dkimAlgorithm("Dkim2RsaSha256")).toBe("RSA · DKIM2");
});
});