Files
Checkmate/server/test/unit/utils/monitorCertificate.test.ts
T
gorkem-bwl f74d1d53f6 fix(monitors): check TLS certificate on the monitor's own port
fetchMonitorCertificate parsed the monitor URL but passed only the hostname
to ssl-checker, which then defaulted to 443. A monitor on a non-standard
port such as https://host:54321 therefore had the certificate of whatever
answered on 443 inspected instead of its own, surfacing as "N/A" in the
certificate expiry box when nothing was listening there.

URL.port is an empty string for default ports, so it is mapped to 443
explicitly rather than passed through Number(), which would have yielded
NaN. ssl-checker already accepts the port via SSLOptions, so no dependency
or signature change is needed.

Adds a regression suite covering the reported case (non-standard port), the
default-port fallback, an explicit :443, and the NaN guard. Verified by
reverting the fix: the three port assertions fail without it.

Fixes #3646
2026-08-25 18:14:25 -04:00

79 lines
2.7 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import { fetchMonitorCertificate } from "../../../src/api/controllers/controllerUtils.ts";
import type { Monitor } from "../../../src/domain/monitors/monitor.type.ts";
import type { SSLDetails, SSLOptions } from "ssl-checker";
// Regression cover for #3646: the certificate probe parsed the monitor URL but
// dropped its port, so a monitor on a non-standard port had its certificate
// checked on 443 and surfaced as "N/A" in the UI.
const VALID_CERT: SSLDetails = {
daysRemaining: 30,
valid: true,
validFrom: "2026-01-01T00:00:00.000Z",
validTo: "2026-12-31T00:00:00.000Z",
validFor: ["example.com"],
};
/** Records the arguments the checker was called with. */
const spyChecker = (cert: SSLDetails = VALID_CERT) => {
const calls: Array<{ hostname: string; options?: SSLOptions }> = [];
const checker = (async (hostname: string, options?: SSLOptions) => {
calls.push({ hostname, options });
return cert;
}) as unknown as typeof import("ssl-checker").default;
return { checker, calls };
};
const monitorWithUrl = (url: string) => ({ url }) as Monitor;
describe("fetchMonitorCertificate", () => {
it("probes the port named in the monitor URL", async () => {
const { checker, calls } = spyChecker();
await fetchMonitorCertificate(checker, monitorWithUrl("https://example.com:54321"));
expect(calls).toHaveLength(1);
expect(calls[0].hostname).toBe("example.com");
expect(calls[0].options?.port).toBe(54321);
});
it("falls back to 443 when the URL names no port", async () => {
const { checker, calls } = spyChecker();
await fetchMonitorCertificate(checker, monitorWithUrl("https://example.com"));
expect(calls[0].options?.port).toBe(443);
});
it("passes an explicit 443 through unchanged", async () => {
const { checker, calls } = spyChecker();
await fetchMonitorCertificate(checker, monitorWithUrl("https://example.com:443"));
expect(calls[0].options?.port).toBe(443);
});
it("never passes NaN as the port", async () => {
const { checker, calls } = spyChecker();
await fetchMonitorCertificate(checker, monitorWithUrl("https://example.com"));
expect(Number.isNaN(calls[0].options?.port)).toBe(false);
});
it("returns the certificate details it received", async () => {
const { checker } = spyChecker();
const cert = await fetchMonitorCertificate(checker, monitorWithUrl("https://example.com:8443"));
expect(cert.validTo).toBe(VALID_CERT.validTo);
});
it("throws when the certificate has no expiry", async () => {
const { checker } = spyChecker({ ...VALID_CERT, validTo: undefined as unknown as string });
await expect(fetchMonitorCertificate(checker, monitorWithUrl("https://example.com:8443"))).rejects.toThrow("Certificate not found");
});
});