From 238635f3f1dd8e1800c2094da6f0e8461fdd59f1 Mon Sep 17 00:00:00 2001 From: Ugwoke Levi <113690452+levoski1@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:22:59 +0000 Subject: [PATCH] test(index): add unit tests for parseConfig and shutdown --- src/index.js | 35 +++++++++++++++++-------- tests/index.test.js | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 tests/index.test.js diff --git a/src/index.js b/src/index.js index f09b9a2..e30a031 100644 --- a/src/index.js +++ b/src/index.js @@ -2,15 +2,24 @@ import "dotenv/config"; import { createServer } from "./server.js"; import { logger } from "./logger.js"; -const port = parseInt(process.env.PORT ?? "8080", 10); -const heartbeatMs = parseInt(process.env.WS_HEARTBEAT_MS ?? "30000", 10); -const maxPayloadBytes = parseInt(process.env.MAX_PAYLOAD_BYTES ?? "1024", 10); - -const { wss } = createServer({ port, heartbeatMs, maxPayloadBytes }); - -logger.info("Gateway started", { port, heartbeatMs, maxPayloadBytes }); +/** + * Parses gateway configuration from environment variables. + * @returns {{ port: number, heartbeatMs: number, maxPayloadBytes: number }} + */ +export function parseConfig() { + return { + port: parseInt(process.env.PORT ?? "8080", 10), + heartbeatMs: parseInt(process.env.WS_HEARTBEAT_MS ?? "30000", 10), + maxPayloadBytes: parseInt(process.env.MAX_PAYLOAD_BYTES ?? "1024", 10), + }; +} -function shutdown(signal) { +/** + * Initiates graceful shutdown of the WebSocket server. + * @param {import('ws').WebSocketServer} wss + * @param {string} signal + */ +export function shutdown(wss, signal) { logger.info("Shutting down", { signal }); wss.close(() => { logger.info("Server closed"); @@ -22,5 +31,11 @@ function shutdown(signal) { }, 5000); } -process.on("SIGTERM", () => shutdown("SIGTERM")); -process.on("SIGINT", () => shutdown("SIGINT")); +// Entry point — only runs when executed directly +const config = parseConfig(); +const { wss } = createServer(config); + +logger.info("Gateway started", config); + +process.on("SIGTERM", () => shutdown(wss, "SIGTERM")); +process.on("SIGINT", () => shutdown(wss, "SIGINT")); diff --git a/tests/index.test.js b/tests/index.test.js new file mode 100644 index 0000000..652f8de --- /dev/null +++ b/tests/index.test.js @@ -0,0 +1,62 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { parseConfig, shutdown } from "../src/index.js"; + +describe("parseConfig", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("returns defaults when env vars are absent", () => { + delete process.env.PORT; + delete process.env.WS_HEARTBEAT_MS; + delete process.env.MAX_PAYLOAD_BYTES; + const config = parseConfig(); + expect(config).toEqual({ port: 8080, heartbeatMs: 30000, maxPayloadBytes: 1024 }); + }); + + it("reads values from environment variables", () => { + process.env.PORT = "9090"; + process.env.WS_HEARTBEAT_MS = "15000"; + process.env.MAX_PAYLOAD_BYTES = "2048"; + const config = parseConfig(); + expect(config).toEqual({ port: 9090, heartbeatMs: 15000, maxPayloadBytes: 2048 }); + }); + + it("returns integer values", () => { + const { port, heartbeatMs, maxPayloadBytes } = parseConfig(); + expect(Number.isInteger(port)).toBe(true); + expect(Number.isInteger(heartbeatMs)).toBe(true); + expect(Number.isInteger(maxPayloadBytes)).toBe(true); + }); +}); + +describe("shutdown", () => { + let mockWss; + let exitSpy; + + beforeEach(() => { + mockWss = { close: vi.fn((cb) => cb()) }; + exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {}); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.useRealTimers(); + }); + + it("calls wss.close and then process.exit(0)", () => { + shutdown(mockWss, "SIGTERM"); + expect(mockWss.close).toHaveBeenCalledOnce(); + expect(exitSpy).toHaveBeenCalledWith(0); + }); + + it("forces process.exit(1) after 5 seconds if server does not close", () => { + mockWss.close = vi.fn(); // does NOT call callback + shutdown(mockWss, "SIGINT"); + vi.advanceTimersByTime(5000); + expect(exitSpy).toHaveBeenCalledWith(1); + }); +});