391 lines
11 KiB
TypeScript
391 lines
11 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
|
|
import { describe, expect, mock, test } from "bun:test";
|
|
|
|
import type { Sink } from "../../../src/web/utils/logger";
|
|
|
|
import {
|
|
AntdMessageSink,
|
|
ConsoleSink,
|
|
createConsoleLogger,
|
|
createDefaultLogger,
|
|
createMemoryLogger,
|
|
createNoopLogger,
|
|
} from "../../../src/web/utils/logger";
|
|
|
|
describe("ConsoleSink", () => {
|
|
test("调试环境输出 debug 级别", () => {
|
|
const sink = new ConsoleSink(false);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.log;
|
|
console.log = spy;
|
|
|
|
sink.write("debug", "测试消息", undefined, {});
|
|
console.log = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:DEBUG\] 测试消息/);
|
|
});
|
|
|
|
test("生产环境屏蔽 debug 级别", () => {
|
|
const sink = new ConsoleSink(true);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.log;
|
|
console.log = spy;
|
|
|
|
sink.write("debug", "测试消息", undefined, {});
|
|
console.log = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("生产环境屏蔽 info 级别", () => {
|
|
const sink = new ConsoleSink(true);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.log;
|
|
console.log = spy;
|
|
|
|
sink.write("info", "测试消息", undefined, {});
|
|
console.log = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("生产环境保留 warn 级别", () => {
|
|
const sink = new ConsoleSink(true);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.warn;
|
|
console.warn = spy;
|
|
|
|
sink.write("warn", "测试消息", undefined, {});
|
|
console.warn = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:WARN\] 测试消息/);
|
|
});
|
|
|
|
test("生产环境保留 error 级别", () => {
|
|
const sink = new ConsoleSink(true);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.error;
|
|
console.error = spy;
|
|
|
|
sink.write("error", "测试消息", undefined, {});
|
|
console.error = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:ERROR\] 测试消息/);
|
|
});
|
|
|
|
test("绑定信息追加到消息后缀", () => {
|
|
const sink = new ConsoleSink(false);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.log;
|
|
console.log = spy;
|
|
|
|
sink.write("info", "测试消息", undefined, { id: "123", page: "projects" });
|
|
console.log = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:INFO\] 测试消息 \[id=123\]\[page=projects\]/);
|
|
});
|
|
|
|
test("data 透传不序列化", () => {
|
|
const sink = new ConsoleSink(false);
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.error;
|
|
console.error = spy;
|
|
const err = new Error("测试错误");
|
|
|
|
sink.write("error", "失败", err, {});
|
|
console.error = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[1]).toBe(err);
|
|
});
|
|
|
|
test("error 级别映射到 console.error", () => {
|
|
const sink = new ConsoleSink(false);
|
|
const logSpy = mock((..._args: unknown[]) => {});
|
|
const errorSpy = mock((..._args: unknown[]) => {});
|
|
const origLog = console.log;
|
|
const origError = console.error;
|
|
console.log = logSpy;
|
|
console.error = errorSpy;
|
|
|
|
sink.write("error", "错误", undefined, {});
|
|
console.log = origLog;
|
|
console.error = origError;
|
|
|
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
|
expect(logSpy).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("warn 级别映射到 console.warn", () => {
|
|
const sink = new ConsoleSink(false);
|
|
const logSpy = mock((..._args: unknown[]) => {});
|
|
const warnSpy = mock((..._args: unknown[]) => {});
|
|
const origLog = console.log;
|
|
const origWarn = console.warn;
|
|
console.log = logSpy;
|
|
console.warn = warnSpy;
|
|
|
|
sink.write("warn", "警告", undefined, {});
|
|
console.log = origLog;
|
|
console.warn = origWarn;
|
|
|
|
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
expect(logSpy).toHaveBeenCalledTimes(0);
|
|
});
|
|
});
|
|
|
|
describe("AntdMessageSink", () => {
|
|
test("warn 级别调用 message.warning", () => {
|
|
const warningSpy = mock(() => {});
|
|
const messageApi = {
|
|
error: mock(() => {}),
|
|
info: mock(() => {}),
|
|
loading: mock(() => {}),
|
|
success: mock(() => {}),
|
|
warning: warningSpy,
|
|
};
|
|
const sink = new AntdMessageSink(messageApi as never);
|
|
|
|
sink.write("warn", "操作警告", undefined, {});
|
|
|
|
expect(warningSpy).toHaveBeenCalledWith("操作警告");
|
|
});
|
|
|
|
test("error 级别调用 message.error", () => {
|
|
const errorSpy = mock(() => {});
|
|
const messageApi = {
|
|
error: errorSpy,
|
|
info: mock(() => {}),
|
|
loading: mock(() => {}),
|
|
success: mock(() => {}),
|
|
warning: mock(() => {}),
|
|
};
|
|
const sink = new AntdMessageSink(messageApi as never);
|
|
|
|
sink.write("error", "操作失败", undefined, {});
|
|
|
|
expect(errorSpy).toHaveBeenCalledWith("操作失败");
|
|
});
|
|
|
|
test("debug 级别不触发 notification", () => {
|
|
const messageApi = {
|
|
error: mock(() => {}),
|
|
info: mock(() => {}),
|
|
loading: mock(() => {}),
|
|
success: mock(() => {}),
|
|
warning: mock(() => {}),
|
|
};
|
|
const sink = new AntdMessageSink(messageApi as never);
|
|
|
|
sink.write("debug", "调试消息", undefined, {});
|
|
|
|
expect(messageApi.info).toHaveBeenCalledTimes(0);
|
|
expect(messageApi.warning).toHaveBeenCalledTimes(0);
|
|
expect(messageApi.error).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("info 级别不触发 notification", () => {
|
|
const messageApi = {
|
|
error: mock(() => {}),
|
|
info: mock(() => {}),
|
|
loading: mock(() => {}),
|
|
success: mock(() => {}),
|
|
warning: mock(() => {}),
|
|
};
|
|
const sink = new AntdMessageSink(messageApi as never);
|
|
|
|
sink.write("info", "信息消息", undefined, {});
|
|
|
|
expect(messageApi.info).toHaveBeenCalledTimes(0);
|
|
expect(messageApi.warning).toHaveBeenCalledTimes(0);
|
|
expect(messageApi.error).toHaveBeenCalledTimes(0);
|
|
});
|
|
});
|
|
|
|
describe("NoopLogger", () => {
|
|
test("所有方法静默不抛异常", () => {
|
|
const logger = createNoopLogger();
|
|
|
|
logger.debug("调试");
|
|
logger.info("信息");
|
|
logger.warn("警告");
|
|
logger.error("错误");
|
|
|
|
expect(logger.child({ page: "test" })).toBe(logger);
|
|
});
|
|
});
|
|
|
|
describe("MemoryLogger", () => {
|
|
test("记录所有级别的日志", () => {
|
|
const logger = createMemoryLogger();
|
|
|
|
logger.debug("调试");
|
|
logger.info("信息");
|
|
logger.warn("警告");
|
|
logger.error("错误");
|
|
|
|
expect(logger.entries).toEqual([
|
|
{ data: undefined, level: "debug", message: "调试" },
|
|
{ data: undefined, level: "info", message: "信息" },
|
|
{ data: undefined, level: "warn", message: "警告" },
|
|
{ data: undefined, level: "error", message: "错误" },
|
|
]);
|
|
});
|
|
|
|
test("记录附带 data 的日志", () => {
|
|
const logger = createMemoryLogger();
|
|
const err = new Error("测试");
|
|
|
|
logger.error("失败", err);
|
|
|
|
expect(logger.entries[0]).toEqual({ data: err, level: "error", message: "失败" });
|
|
});
|
|
});
|
|
|
|
describe("DefaultLogger isProduction", () => {
|
|
function createSpySink(): { entries: Array<{ data: unknown; level: string; message: string }>; sink: Sink } {
|
|
const entries: Array<{ data: unknown; level: string; message: string }> = [];
|
|
return {
|
|
entries,
|
|
sink: {
|
|
write(level, message, data) {
|
|
entries.push({ data, level, message });
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
test("isProduction=true 时 debug/info 不记录", () => {
|
|
const spy = createSpySink();
|
|
const logger = createDefaultLogger([spy.sink], true);
|
|
|
|
logger.debug("调试");
|
|
logger.info("信息");
|
|
|
|
expect(spy.entries).toHaveLength(0);
|
|
});
|
|
|
|
test("isProduction=true 时 warn/error 正常记录", () => {
|
|
const spy = createSpySink();
|
|
const logger = createDefaultLogger([spy.sink], true);
|
|
|
|
logger.warn("警告");
|
|
logger.error("错误");
|
|
|
|
expect(spy.entries).toHaveLength(2);
|
|
expect(spy.entries[0]!.level).toBe("warn");
|
|
expect(spy.entries[1]!.level).toBe("error");
|
|
});
|
|
|
|
test("isProduction=false 时 debug/info 正常记录", () => {
|
|
const spy = createSpySink();
|
|
const logger = createDefaultLogger([spy.sink], false);
|
|
|
|
logger.debug("调试");
|
|
logger.info("信息");
|
|
|
|
expect(spy.entries).toHaveLength(2);
|
|
expect(spy.entries[0]!.level).toBe("debug");
|
|
expect(spy.entries[1]!.level).toBe("info");
|
|
});
|
|
});
|
|
|
|
describe("child() 作用域", () => {
|
|
test("child 绑定信息输出到日志前缀", () => {
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.warn;
|
|
console.warn = spy;
|
|
const sink = new ConsoleSink(false);
|
|
const logger = createDefaultLogger([sink], false).child({ page: "projects" });
|
|
logger.warn("测试");
|
|
|
|
console.warn = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:WARN\] 测试 \[page=projects\]/);
|
|
});
|
|
|
|
test("嵌套 child 追加绑定", () => {
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.warn;
|
|
console.warn = spy;
|
|
const sink = new ConsoleSink(false);
|
|
const logger = createDefaultLogger([sink], false).child({ page: "projects" }).child({ action: "delete" });
|
|
logger.warn("测试");
|
|
|
|
console.warn = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:WARN\] 测试 \[page=projects\]\[action=delete\]/);
|
|
});
|
|
|
|
test("嵌套 child 同 key 覆盖", () => {
|
|
const spy = mock((..._args: unknown[]) => {});
|
|
const orig = console.warn;
|
|
console.warn = spy;
|
|
const sink = new ConsoleSink(false);
|
|
const logger = createDefaultLogger([sink], false).child({ page: "projects" }).child({ page: "models" });
|
|
logger.warn("测试");
|
|
|
|
console.warn = orig;
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
const call = spy.mock.calls[0]!;
|
|
expect(call[0]).toMatch(/\[Alfred:WARN\] 测试 \[page=models\]/);
|
|
});
|
|
});
|
|
|
|
describe("setLevel 运行时调整", () => {
|
|
test("setLevel 可提高最小输出级别", () => {
|
|
const spy = (() => {
|
|
const entries: Array<{ level: string; message: string }> = [];
|
|
return {
|
|
entries,
|
|
sink: {
|
|
write(level: string, message: string) {
|
|
entries.push({ level, message });
|
|
},
|
|
},
|
|
};
|
|
})();
|
|
const logger = createDefaultLogger([spy.sink], false);
|
|
|
|
logger.debug("调试");
|
|
expect(spy.entries).toHaveLength(1);
|
|
|
|
logger.setLevel("error");
|
|
logger.debug("调试2");
|
|
logger.warn("警告");
|
|
logger.error("错误");
|
|
|
|
expect(spy.entries).toHaveLength(2);
|
|
expect(spy.entries[1]!.level).toBe("error");
|
|
expect(spy.entries[1]!.message).toBe("错误");
|
|
});
|
|
});
|
|
|
|
describe("createConsoleLogger", () => {
|
|
test("返回非 null Logger 实例", () => {
|
|
const logger = createConsoleLogger();
|
|
expect(logger).not.toBeNull();
|
|
expect(typeof logger.debug).toBe("function");
|
|
expect(typeof logger.info).toBe("function");
|
|
expect(typeof logger.warn).toBe("function");
|
|
expect(typeof logger.error).toBe("function");
|
|
expect(typeof logger.child).toBe("function");
|
|
expect(typeof logger.setLevel).toBe("function");
|
|
});
|
|
});
|