Files
Checkmate/server/test/unit/utils/domainExpiry.test.ts
colinjtsen2010 75516a220d fix: handle IPs, avoid wrong parent-domain lookups, cache whois results
- Bare IP addresses and non-domain hosts (localhost, IPv6) now return a
  null expiry instead of throwing, so they no longer spam error logs.
- Extract the registrable domain with tldts instead of walking up
  hostname labels, so a subdomain can never accidentally return its
  parent domain's expiry date.
- Cache WHOIS lookups per registrable domain (1h positive, 10min
  negative) so visiting details pages does not re-query the registry
  every time.

Addresses review feedback on #3834.
2026-08-13 12:02:49 +08:00

103 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import { createDomainExpiryCache, extractDomainExpiryDate, fetchMonitorDomain } from "../../../src/api/controllers/controllerUtils.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
const makeMonitor = (url: string): Monitor =>
({
url,
type: "http",
}) as unknown as Monitor;
describe("extractDomainExpiryDate", () => {
it("returns an ISO date for ISO registry expiry values", () => {
expect(extractDomainExpiryDate({ "Registry Expiry Date": "2027-08-13T04:00:00Z" })).toBe("2027-08-13T04:00:00.000Z");
});
it("returns an ISO date for dd-MMM-yyyy expiry values", () => {
expect(extractDomainExpiryDate({ "Expiry Date": "14-Feb-2027" })).toBe("2027-02-14T00:00:00.000Z");
});
it("returns null when no expiry key exists", () => {
expect(extractDomainExpiryDate({ "Domain Name": "example.com" })).toBeNull();
});
it("returns null when the expiry value cannot be parsed", () => {
expect(extractDomainExpiryDate({ "Expiry Date": "not-a-date" })).toBeNull();
});
});
describe("fetchMonitorDomain", () => {
it("queries the registrable domain for subdomains", async () => {
const queried: string[] = [];
const client = {
domain: async (domain: string) => {
queried.push(domain);
return { "whois.verisign-grs.com": { "Expiry Date": "2027-08-13T04:00:00Z" } };
},
};
const cache = createDomainExpiryCache(60_000, 10_000);
const result = await fetchMonitorDomain(client as never, makeMonitor("https://www.example.co.uk"), cache);
expect(queried).toEqual(["example.co.uk"]);
expect(result).toEqual({ domain: "example.co.uk", expiryDate: "2027-08-13T04:00:00.000Z" });
});
it("returns null expiry for bare IP addresses without querying whois", async () => {
const client = {
domain: async () => {
throw new Error("should not be called");
},
};
const result = await fetchMonitorDomain(client as never, makeMonitor("https://1.2.3.4"));
expect(result).toEqual({ domain: null, expiryDate: null });
});
it("returns null expiry when the registry has no expiry record", async () => {
const client = {
domain: async () => ({ "whois.verisign-grs.com": { "Domain Name": "example.com" } }),
};
const result = await fetchMonitorDomain(client as never, makeMonitor("https://example.com"));
expect(result).toEqual({ domain: "example.com", expiryDate: null });
});
it("serves repeat lookups from cache", async () => {
let calls = 0;
const client = {
domain: async () => {
calls++;
return { "whois.verisign-grs.com": { "Expiry Date": "2027-08-13T04:00:00Z" } };
},
};
const cache = createDomainExpiryCache(60_000, 10_000);
const monitor = makeMonitor("https://example.com");
await fetchMonitorDomain(client as never, monitor, cache);
const second = await fetchMonitorDomain(client as never, monitor, cache);
expect(calls).toBe(1);
expect(second).toEqual({ domain: "example.com", expiryDate: "2027-08-13T04:00:00.000Z" });
});
it("re-queries after the cache expires", async () => {
let calls = 0;
const client = {
domain: async () => {
calls++;
return { "whois.verisign-grs.com": { "Expiry Date": "2027-08-13T04:00:00Z" } };
},
};
const cache = createDomainExpiryCache(-1, -1);
const monitor = makeMonitor("https://example.com");
await fetchMonitorDomain(client as never, monitor, cache);
await fetchMonitorDomain(client as never, monitor, cache);
expect(calls).toBe(2);
});
});