import { describe, expect, test } from "bun:test"; import type { Logger } from "../../src/server/logger"; import { createConsoleFallback, createMemoryLogger, createNoopLogger, REDACT_PATHS } from "../../src/server/logger"; describe("NoopLogger", () => { test("所有方法不抛异常", () => { const logger = createNoopLogger(); logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); logger.fatal("fatal"); logger.flush(); const child = logger.child({ component: "test" }); expect(child).toBeDefined(); }); }); describe("MemoryLogger", () => { test("记录所有等级日志", () => { const logger = createMemoryLogger(); logger.trace("trace-msg"); logger.debug("debug-msg"); logger.info("info-msg"); logger.warn("warn-msg"); logger.error("error-msg"); logger.fatal("fatal-msg"); expect(logger.entries).toHaveLength(6); expect(logger.entries[0]).toEqual({ level: "trace", msg: "trace-msg" }); expect(logger.entries[5]).toEqual({ level: "fatal", msg: "fatal-msg" }); }); test("记录结构化日志", () => { const logger = createMemoryLogger(); logger.info({ matched: true, targetId: "abc" }, "check complete"); expect(logger.entries).toHaveLength(1); expect(logger.entries[0]!.level).toBe("info"); expect(logger.entries[0]!.msg).toBe("check complete"); expect(logger.entries[0]!.obj).toEqual({ matched: true, targetId: "abc" }); }); test("child 返回自身", () => { const logger = createMemoryLogger(); const child = logger.child({ component: "test" }); child.info("child-msg"); expect(logger.entries).toHaveLength(1); }); test("flush 不抛异常", () => { const logger = createMemoryLogger(); logger.flush(); }); }); describe("ConsoleFallbackLogger", () => { test("不抛异常", () => { const logger = createConsoleFallback(); logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); logger.fatal("fatal"); logger.flush(); const child = logger.child({ component: "test" }); expect(child).toBeDefined(); }); }); describe("Logger 接口契约", () => { function assertLogger(logger: Logger): void { logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); logger.fatal("fatal"); logger.info({ key: "value" }, "structured"); logger.child({ component: "test" }).info("child"); logger.flush(); } test("NoopLogger 满足 Logger 接口", () => { expect(() => assertLogger(createNoopLogger())).not.toThrow(); }); test("MemoryLogger 满足 Logger 接口", () => { expect(() => assertLogger(createMemoryLogger())).not.toThrow(); }); test("ConsoleFallbackLogger 满足 Logger 接口", () => { expect(() => assertLogger(createConsoleFallback())).not.toThrow(); }); }); describe("redaction 敏感信息保护", () => { test("MemoryLogger 不做 redaction(测试用途,仅 Pino 运行时 redact)", () => { const logger = createMemoryLogger(); logger.info({ authorization: "Bearer secret", password: "hunter2" }, "test"); const entry = logger.entries[0]!; expect(entry.obj!["authorization"]).toBe("Bearer secret"); expect(entry.obj!["password"]).toBe("hunter2"); }); test("REDACT_PATHS 覆盖所有敏感字段键名", () => { const sensitiveKeys = ["authorization", "cookie", "set-cookie", "authToken", "key", "password", "token", "apiKey"]; for (const key of sensitiveKeys) { expect(REDACT_PATHS).toContain(key); expect(REDACT_PATHS).toContain(`*.${key}`); } }); });