From 04a39ca8520db8a9910b20b5135f7d1b478a90e5 Mon Sep 17 00:00:00 2001 From: venus Date: Sun, 13 Sep 2026 15:09:11 -0500 Subject: [PATCH] fix(healthcheck): correct arrow syntax and add unit tests --- sample-docker-compose.yml | 4 +- test/healthcheck.js | 4 +- test/healthcheck.test.js | 79 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test/healthcheck.test.js diff --git a/sample-docker-compose.yml b/sample-docker-compose.yml index 3b459c2..b35e736 100644 --- a/sample-docker-compose.yml +++ b/sample-docker-compose.yml @@ -11,9 +11,11 @@ services: container_name: river_rooks_web restart: unless-stopped healthcheck: - test: ["CMD", "node", "healthcheck.js"] + test: ["CMD", "node", "test/healthcheck.js"] interval: 30s timeout: 3s + start_period: 3s + retries: 3 environment: - "SITE_DOMAIN=${SITE_DOMAIN:?Error: SITE_DOMAIN is not set. Please define SITE_DOMAIN in .env}" - "SITE_URL=${SITE_URL:-https://${SITE_DOMAIN}}" diff --git a/test/healthcheck.js b/test/healthcheck.js index 95455e6..74805a0 100644 --- a/test/healthcheck.js +++ b/test/healthcheck.js @@ -1,5 +1,5 @@ const port = process.env.PORT || 8080; fetch(`http://127.0.0.1:${port}/healthz`) - .then((res)) => process.exit(res.ok ? 0:1)) - .catch(() process.exit(1)); + .then((res) => process.exit(res.ok ? 0 : 1)) + .catch(() => process.exit(1)); diff --git a/test/healthcheck.test.js b/test/healthcheck.test.js new file mode 100644 index 0000000..cfff444 --- /dev/null +++ b/test/healthcheck.test.js @@ -0,0 +1,79 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import http from "node:http"; +import { spawn } from "node:child_process"; + +test("Healthcheck: test/healthcheck.js script exists and parses cleanly", () => { + const scriptPath = path.resolve(process.cwd(), "test/healthcheck.js"); + assert.ok(fs.existsSync(scriptPath), "test/healthcheck.js must exist"); + + const content = fs.readFileSync(scriptPath, "utf-8"); + assert.match(content, /127\.0\.0\.1/, "Must target IPv4 loopback 127.0.0.1 to avoid Alpine IPv6 lookup failures"); + assert.match(content, /\/healthz/, "Must target the /healthz endpoint"); +}); + +test("Healthcheck: test/healthcheck.js succeeds when server returns 200 OK", async () => { + const scriptPath = path.resolve(process.cwd(), "test/healthcheck.js"); + + // Start dummy HTTP server responding 200 on /healthz + const server = http.createServer((req, res) => { + if (req.url === "/healthz") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok" })); + } else { + res.writeHead(404); + res.end(); + } + }); + + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const port = server.address().port; + + try { + const exitCode = await new Promise((resolve) => { + const child = spawn(process.execPath, [scriptPath], { + env: { ...process.env, PORT: String(port) }, + }); + child.on("close", resolve); + }); + + assert.strictEqual(exitCode, 0, "healthcheck.js must exit with code 0 on healthy endpoint"); + } finally { + await new Promise((resolve) => server.close(resolve)); + } +}); + +test("Healthcheck: test/healthcheck.js fails when connection is refused or server is 500", async () => { + const scriptPath = path.resolve(process.cwd(), "test/healthcheck.js"); + + // Pick an unused high port + const exitCodeDown = await new Promise((resolve) => { + const child = spawn(process.execPath, [scriptPath], { + env: { ...process.env, PORT: "59999" }, + }); + child.on("close", resolve); + }); + assert.strictEqual(exitCodeDown, 1, "healthcheck.js must exit with code 1 when server is unreachable"); + + // Test 500 status code + const server = http.createServer((req, res) => { + res.writeHead(500); + res.end(); + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const port = server.address().port; + + try { + const exitCode500 = await new Promise((resolve) => { + const child = spawn(process.execPath, [scriptPath], { + env: { ...process.env, PORT: String(port) }, + }); + child.on("close", resolve); + }); + assert.strictEqual(exitCode500, 1, "healthcheck.js must exit with code 1 on HTTP 500"); + } finally { + await new Promise((resolve) => server.close(resolve)); + } +});