Build, Test, and Push CFDM Docker Image / test (push) Successful in 4m0s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m13s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Failing after 6s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import { describe, expect, it, beforeAll, afterAll } from "vitest";
|
||
import { createServer, type Server } from "node:net";
|
||
import * as healthCheckService from "../src/services/health-check-service.js";
|
||
import type { HealthCheckTarget } from "@cfdm/shared";
|
||
|
||
function startTcpServer(): Promise<{ server: Server; port: number }> {
|
||
return new Promise((resolve) => {
|
||
const server = createServer();
|
||
server.listen(0, "127.0.0.1", () => {
|
||
const address = server.address();
|
||
const port =
|
||
typeof address === "object" && address ? address.port : 0;
|
||
resolve({ server, port });
|
||
});
|
||
});
|
||
}
|
||
|
||
describe("health-check probeTarget", () => {
|
||
let server: Server;
|
||
let port: number;
|
||
|
||
beforeAll(async () => {
|
||
const started = await startTcpServer();
|
||
server = started.server;
|
||
port = started.port;
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||
});
|
||
|
||
it("tcp probe succeeds for open port", async () => {
|
||
const target: HealthCheckTarget = {
|
||
scope: "binding",
|
||
ref_id: 1,
|
||
ip: "127.0.0.1",
|
||
hostname: "test.local",
|
||
type: "tcp",
|
||
port,
|
||
path: null,
|
||
expected_status: null,
|
||
timeout_ms: 1000,
|
||
};
|
||
const result = await healthCheckService.probeTarget(target);
|
||
expect(result.ok).toBe(true);
|
||
expect(result.error).toBeNull();
|
||
expect(result.latencyMs).toBeGreaterThanOrEqual(0);
|
||
});
|
||
|
||
it("tcp probe fails for closed port", async () => {
|
||
const target: HealthCheckTarget = {
|
||
scope: "binding",
|
||
ref_id: 1,
|
||
ip: "127.0.0.1",
|
||
hostname: "test.local",
|
||
type: "tcp",
|
||
port: 1,
|
||
path: null,
|
||
expected_status: null,
|
||
timeout_ms: 500,
|
||
};
|
||
const result = await healthCheckService.probeTarget(target);
|
||
expect(result.ok).toBe(false);
|
||
expect(result.error).not.toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("health-check state derivation via runAllChecks", () => {
|
||
it("marks ip down after threshold failures and up after recovery", async () => {
|
||
const { createMemoryDb, repos, runMigrations } = await import("@cfdm/db");
|
||
const { db, sqlite } = createMemoryDb();
|
||
runMigrations(sqlite);
|
||
|
||
const domain = repos.createDomain(db, null, "example.com", "zone-id");
|
||
const service = repos.createService(db, "Svc", "svc");
|
||
const binding = repos.insertBinding(
|
||
db,
|
||
domain.id,
|
||
service.id,
|
||
"@",
|
||
null,
|
||
);
|
||
repos.updateBindingLbConfig(db, binding.id, {
|
||
health_check_enabled: true,
|
||
health_check_type: "tcp",
|
||
health_check_port: 1,
|
||
health_check_timeout_ms: 200,
|
||
});
|
||
repos.replaceBindingIpsWithMeta(db, binding.id, [
|
||
{ ip: "127.0.0.1", weight: 1, priority: 1 },
|
||
]);
|
||
|
||
// Запуск с closed port (port 1) — должен зафиксировать down после 2 проверок
|
||
await healthCheckService.runAllChecks(db, {
|
||
thresholds: {
|
||
degradedFailures: 1,
|
||
downFailures: 2,
|
||
latencyWarnMs: 1000,
|
||
},
|
||
});
|
||
let status = repos.getIpHealthStatusRow(
|
||
db,
|
||
"binding",
|
||
binding.id,
|
||
"127.0.0.1",
|
||
);
|
||
expect(status?.status).toBe("degraded");
|
||
|
||
await healthCheckService.runAllChecks(db, {
|
||
thresholds: {
|
||
degradedFailures: 1,
|
||
downFailures: 2,
|
||
latencyWarnMs: 1000,
|
||
},
|
||
});
|
||
status = repos.getIpHealthStatusRow(
|
||
db,
|
||
"binding",
|
||
binding.id,
|
||
"127.0.0.1",
|
||
);
|
||
expect(status?.status).toBe("down");
|
||
});
|
||
});
|