import { expect } from "chai"; import { LoggerService, logger, minLevelFor } from "../src/lib/logger.service.js"; /** * Stubs one `console` method for one test, capturing every call instead of * actually writing to stdout/stderr — same "stub the one thing that * touches the outside world" approach `the-meal-db.test.ts` uses for * `fetch`. Restored by the caller (`afterEach` below) regardless of which * test used it. */ function stubConsoleMethod(method: "debug" | "info" | "warn" | "error") { const calls: unknown[][] = []; // biome-ignore lint/suspicious/noConsole: this *is* the console-stubbing helper — it has to read the real method to be able to restore it. Never calls it for real. const original = console[method]; console[method] = (...args: unknown[]) => { calls.push(args); }; return { calls, restore: () => (console[method] = original), }; } describe("logger.service", () => { describe("minLevelFor", () => { it("only lets warn/error through in test — Mocha's own output shouldn't get any noisier", () => { expect(minLevelFor("test")).to.equal("warn"); }); it("only lets info/warn/error through in production — debug is too verbose to ship", () => { expect(minLevelFor("production")).to.equal("info"); }); it("lets everything through, including debug, in development", () => { expect(minLevelFor("development")).to.equal("debug"); }); }); describe("logger", () => { let stub: ReturnType; afterEach(() => { stub?.restore(); }); it("emits a warn line as a single JSON object via console.warn, with a timestamp/level/message and any extra meta merged in", () => { stub = stubConsoleMethod("warn"); logger.warn("Something routine failed", { status: 404, code: 4049 }); expect(stub.calls).to.have.length(1); const [line] = stub.calls[0]; const parsed = JSON.parse(line as string); expect(parsed.level).to.equal("warn"); expect(parsed.message).to.equal("Something routine failed"); expect(parsed.status).to.equal(404); expect(parsed.code).to.equal(4049); expect(new Date(parsed.timestamp).toString()).to.not.equal("Invalid Date"); }); it("emits an error line via console.error", () => { stub = stubConsoleMethod("error"); logger.error("Something broke"); expect(stub.calls).to.have.length(1); const parsed = JSON.parse(stub.calls[0][0] as string); expect(parsed.level).to.equal("error"); }); it('drops debug/info under the test suite\'s own NODE_ENV=test (minLevelFor("test") === "warn")', () => { const debugStub = stubConsoleMethod("debug"); const infoStub = stubConsoleMethod("info"); logger.debug("Should not appear"); logger.info("Should not appear either"); expect(debugStub.calls).to.have.length(0); expect(infoStub.calls).to.have.length(0); debugStub.restore(); infoStub.restore(); }); it("never lets meta override the line's own timestamp/level/message keys", () => { stub = stubConsoleMethod("warn"); logger.warn("Real message", { message: "spoofed", level: "spoofed", timestamp: "spoofed" }); const parsed = JSON.parse(stub.calls[0][0] as string); expect(parsed.message).to.equal("Real message"); expect(parsed.level).to.equal("warn"); expect(new Date(parsed.timestamp).toString()).to.not.equal("Invalid Date"); }); it('a separate instance constructed with nodeEnv="development" lets debug through, independently of the shared logger\'s own NODE_ENV=test threshold', () => { const debugStub = stubConsoleMethod("debug"); const devLogger = new LoggerService("development"); devLogger.debug("Visible in dev"); expect(debugStub.calls).to.have.length(1); debugStub.restore(); }); }); });