Files
Checkmate/server/test/unit/validation/monitorValidation.test.ts
2026-07-06 09:39:59 -07:00

473 lines
13 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import {
createMonitorBodyValidation,
editMonitorBodyValidation,
importMonitorsBodyValidation,
} from "../../../src/api/validation/monitorValidation.ts";
const baseDnsBody = {
name: "DNS check",
type: "dns" as const,
url: "example.com",
};
describe("monitorValidation — DNS fields", () => {
describe("createMonitorBodyValidation", () => {
it("retains dnsServer and dnsRecordType on a DNS monitor", () => {
const parsed = createMonitorBodyValidation.parse({
...baseDnsBody,
dnsServer: "8.8.8.8",
dnsRecordType: "A",
});
expect(parsed.dnsServer).toBe("8.8.8.8");
expect(parsed.dnsRecordType).toBe("A");
});
it("accepts every supported DNS record type", () => {
for (const recordType of ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] as const) {
const parsed = createMonitorBodyValidation.parse({
...baseDnsBody,
dnsServer: "1.1.1.1",
dnsRecordType: recordType,
});
expect(parsed.dnsRecordType).toBe(recordType);
}
});
it("rejects unknown DNS record types", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseDnsBody,
dnsServer: "8.8.8.8",
dnsRecordType: "BOGUS",
})
).toThrow();
});
it("treats DNS fields as optional (other monitor types still validate)", () => {
const parsed = createMonitorBodyValidation.parse({
name: "HTTP check",
type: "http",
url: "https://example.com",
});
expect(parsed.dnsServer).toBeUndefined();
expect(parsed.dnsRecordType).toBeUndefined();
});
it("rejects a DNS monitor whose url has a scheme or path", () => {
for (const badUrl of ["https://example.com", "example.com/path", "example.com:53", " example.com"]) {
expect(() =>
createMonitorBodyValidation.parse({
...baseDnsBody,
url: badUrl,
dnsServer: "8.8.8.8",
dnsRecordType: "A",
})
).toThrow();
}
});
it("rejects a DNS monitor with an invalid dnsServer", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseDnsBody,
dnsServer: "not-an-ip",
dnsRecordType: "A",
})
).toThrow();
});
it("accepts service labels with leading underscore (DMARC, SRV, ACME)", () => {
for (const url of ["_dmarc.example.com", "_imaps._tcp.example.com", "_acme-challenge.example.com"]) {
const parsed = createMonitorBodyValidation.parse({
...baseDnsBody,
url,
dnsServer: "8.8.8.8",
dnsRecordType: "TXT",
});
expect(parsed.url).toBe(url);
}
});
it("does not enforce the hostname rule for non-DNS monitors", () => {
const parsed = createMonitorBodyValidation.parse({
name: "HTTP check",
type: "http",
url: "https://example.com/some/path",
});
expect(parsed.url).toBe("https://example.com/some/path");
});
});
describe("editMonitorBodyValidation", () => {
it("retains dnsServer and dnsRecordType on edits", () => {
const parsed = editMonitorBodyValidation.parse({
dnsServer: "1.1.1.1",
dnsRecordType: "MX",
});
expect(parsed.dnsServer).toBe("1.1.1.1");
expect(parsed.dnsRecordType).toBe("MX");
});
});
describe("importMonitorsBodyValidation", () => {
it("retains dnsServer and dnsRecordType on imported DNS monitors", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [
{
name: "Imported DNS",
type: "dns",
url: "example.com",
dnsServer: "8.8.4.4",
dnsRecordType: "TXT",
},
],
});
expect(parsed.monitors[0].dnsServer).toBe("8.8.4.4");
expect(parsed.monitors[0].dnsRecordType).toBe("TXT");
});
});
});
describe("monitorValidation — strategy gating", () => {
describe("createMonitorBodyValidation", () => {
it("accepts strategy on pagespeed monitors", () => {
const parsed = createMonitorBodyValidation.parse({
name: "PS check",
type: "pagespeed",
url: "https://example.com",
strategy: "mobile",
});
expect(parsed.strategy).toBe("mobile");
});
it("accepts pagespeed monitors without a strategy", () => {
const parsed = createMonitorBodyValidation.parse({
name: "PS check",
type: "pagespeed",
url: "https://example.com",
});
expect(parsed.strategy).toBeUndefined();
});
it("rejects strategy on non-pagespeed monitor types", () => {
for (const type of ["http", "ping", "docker", "port", "game", "grpc", "websocket", "dns", "hardware"] as const) {
expect(() =>
createMonitorBodyValidation.parse({
name: "wrong-type check",
type,
url: type === "dns" ? "example.com" : "https://example.com",
strategy: "desktop",
})
).toThrow();
}
});
it("allows non-pagespeed monitors without a strategy", () => {
const parsed = createMonitorBodyValidation.parse({
name: "HTTP check",
type: "http",
url: "https://example.com",
});
expect(parsed.strategy).toBeUndefined();
});
});
describe("editMonitorBodyValidation", () => {
it("rejects strategy when type is explicitly non-pagespeed", () => {
expect(() =>
editMonitorBodyValidation.parse({
type: "http",
strategy: "mobile",
})
).toThrow();
});
it("allows strategy when type is omitted (partial edit)", () => {
const parsed = editMonitorBodyValidation.parse({
strategy: "mobile",
});
expect(parsed.strategy).toBe("mobile");
});
});
describe("importMonitorsBodyValidation", () => {
it("does not inject a default strategy on imported HTTP monitors", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [
{
name: "Imported HTTP",
type: "http",
url: "https://example.com",
},
],
});
expect(parsed.monitors[0].strategy).toBeUndefined();
});
it("rejects strategy on imported non-pagespeed monitors", () => {
expect(() =>
importMonitorsBodyValidation.parse({
monitors: [
{
name: "Imported HTTP",
type: "http",
url: "https://example.com",
strategy: "desktop",
},
],
})
).toThrow();
});
});
});
describe("monitorValidation — HEAD method gating", () => {
const baseHead = {
name: "HEAD check",
type: "http" as const,
url: "https://example.com",
method: "HEAD" as const,
};
describe("createMonitorBodyValidation", () => {
it("accepts a HEAD monitor with no body matching", () => {
const parsed = createMonitorBodyValidation.parse(baseHead);
expect(parsed.method).toBe("HEAD");
});
it("accepts GET together with advanced matching", () => {
const parsed = createMonitorBodyValidation.parse({
...baseHead,
method: "GET",
useAdvancedMatching: true,
jsonPath: "status",
});
expect(parsed.method).toBe("GET");
});
it("rejects HEAD combined with advanced matching", () => {
expect(() => createMonitorBodyValidation.parse({ ...baseHead, useAdvancedMatching: true })).toThrow();
});
it("rejects HEAD combined with a non-empty jsonPath", () => {
expect(() => createMonitorBodyValidation.parse({ ...baseHead, jsonPath: "status" })).toThrow();
});
it("allows HEAD with an empty-string jsonPath", () => {
const parsed = createMonitorBodyValidation.parse({ ...baseHead, jsonPath: "" });
expect(parsed.method).toBe("HEAD");
});
});
describe("editMonitorBodyValidation", () => {
it("rejects HEAD combined with advanced matching", () => {
expect(() => editMonitorBodyValidation.parse({ method: "HEAD", useAdvancedMatching: true })).toThrow();
});
});
describe("importMonitorsBodyValidation", () => {
it("rejects HEAD combined with a jsonPath on imported monitors", () => {
expect(() => importMonitorsBodyValidation.parse({ monitors: [{ ...baseHead, jsonPath: "status" }] })).toThrow();
});
});
});
describe("monitorValidation — regex pattern gating", () => {
const baseRegex = {
name: "regex check",
type: "http" as const,
url: "https://example.com",
useAdvancedMatching: true,
matchMethod: "regex" as const,
};
describe("createMonitorBodyValidation", () => {
it("accepts an RE2-supported regex pattern", () => {
const parsed = createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "^2\\d{2}$" });
expect(parsed.expectedValue).toBe("^2\\d{2}$");
});
it("rejects a pattern using a backreference", () => {
expect(() => createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "(a+)\\1" })).toThrow();
});
it("rejects a pattern using lookahead", () => {
expect(() => createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "foo(?=bar)" })).toThrow();
});
it("ignores an invalid regex when matchMethod is not 'regex'", () => {
const parsed = createMonitorBodyValidation.parse({ ...baseRegex, matchMethod: "include", expectedValue: "(a+)\\1" });
expect(parsed.expectedValue).toBe("(a+)\\1");
});
it("ignores an empty expectedValue", () => {
const parsed = createMonitorBodyValidation.parse({ ...baseRegex, expectedValue: "" });
expect(parsed.matchMethod).toBe("regex");
});
});
describe("editMonitorBodyValidation", () => {
it("rejects a pattern using a backreference", () => {
expect(() => editMonitorBodyValidation.parse({ matchMethod: "regex", expectedValue: "(a+)\\1" })).toThrow();
});
});
describe("importMonitorsBodyValidation", () => {
it("rejects a pattern using a backreference on imported monitors", () => {
expect(() => importMonitorsBodyValidation.parse({ monitors: [{ ...baseRegex, expectedValue: "(a+)\\1" }] })).toThrow();
});
});
});
describe("monitorValidation — customUpCodes", () => {
const baseHttpBody = {
name: "HTTP check",
type: "http" as const,
url: "https://example.com",
};
describe("createMonitorBodyValidation", () => {
it("accepts valid HTTP status codes", () => {
const parsed = createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [200, 301, 404, 503],
});
expect(parsed.customUpCodes).toEqual([200, 301, 404, 503]);
});
it("accepts non-standard HTTP status codes (Cloudflare, AWS ELB, etc.)", () => {
const parsed = createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561],
});
expect(parsed.customUpCodes).toEqual([419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561]);
});
it("rejects invalid HTTP status codes", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [5000],
})
).toThrow();
expect(() =>
createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [-1],
})
).toThrow();
});
it("rejects fractional or floating-point status codes", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [200.5],
})
).toThrow();
});
it("rejects status codes below 100", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [99],
})
).toThrow();
});
it("rejects valid-looking but mathematically unsupported status codes (e.g. 599)", () => {
expect(() =>
createMonitorBodyValidation.parse({
...baseHttpBody,
customUpCodes: [599], // Not in Node's default list nor our expanded list
})
).toThrow();
});
it("defaults to an empty array when not provided", () => {
const parsed = createMonitorBodyValidation.parse(baseHttpBody);
expect(parsed.customUpCodes).toEqual([]);
});
});
describe("editMonitorBodyValidation", () => {
it("accepts valid HTTP status codes on edits", () => {
const parsed = editMonitorBodyValidation.parse({
customUpCodes: [200, 201],
});
expect(parsed.customUpCodes).toEqual([200, 201]);
});
it("accepts non-standard HTTP status codes on edits", () => {
const parsed = editMonitorBodyValidation.parse({
customUpCodes: [419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561],
});
expect(parsed.customUpCodes).toEqual([419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561]);
});
it("rejects invalid HTTP status codes on edits", () => {
expect(() =>
editMonitorBodyValidation.parse({
customUpCodes: [9999],
})
).toThrow();
});
});
describe("importMonitorsBodyValidation", () => {
it("retains valid HTTP status codes on imported HTTP monitors", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [
{
...baseHttpBody,
customUpCodes: [404],
},
],
});
expect(parsed.monitors[0].customUpCodes).toEqual([404]);
});
it("accepts non-standard HTTP status codes on imported HTTP monitors", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [
{
...baseHttpBody,
customUpCodes: [419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561],
},
],
});
expect(parsed.monitors[0].customUpCodes).toEqual([
419, 420, 440, 449, 460, 463, 497, 499, 509, 520, 521, 522, 523, 524, 525, 526, 527, 529, 530, 561,
]);
});
it("rejects invalid HTTP status codes on imported HTTP monitors", () => {
expect(() =>
importMonitorsBodyValidation.parse({
monitors: [
{
...baseHttpBody,
customUpCodes: [5000],
},
],
})
).toThrow();
});
it("defaults to an empty array when not provided on import", () => {
const parsed = importMonitorsBodyValidation.parse({
monitors: [baseHttpBody],
});
expect(parsed.monitors[0].customUpCodes).toEqual([]);
});
});
});