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> = {}, ): 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 returns recovering unknown primary immediately", () => { const config: LbTargetConfig = { lb_mode: "failover", health_check_enabled: true, }; const rows = [ row("1.1.1.1", { priority: 1, health: "unknown" }), row("2.2.2.2", { priority: 2, health: "up" }), ]; 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 includes recovering unknown in the cycle", () => { const rows = [ row("1.1.1.1", { weight: 1, health: "up" }), 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 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 puts recovering unknown back with live ips", () => { 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).sort()).toEqual([ "1.1.1.1", "2.2.2.2", ]); }); it("round_robin keeps degraded in the pool with live ips", () => { 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: "degraded" }), ]; expect(selectActiveIpsByMode(config, rows).sort()).toEqual([ "1.1.1.1", "2.2.2.2", ]); }); 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("does not drain when the service has only one unique IP", () => { expect( resolveDesiredAIps( weightedConfig, [row("1.1.1.1", { health: "down" })], ["1.1.1.1", "1.1.1.1"], 0, ["1.1.1.1"], ), ).toEqual(["1.1.1.1", "1.1.1.1"]); }); it("does not apply overlay when service pool is a single IP", () => { const rows = [ row("1.1.1.1", { health: "up" }), row("2.2.2.2", { health: "down" }), ]; expect( resolveDesiredAIps( weightedConfig, rows, ["1.1.1.1", "2.2.2.2"], 0, ["1.1.1.1"], ), ).toEqual(["1.1.1.1", "2.2.2.2"]); }); 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 duplicate listings of the same IP", () => { expect( shouldRecordFailoverDnsDiff({ configuredIps: ["1.1.1.1", "1.1.1.1"], lbMode: "failover", added: [], removed: ["1.1.1.1"], downIps: new Set(["1.1.1.1"]), }), ).toBe(false); }); 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); }); });