Files
Alfred/tests/web/utils/logger.test.ts
lanyuanxiaoyao ab7b7fb189 fix: 质量修复 — ESLint 规则 TS6 兼容 + catch 注解 + 空函数体注释化 + 后端架构对齐 + 前端红线修复
- enforce-catch-type: 增加 TSUnknownKeyword 判断,消除28个 TS6 假阳性
- no-empty-function: 统一为注释方案,移除测试/生产分支和 eslint-disable 引导
- logger.ts: 空函数体改为注释说明,删除无用 eslint-disable 指令
- 补充15处 catch 子句 : unknown 类型注解
- 清理7个测试文件失效 eslint-disable 指令
- chat/send.ts: 提取 getModelWithProvider DAO,消除直接 Drizzle 操作
- projects/update.ts: 修复死代码+条件逻辑 bug
- providers/update.ts: 补充至少一个字段校验
- 前端: inline style → CSS className, ProviderFormModal whitespace 校验
- 开发文档: 更新 Zod 使用说明(AI SDK 框架级约束)
2026-06-01 23:11:42 +08:00

389 lines
11 KiB
TypeScript

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");
});
});