Files
Checkmate/server/test/unit/validation/settingsValidation.test.ts
Craig Lawson 08f104e283 Address review on the egress recovery job (#3937)
- The egress job runs through WorkerHelper.getEgressRecoveryJob(), like
  the cleanup jobs, instead of a direct service call from the queue
- Toggling egressCheckEnabled resets the egress state and removes any
  pending recovery job
- Probes resolve and pass the proxy, so http targets honour the global
  proxy as monitors do
- PingProvider keeps IPv6 addresses intact instead of truncating at the
  first colon
- Egress targets are parsed by one function shared by the validator and
  the probe; malformed addresses such as unbalanced brackets are rejected
- Notification id pattern drops the /i flag, which produced an invalid
  OpenAPI pattern
- EgressState findSingleton reads before it writes, so a read no longer
  bumps updatedAt
- "disabled" is a distinct assessment result and is cached like any other
- Concurrent failures share an in-flight assessment for as long as it
  runs, then reuse the settled result for 5 s
- The recovery job row is only removed if nobody rescheduled it since
  this run claimed it, so a concurrent new episode keeps its job
- Deleting a notification removes it from egressNotifications

🔧
2026-09-15 08:42:09 +01:00

100 lines
4.2 KiB
TypeScript

import { describe, expect, it } from "@jest/globals";
import { updateAppSettingsBodyValidation } from "../../../src/api/validation/settingsValidation.ts";
import { MAX_EGRESS_POLL_INTERVAL_SECONDS, MAX_EGRESS_TARGETS, MIN_EGRESS_POLL_INTERVAL_SECONDS } from "../../../src/domain/egress/egress.type.ts";
const validNotificationId = "64b7f0c2a1d2e3f4a5b6c7d8";
describe("settingsValidation", () => {
describe("updateAppSettingsBodyValidation egress fields", () => {
it("accepts a full set of valid egress settings", () => {
const parsed = updateAppSettingsBodyValidation.parse({
egressCheckEnabled: true,
egressCheckTargets: ["1.1.1.1", "dns.google:53", "https://example.com/health"],
egressPollIntervalSeconds: 30,
egressNotifications: [validNotificationId],
});
expect(parsed.egressCheckEnabled).toBe(true);
expect(parsed.egressCheckTargets).toEqual(["1.1.1.1", "dns.google:53", "https://example.com/health"]);
expect(parsed.egressPollIntervalSeconds).toBe(30);
expect(parsed.egressNotifications).toEqual([validNotificationId]);
});
it("accepts a body with no egress fields", () => {
expect(() => updateAppSettingsBodyValidation.parse({ checkTTL: 30 })).not.toThrow();
});
it("accepts an empty target list (the worker falls back to the default targets)", () => {
expect(updateAppSettingsBodyValidation.parse({ egressCheckTargets: [] }).egressCheckTargets).toEqual([]);
});
it("rejects more targets than the maximum", () => {
const targets = Array.from({ length: MAX_EGRESS_TARGETS + 1 }, (_, i) => `10.0.0.${i}`);
expect(() => updateAppSettingsBodyValidation.parse({ egressCheckTargets: targets })).toThrow();
});
it("accepts IPv6 targets, bare or bracketed with a port", () => {
const targets = ["2606:4700:4700::1111", "[2606:4700:4700::1111]", "[2606:4700:4700::1111]:53", "localhost", "localhost:8080"];
expect(updateAppSettingsBodyValidation.parse({ egressCheckTargets: targets }).egressCheckTargets).toEqual(targets);
});
it("rejects targets that are not a host, host:port or http(s) URL", () => {
for (const target of ["", " ", "ftp://example.com", "bad host", "1.1.1.1;rm", "http://"]) {
expect(() => updateAppSettingsBodyValidation.parse({ egressCheckTargets: [target] })).toThrow();
}
});
it("rejects malformed addresses that the probe could never reach", () => {
for (const target of [
"[1.1.1.1",
"1.1.1.1]",
"[::1",
"::1]",
"[[::1]]",
"[not-an-address]:53",
"999.1.1.1",
"1.1.1.1:0",
"1.1.1.1:70000",
"[::1]:",
]) {
expect(() => updateAppSettingsBodyValidation.parse({ egressCheckTargets: [target] })).toThrow();
}
});
it("accepts the poll interval boundaries", () => {
expect(updateAppSettingsBodyValidation.parse({ egressPollIntervalSeconds: MIN_EGRESS_POLL_INTERVAL_SECONDS }).egressPollIntervalSeconds).toBe(
MIN_EGRESS_POLL_INTERVAL_SECONDS
);
expect(updateAppSettingsBodyValidation.parse({ egressPollIntervalSeconds: MAX_EGRESS_POLL_INTERVAL_SECONDS }).egressPollIntervalSeconds).toBe(
MAX_EGRESS_POLL_INTERVAL_SECONDS
);
});
it("rejects out-of-range and non-integer poll intervals", () => {
for (const interval of [MIN_EGRESS_POLL_INTERVAL_SECONDS - 1, MAX_EGRESS_POLL_INTERVAL_SECONDS + 1, 0, -5, 30.5, "30"]) {
expect(() => updateAppSettingsBodyValidation.parse({ egressPollIntervalSeconds: interval })).toThrow();
}
});
it("accepts an empty notification list", () => {
expect(updateAppSettingsBodyValidation.parse({ egressNotifications: [] }).egressNotifications).toEqual([]);
});
it("accepts upper-case hex notification ids", () => {
const id = "64B7F0C2A1D2E3F4A5B6C7D8";
expect(updateAppSettingsBodyValidation.parse({ egressNotifications: [id] }).egressNotifications).toEqual([id]);
});
it("rejects notification ids that are not 24-character hex", () => {
for (const id of ["not-an-id", "64b7f0c2a1d2e3f4a5b6c7d", "zzzzzzzzzzzzzzzzzzzzzzzz", 123]) {
expect(() => updateAppSettingsBodyValidation.parse({ egressNotifications: [id] })).toThrow();
}
});
it("rejects a non-boolean enabled flag", () => {
expect(() => updateAppSettingsBodyValidation.parse({ egressCheckEnabled: "yes" })).toThrow();
});
});
});