Files
cloudflare-domain-manager/apps/api/test/lb-reconcile.test.ts
T
DenozordecandCursor 2b150fa3a6 fix(services): снимать Down только с общего FQDN
Персональные A не крутить и не трогать при падении. Панель называется по режиму балансировки.

Co-authored-by: Cursor <[email protected]>
2026-08-20 17:51:49 +07:00

221 lines
6.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
resolveDesiredAIps,
selectActiveIpsByMode,
shouldRecordFailoverDnsDiff,
type LbIpRow,
type LbTargetConfig,
} from "../src/services/service-config-service.js";
import { WEIGHTED_SLOT_MS } from "../src/services/routing/weighted.js";
function row(
ip: string,
opts: Partial<Pick<LbIpRow, "weight" | "priority" | "health">> = {},
): LbIpRow {
return {
ip,
weight: opts.weight ?? 1,
priority: opts.priority ?? 1,
health: opts.health ?? "up",
};
}
const weightedConfig: LbTargetConfig = {
lb_mode: "weighted",
health_check_enabled: true,
};
describe("selectActiveIpsByMode", () => {
it("round_robin returns all healthy ips, falls back to all if none healthy", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { health: "up" }),
row("2.2.2.2", { health: "down" }),
row("3.3.3.3", { health: "up" }),
];
expect(selectActiveIpsByMode(config, rows).sort()).toEqual([
"1.1.1.1",
"3.3.3.3",
]);
});
it("round_robin returns all ips when none checked (unknown)", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { health: "unknown" }),
row("2.2.2.2", { health: "unknown" }),
];
expect(selectActiveIpsByMode(config, rows).sort()).toEqual([
"1.1.1.1",
"2.2.2.2",
]);
});
it("failover returns only min-priority healthy ips", () => {
const config: LbTargetConfig = {
lb_mode: "failover",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { priority: 1, health: "up" }),
row("2.2.2.2", { priority: 2, health: "up" }),
row("3.3.3.3", { priority: 1, health: "down" }),
];
expect(selectActiveIpsByMode(config, rows)).toEqual(["1.1.1.1"]);
});
it("failover falls back to min-priority ip among all when none healthy", () => {
const config: LbTargetConfig = {
lb_mode: "failover",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { priority: 3, health: "down" }),
row("2.2.2.2", { priority: 1, health: "down" }),
row("3.3.3.3", { priority: 2, health: "down" }),
];
expect(selectActiveIpsByMode(config, rows)).toEqual(["2.2.2.2"]);
});
it("weighted 1:3 picks the lighter ip on slot 0 and the heavier on slot 1", () => {
const rows = [
row("1.1.1.1", { weight: 1, health: "up" }),
row("2.2.2.2", { weight: 3, health: "up" }),
];
expect(selectActiveIpsByMode(weightedConfig, rows, 0)).toEqual(["1.1.1.1"]);
expect(selectActiveIpsByMode(weightedConfig, rows, WEIGHTED_SLOT_MS)).toEqual([
"2.2.2.2",
]);
});
it("weighted excludes down ips from the cycle", () => {
const rows = [
row("1.1.1.1", { weight: 1, health: "up" }),
row("2.2.2.2", { weight: 3, health: "down" }),
];
expect(selectActiveIpsByMode(weightedConfig, rows, 0)).toEqual(["1.1.1.1"]);
expect(
selectActiveIpsByMode(weightedConfig, rows, WEIGHTED_SLOT_MS),
).toEqual(["1.1.1.1"]);
});
it("weighted with one ip always returns that ip", () => {
expect(
selectActiveIpsByMode(weightedConfig, [row("1.1.1.1", { weight: 5 })], 0),
).toEqual(["1.1.1.1"]);
});
it("weighted with all unknown rotates across every ip", () => {
const rows = [
row("1.1.1.1", { weight: 1, health: "unknown" }),
row("2.2.2.2", { weight: 3, health: "unknown" }),
];
expect(selectActiveIpsByMode(weightedConfig, rows, 0)).toEqual(["1.1.1.1"]);
expect(selectActiveIpsByMode(weightedConfig, rows, WEIGHTED_SLOT_MS)).toEqual([
"2.2.2.2",
]);
});
it("weighted returns empty array for no rows", () => {
expect(selectActiveIpsByMode(weightedConfig, [], 0)).toEqual([]);
});
it("round_robin excludes unknown when another ip is up", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
const rows = [
row("1.1.1.1", { health: "up" }),
row("2.2.2.2", { health: "unknown" }),
];
expect(selectActiveIpsByMode(config, rows)).toEqual(["1.1.1.1"]);
});
it("returns empty array for no rows", () => {
const config: LbTargetConfig = {
lb_mode: "round_robin",
health_check_enabled: true,
};
expect(selectActiveIpsByMode(config, [])).toEqual([]);
});
});
describe("resolveDesiredAIps", () => {
it("keeps a dedicated single IP even when down", () => {
expect(
resolveDesiredAIps(
weightedConfig,
[row("1.1.1.1", { health: "down" })],
["1.1.1.1"],
),
).toEqual(["1.1.1.1"]);
});
it("applies weighted overlay on a shared pool", () => {
const rows = [
row("1.1.1.1", { weight: 1, health: "up" }),
row("2.2.2.2", { weight: 3, health: "up" }),
];
expect(
resolveDesiredAIps(weightedConfig, rows, ["1.1.1.1", "2.2.2.2"], 0),
).toEqual(selectActiveIpsByMode(weightedConfig, rows, 0));
});
});
describe("shouldRecordFailoverDnsDiff", () => {
it("skips dedicated extra-FQDN", () => {
expect(
shouldRecordFailoverDnsDiff({
configuredIps: ["1.1.1.1"],
lbMode: "failover",
added: [],
removed: ["1.1.1.1"],
downIps: new Set(["1.1.1.1"]),
}),
).toBe(false);
});
it("skips weighted live-to-live slot swap", () => {
expect(
shouldRecordFailoverDnsDiff({
configuredIps: ["1.1.1.1", "2.2.2.2"],
lbMode: "weighted",
added: ["2.2.2.2"],
removed: ["1.1.1.1"],
downIps: new Set(),
}),
).toBe(false);
});
it("logs weighted swap when a down ip leaves the pool", () => {
expect(
shouldRecordFailoverDnsDiff({
configuredIps: ["1.1.1.1", "2.2.2.2"],
lbMode: "weighted",
added: ["2.2.2.2"],
removed: ["1.1.1.1"],
downIps: new Set(["1.1.1.1"]),
}),
).toBe(true);
});
it("logs failover diffs on a shared pool", () => {
expect(
shouldRecordFailoverDnsDiff({
configuredIps: ["1.1.1.1", "2.2.2.2"],
lbMode: "failover",
added: ["2.2.2.2"],
removed: ["1.1.1.1"],
downIps: new Set(["1.1.1.1"]),
}),
).toBe(true);
});
});