import { describe, expect, it } from "vitest"; import { buildApp } from "../src/app.js"; import { loadConfig } from "../src/config.js"; import { repos, type Db } from "@cfdm/db"; import * as healthCheckService from "../src/services/health-check-service.js"; import { buildMeasurementBody, summarizeMeasurement, } from "../src/lib/globalping-client.js"; import { aggregateHealthOk } from "@cfdm/shared"; import type { HealthCheckTarget } from "@cfdm/shared"; const thresholds = { degradedFailures: 1, downFailures: 2, latencyWarnMs: 1000, successRecoveries: 2, }; function tcpTarget(overrides?: Partial): HealthCheckTarget { return { scope: "binding", ref_id: 1, ip: "203.0.113.10", hostname: "panel.example.com", type: "tcp", port: 443, path: null, expected_status: null, timeout_ms: 400, verify_tls: false, provider: "globalping", providers: ["globalping"], aggregate: "majority", ...overrides, }; } async function seedBinding( db: Db, opts: { ip: string; providers: Array<"local" | "cloudflare" | "globalping">; aggregate?: "any" | "all" | "majority"; port?: number; }, ) { const domain = repos.createDomain(db, null, "example.com", "zone-1"); const service = repos.createService(db, "Panel", "panel"); repos.setServiceEnabled(db, service.id, true); repos.replaceServiceIps(db, service.id, [opts.ip]); const binding = repos.insertBinding(db, domain.id, service.id, "panel", null); repos.replaceBindingIpsWithMeta(db, binding.id, [ { ip: opts.ip, weight: 1, priority: 1 }, ]); repos.updateBindingLbConfig(db, binding.id, { health_check_enabled: true, health_check_type: "tcp", health_check_port: opts.port ?? 1, health_check_timeout_ms: 400, health_check_providers: opts.providers, health_check_aggregate: opts.aggregate ?? "majority", }); return { service, binding, domain }; } function mockFetch(handler: (url: string, init?: RequestInit) => Response): typeof fetch { return (async (input: RequestInfo | URL, init?: RequestInit) => { const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; return handler(url, init); }) as typeof fetch; } describe("aggregateHealthOk", () => { it("any / all / majority", () => { expect(aggregateHealthOk([true, false], "any")).toBe(false); expect(aggregateHealthOk([true, false], "all")).toBe(true); expect(aggregateHealthOk([true, false], "majority")).toBe(true); expect(aggregateHealthOk([false, false], "majority")).toBe(false); expect(aggregateHealthOk([true, false, false], "majority")).toBe(false); expect(aggregateHealthOk([true, true, false], "majority")).toBe(true); expect(aggregateHealthOk([true], "majority")).toBe(true); }); }); describe("globalping mapping", () => { it("maps CFDM TCP to ping+TCP and HTTP to http+host", () => { const tcp = buildMeasurementBody(tcpTarget(), { limit: 3, locations: "World" }); expect(tcp.type).toBe("ping"); expect(tcp.measurementOptions.protocol).toBe("TCP"); expect(tcp.measurementOptions.port).toBe(443); expect(tcp.inProgressUpdates).toBe(false); const http = buildMeasurementBody( tcpTarget({ type: "http", port: 443, path: "/health", expected_status: 200 }), { limit: 2, locations: "EU,US" }, ); expect(http.type).toBe("http"); expect(http.locations).toEqual([{ magic: "EU" }, { magic: "US" }]); expect(http.measurementOptions.request).toMatchObject({ host: "panel.example.com", path: "/health", method: "GET", }); }); it("summarizes HTTP majority and TCP packet loss", () => { const httpOk = summarizeMeasurement( tcpTarget({ type: "http", expected_status: 200 }), { status: "finished", results: [ { probe: { city: "Frankfurt", country: "DE" }, result: { status: "finished", statusCode: 200, timings: { total: 40 } } }, { probe: { city: "London", country: "GB" }, result: { status: "finished", statusCode: 200, timings: { total: 50 } } }, { probe: { city: "Paris", country: "FR" }, result: { status: "finished", statusCode: 500, timings: { total: 20 } } }, ], }, ); expect(httpOk.ok).toBe(true); expect(httpOk.colo).toBe("Frankfurt, DE"); const tcpFail = summarizeMeasurement(tcpTarget(), { status: "finished", results: [ { result: { status: "finished", stats: { avg: 12, loss: 100 } } }, { result: { status: "finished", stats: { avg: 11, loss: 100 } } }, ], }); expect(tcpFail.ok).toBe(false); }); }); describe("globalping engine", () => { it("POST 202 + GET finished writes colo from probe city", async () => { const app = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const { binding } = await seedBinding(app.db, { ip: "203.0.113.40", providers: ["globalping"], port: 443, }); const fetchImpl = mockFetch((url) => { if (url.endsWith("/v1/measurements")) { return new Response(JSON.stringify({ id: "meas-1" }), { status: 202 }); } return new Response( JSON.stringify({ id: "meas-1", status: "finished", results: [ { probe: { city: "Amsterdam", country: "NL" }, result: { status: "finished", stats: { avg: 18, loss: 0 } }, }, { probe: { city: "Frankfurt", country: "DE" }, result: { status: "finished", stats: { avg: 22, loss: 0 } }, }, ], }), { status: 200 }, ); }); await healthCheckService.runAllChecks(app.db, { thresholds, probeGapMs: 0, globalping: { token: "gp_test", locations: "World", limit: 2, pollIntervalMs: 0, fetchImpl, }, }); const row = repos.getIpHealthStatusRow( app.db, "binding", binding.id, "203.0.113.40", ); expect(row?.status).toBe("up"); expect(row?.provider).toBe("globalping"); expect(row?.colo).toMatch(/Amsterdam/); await app.close(); }); it("429 fails the source and does not fall back to local", async () => { const app = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const { binding } = await seedBinding(app.db, { ip: "127.0.0.1", providers: ["globalping"], port: 1, }); const fetchImpl = mockFetch(() => new Response("rate limited", { status: 429 })); await healthCheckService.runAllChecks(app.db, { thresholds, probeGapMs: 0, globalping: { token: "gp_test", locations: "World", limit: 1, pollIntervalMs: 0, fetchImpl, }, }); const row = repos.getIpHealthStatusRow( app.db, "binding", binding.id, "127.0.0.1", ); expect(row?.last_error).toMatch(/429/i); expect(row?.provider).toBe("globalping"); await app.close(); }); it("local+globalping all keeps IP up if Globalping is ok", async () => { const app = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const { binding, service } = await seedBinding(app.db, { ip: "127.0.0.1", providers: ["local", "globalping"], aggregate: "all", port: 1, }); const fetchImpl = mockFetch((url) => { if (url.endsWith("/v1/measurements")) { return new Response(JSON.stringify({ id: "meas-2" }), { status: 202 }); } return new Response( JSON.stringify({ status: "finished", results: [ { probe: { city: "Vienna", country: "AT" }, result: { status: "finished", stats: { avg: 9, loss: 0 } } }, ], }), { status: 200 }, ); }); await healthCheckService.runAllChecks(app.db, { thresholds, probeGapMs: 0, globalping: { token: "gp_test", locations: "World", limit: 1, pollIntervalMs: 0, fetchImpl, }, }); const row = repos.getIpHealthStatusRow( app.db, "binding", binding.id, "127.0.0.1", ); expect(row?.status).toBe("up"); expect(row?.provider).toBe("aggregate"); const logs = repos.listHealthProbeLogForService(app.db, service.id); expect(logs.map((row) => row.provider).sort()).toEqual([ "globalping", "local", ]); await app.close(); }); });