Files
CertScope-Public/lib/inspect/dns.test.ts
T

79 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { collectDns, isAbsentDnsError, parseDmarcRecord, summarizeDmarc, summarizeSpf, type ResolverFactory, type ResolverLike } from "./dns";
function codedError(code: string) {
return Object.assign(new Error(code), { code });
}
describe("DNS error and family handling", () => {
it.each(["ENODATA", "ENOTFOUND", "EAI_NONAME"])('treats %s as legitimate record absence', (code) => {
expect(isAbsentDnsError({ code })).toBe(true);
});
it.each(["ESERVFAIL", "ETIMEOUT", "EREFUSED"])('treats %s as a probe failure', (code) => {
expect(isAbsentDnsError({ code })).toBe(false);
});
it("keeps IPv4 evidence when the IPv6 family times out", async () => {
const resolverFactory: ResolverFactory = () => ({
resolve4: (async () => ["1.1.1.1"]) as unknown as ResolverLike["resolve4"],
resolve6: (async () => { throw codedError("ETIMEOUT"); }) as ResolverLike["resolve6"],
resolveMx: (async () => []) as ResolverLike["resolveMx"],
resolveNs: (async () => ["ns.example.com"]) as ResolverLike["resolveNs"],
resolveTxt: (async () => [["v=spf1 -all"]]) as ResolverLike["resolveTxt"],
cancel: () => undefined,
});
const result = await collectDns("example.com", { resolverFactory });
expect(result.a).toEqual(["1.1.1.1"]);
expect(result.aaaa).toEqual([]);
expect(result.addresses).toEqual(["1.1.1.1"]);
expect(result.families).toMatchObject({
ipv4: { status: "ok", addresses: ["1.1.1.1"], error: null },
ipv6: { status: "failed", addresses: [] },
});
expect(result._meta.status).toBe("partial");
expect(result._meta.partial).toBe(true);
expect(result._meta.errors).toEqual(expect.arrayContaining([expect.objectContaining({ probe: "AAAA" })]));
});
it("represents an all-family failure as failed without fabricating addresses", async () => {
const resolverFactory: ResolverFactory = () => ({
resolve4: (async () => { throw codedError("ESERVFAIL"); }) as ResolverLike["resolve4"],
resolve6: (async () => { throw codedError("ETIMEOUT"); }) as ResolverLike["resolve6"],
resolveMx: (async () => []) as ResolverLike["resolveMx"],
resolveNs: (async () => []) as ResolverLike["resolveNs"],
resolveTxt: (async () => { throw codedError("ESERVFAIL"); }) as ResolverLike["resolveTxt"],
cancel: () => undefined,
});
const result = await collectDns("example.com", { resolverFactory });
expect(result.a).toEqual([]);
expect(result.aaaa).toEqual([]);
expect(result.families.ipv4.status).toBe("failed");
expect(result.families.ipv6.status).toBe("failed");
expect(result._meta.status).toBe("failed");
expect(result._meta.partial).toBe(false);
});
});
describe("mail record parsing", () => {
it("parses enforcing and monitor-only DMARC policies", () => {
expect(parseDmarcRecord("v=DMARC1; p=reject; pct=50")).toMatchObject({ valid: true, policy: "reject", pct: 50 });
expect(summarizeDmarc(["v=DMARC1; p=none"])).toMatchObject({ status: "monitor", policy: "none" });
expect(summarizeDmarc(["v=DMARC1; p=quarantine"])).toMatchObject({ status: "quarantine", policy: "quarantine" });
});
it("rejects malformed or duplicate DMARC records", () => {
expect(parseDmarcRecord("v=DMARC1; p=invalid").valid).toBe(false);
expect(summarizeDmarc(["v=DMARC1; p=reject", "v=DMARC1; p=none"]).status).toBe("invalid");
});
it("describes only the visible terminal SPF all mechanism", () => {
expect(summarizeSpf(["v=spf1 include:_spf.example -all"])).toMatchObject({ status: "hardfail", allQualifier: "-" });
expect(summarizeSpf(["v=spf1 ~all"])).toMatchObject({ status: "softfail", allQualifier: "~" });
expect(summarizeSpf(["v=spf1 ?all"])).toMatchObject({ status: "neutral", allQualifier: "?" });
expect(summarizeSpf(["v=spf1 +all"])).toMatchObject({ status: "permissive", allQualifier: "+" });
expect(summarizeSpf(["v=spf1 mx"])).toMatchObject({ status: "incomplete" });
expect(summarizeSpf(["v=spf1 -all include:_spf.example"])).toMatchObject({ status: "invalid" });
});
});