- 新增 pino/pino-pretty/pino-roll 依赖,实现结构化日志(console pretty + file JSONL rolling) - 新增 Logger 接口及 PinoLoggerWrapper/ConsoleFallbackLogger/NoopLogger/MemoryLogger 实现 - 新增 src/pino-roll.d.ts 类型声明 - 新增 server.storage.dataDir 配置(默认 ./data,相对路径基于配置文件目录) - 新增 server.logging 配置(level/console/file/rotation,支持变量引用) - 配置文件从可选改为必填,parseRuntimeArgs 无参数时抛错 - bootstrap 创建 logger、确保 dataDir、shutdown flush、失败路径 fallback - startServer 接收 logger 并输出结构化监听日志 - ESLint 新增 no-restricted-syntax 禁止 src/server 直接 console.*(排除 logger.ts) - 更新 config.example.yaml、README.md、DEVELOPMENT.md 同步配置和日志文档 - 完善测试覆盖:logger、config、schema、bootstrap 共 150 个测试通过
172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
|
|
import { createAuthoringConfigSchema, createNormalizedConfigSchema } from "../../../src/server/config/schema/builder";
|
|
import { createConfigJsonSchema } from "../../../src/server/config/schema/export";
|
|
import {
|
|
createConfigAjv,
|
|
issuesFromAjvErrors,
|
|
validateConfigContract,
|
|
} from "../../../src/server/config/schema/validate";
|
|
|
|
describe("导出 schema 生成", () => {
|
|
test("createConfigJsonSchema 返回有效 JSON Schema", () => {
|
|
const schema = createConfigJsonSchema();
|
|
expect(schema["$schema"]).toBe("http://json-schema.org/draft-07/schema#");
|
|
expect(schema["$id"]).toBe("https://app.local/config.schema.json");
|
|
expect(schema["type"]).toBe("object");
|
|
});
|
|
});
|
|
|
|
describe("Authoring schema 校验", () => {
|
|
const ajv = createConfigAjv();
|
|
const validate = ajv.compile(createAuthoringConfigSchema());
|
|
|
|
test("接受空对象", () => {
|
|
expect(validate({})).toBe(true);
|
|
});
|
|
|
|
test("接受新布局 server.listen", () => {
|
|
expect(validate({ server: { listen: { host: "127.0.0.1", port: 3000 } } })).toBe(true);
|
|
});
|
|
|
|
test("接受变量引用语法", () => {
|
|
expect(validate({ server: { listen: { port: "${PORT|3000}" } } })).toBe(true);
|
|
});
|
|
|
|
test("接受 variables 字段", () => {
|
|
expect(validate({ variables: { HOST: "127.0.0.1" } })).toBe(true);
|
|
});
|
|
|
|
test("接受 server.storage.dataDir", () => {
|
|
expect(validate({ server: { storage: { dataDir: "./data" } } })).toBe(true);
|
|
});
|
|
|
|
test("接受 server.logging 合法配置", () => {
|
|
expect(
|
|
validate({
|
|
server: {
|
|
logging: {
|
|
console: { level: "debug" },
|
|
file: {
|
|
level: "warn",
|
|
path: "/var/log/app.log",
|
|
rotation: { frequency: "daily", maxFiles: 14, size: "50MB" },
|
|
},
|
|
level: "info",
|
|
},
|
|
},
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("接受 server.logging.level 变量引用", () => {
|
|
expect(validate({ server: { logging: { level: "${LOG_LEVEL|info}" } } })).toBe(true);
|
|
});
|
|
|
|
test("拒绝 server.logging 中未知字段", () => {
|
|
expect(validate({ server: { logging: { unknownField: true } } })).toBe(false);
|
|
});
|
|
|
|
test("拒绝 server.logging.level 非法枚举值", () => {
|
|
expect(validate({ server: { logging: { level: "verbose" } } })).toBe(false);
|
|
});
|
|
|
|
test("拒绝 unknown 字段 server.host", () => {
|
|
expect(validate({ server: { host: "127.0.0.1" } })).toBe(false);
|
|
const issues = issuesFromAjvErrors(validate.errors ?? [], {});
|
|
expect(issues.some((i) => i.code === "unknown-field")).toBe(true);
|
|
});
|
|
|
|
test("拒绝未知字段 server.port", () => {
|
|
expect(validate({ server: { port: 3000 } })).toBe(false);
|
|
const issues = issuesFromAjvErrors(validate.errors ?? [], {});
|
|
expect(issues.some((i) => i.code === "unknown-field")).toBe(true);
|
|
});
|
|
|
|
test("拒绝非法类型 port", () => {
|
|
expect(validate({ server: { listen: { port: "not-a-number" } } })).toBe(false);
|
|
});
|
|
|
|
test("拒绝超出范围的 port", () => {
|
|
expect(validate({ server: { listen: { port: 70000 } } })).toBe(false);
|
|
});
|
|
|
|
test("拒绝负数 port", () => {
|
|
expect(validate({ server: { listen: { port: -1 } } })).toBe(false);
|
|
});
|
|
|
|
test("拒绝顶层未知字段", () => {
|
|
expect(validate({ unknown: true })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("Normalized schema 校验", () => {
|
|
const ajv = createConfigAjv();
|
|
const validate = ajv.compile(createNormalizedConfigSchema());
|
|
|
|
test("接受新布局 server.listen", () => {
|
|
expect(validate({ server: { listen: { host: "127.0.0.1", port: 3000 } } })).toBe(true);
|
|
});
|
|
|
|
test("Normalized 不接受 variables 字段", () => {
|
|
expect(validate({ variables: { HOST: "127.0.0.1" } })).toBe(false);
|
|
});
|
|
|
|
test("Normalized 不接受变量引用语法", () => {
|
|
expect(validate({ server: { listen: { port: "${PORT|3000}" } } })).toBe(false);
|
|
});
|
|
|
|
test("接受 server.storage.dataDir", () => {
|
|
expect(validate({ server: { storage: { dataDir: "./data" } } })).toBe(true);
|
|
});
|
|
|
|
test("接受 server.logging 合法配置", () => {
|
|
expect(
|
|
validate({
|
|
server: {
|
|
logging: {
|
|
console: { level: "debug" },
|
|
file: {
|
|
level: "warn",
|
|
path: "/var/log/app.log",
|
|
rotation: { frequency: "daily", maxFiles: 14, size: "50MB" },
|
|
},
|
|
level: "info",
|
|
},
|
|
},
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("接受空对象", () => {
|
|
expect(validate({})).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("validateConfigContract", () => {
|
|
test("有效配置通过校验", () => {
|
|
const result = validateConfigContract({ server: { listen: { host: "0.0.0.0", port: 8080 } } });
|
|
expect(result.config).not.toBeNull();
|
|
});
|
|
|
|
test("空配置通过校验", () => {
|
|
const result = validateConfigContract({});
|
|
expect(result.config).not.toBeNull();
|
|
});
|
|
|
|
test("包含未知字段的配置被拒绝", () => {
|
|
const result = validateConfigContract({ server: { host: "bad" } });
|
|
expect(result.config).toBeNull();
|
|
expect(result.issues.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("schema 同步测试", () => {
|
|
test("config.schema.json 与 createConfigJsonSchema() 输出一致", async () => {
|
|
const file = Bun.file("config.schema.json");
|
|
const existing = await file.text();
|
|
const generated = `${JSON.stringify(createConfigJsonSchema(), null, 2)}\n`;
|
|
expect(existing).toBe(generated);
|
|
});
|
|
});
|