Files
Alfred/tests/web/utils/logger.test.ts
lanyuanxiaoyao b1dec691e9 refactor(web): 前端目录重构 — consoles/pages → layouts/features + shared
- consoles/admin/ → layouts/admin-layout/
- consoles/workbench/ → layouts/workbench-layout/ + features/chat/
- pages/ → features/ (dashboard, models, projects, not-found)
- components/ → shared/components/
- hooks/ → shared/hooks/
- utils/ → shared/utils/
- 更新所有 import 路径 (src/web/ + tests/web/)
- 更新开发文档 (README.md, frontend.md, architecture.md)
2026-06-02 23:17:28 +08:00

389 lines
11 KiB
TypeScript

import { describe, expect, mock, test } from "bun:test";
import type { Sink } from "../../../src/web/shared/utils/logger";
import {
AntdMessageSink,
ConsoleSink,
createConsoleLogger,
createDefaultLogger,
createMemoryLogger,
createNoopLogger,
} from "../../../src/web/shared/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");
});
});