import { describe, expect, test } from "bun:test"; import { mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadServerConfig, parseRuntimeArgs, parseSize } from "../../src/server/config"; import { APP } from "../../src/shared/app"; describe("parseRuntimeArgs", () => { test("无参数抛出需要配置文件路径错误", () => { try { parseRuntimeArgs([]); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("需要指定 YAML 配置文件路径"); } }); test("有参数返回 configPath", () => { const result = parseRuntimeArgs(["config.yaml"]); expect(result).toEqual({ configPath: "config.yaml" }); }); test("--help 抛出错误", () => { try { parseRuntimeArgs(["--help"]); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("用法"); } }); test("-h 抛出错误", () => { try { parseRuntimeArgs(["-h"]); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("用法"); } }); }); describe("parseSize", () => { test("解析数字字节值", () => { expect(parseSize(1024)).toBe(1024); }); test("解析字符串大小", () => { expect(parseSize("1KB")).toBe(1024); expect(parseSize("50MB")).toBe(52428800); expect(parseSize("1GB")).toBe(1073741824); expect(parseSize("1024B")).toBe(1024); }); test("非法格式抛出错误", () => { try { parseSize("invalid"); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("无效的 size 格式"); } }); }); describe("loadServerConfig", () => { test("YAML 配置文件不存在时报错", async () => { try { await loadServerConfig("/nonexistent/path/config.yaml"); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("配置文件不存在"); } }); test("最简配置解析成功", async () => { const temp = tmpdir(); const yamlPath = join(temp, "minimal.yaml"); await writeFile(yamlPath, 'server:\n listen:\n host: "0.0.0.0"\n port: 9999\n'); try { const result = await loadServerConfig(yamlPath); expect(result.host).toBe("0.0.0.0"); expect(result.port).toBe(9999); expect(result.configDir).toBe(temp); expect(result.dataDir).toBe(join(temp, "data")); expect(result.logging.filePath).toBe(join(temp, "data", "logs", `${APP.name}.log`)); expect(result.logging.consoleLevel).toBe("info"); expect(result.logging.fileLevel).toBe("info"); } finally { await rm(yamlPath, { force: true }); } }); test("旧布局 server.host/server.port 被拒绝", async () => { const temp = tmpdir(); const yamlPath = join(temp, "test-old-layout.yaml"); const yamlContent = 'server:\n host: "0.0.0.0"\n port: 9999\n'; await writeFile(yamlPath, yamlContent); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("未知字段"); } finally { await rm(yamlPath, { force: true }); } }); test("非法端口被拒绝", async () => { const temp = tmpdir(); const yamlPath = join(temp, "test-bad-port.yaml"); await writeFile(yamlPath, "server:\n listen:\n port: 99999\n"); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toBeTruthy(); } finally { await rm(yamlPath, { force: true }); } }); test("显式变量引用环境变量生效", async () => { const prevHost = process.env["HOST"]; const prevPort = process.env["PORT"]; process.env["HOST"] = "10.0.0.1"; process.env["PORT"] = "4000"; const temp = tmpdir(); const yamlPath = join(temp, "test-env-var.yaml"); await writeFile(yamlPath, 'server:\n listen:\n host: "${HOST}"\n port: ${PORT}\n'); try { const result = await loadServerConfig(yamlPath); expect(result.host).toBe("10.0.0.1"); expect(result.port).toBe(4000); } finally { await rm(yamlPath, { force: true }); if (prevHost === undefined) delete process.env["HOST"]; else process.env["HOST"] = prevHost; if (prevPort === undefined) delete process.env["PORT"]; else process.env["PORT"] = prevPort; } }); test("变量带默认值生效", async () => { delete process.env["MY_HOST"]; const temp = tmpdir(); const yamlPath = join(temp, "test-default.yaml"); await writeFile(yamlPath, 'server:\n listen:\n host: "${MY_HOST|0.0.0.0}"\n port: ${MY_PORT|5000}\n'); try { const result = await loadServerConfig(yamlPath); expect(result.host).toBe("0.0.0.0"); expect(result.port).toBe(5000); } finally { await rm(yamlPath, { force: true }); } }); test("绝对 dataDir 保持不变", async () => { const temp = tmpdir(); const dataDir = join(temp, "absolute-data"); await mkdir(dataDir, { recursive: true }); const yamlPath = join(temp, "absolute-dir.yaml"); await writeFile(yamlPath, `server:\n storage:\n dataDir: ${JSON.stringify(dataDir)}\n`); try { const result = await loadServerConfig(yamlPath); expect(result.dataDir).toBe(dataDir); } finally { await rm(yamlPath, { force: true }); } }); test("相对 dataDir 基于 configDir", async () => { const temp = tmpdir(); const yamlPath = join(temp, "rel-dir.yaml"); await writeFile(yamlPath, 'server:\n storage:\n dataDir: "./my-data"\n'); try { const result = await loadServerConfig(yamlPath); expect(result.dataDir).toBe(join(temp, "my-data")); } finally { await rm(yamlPath, { force: true }); } }); test("显式相对日志路径基于 configDir", async () => { const temp = tmpdir(); const yamlPath = join(temp, "log-path.yaml"); await writeFile(yamlPath, 'server:\n logging:\n file:\n path: "./logs/app.log"\n'); try { const result = await loadServerConfig(yamlPath); expect(result.logging.filePath).toBe(join(temp, "logs", "app.log")); } finally { await rm(yamlPath, { force: true }); } }); test("绝对日志路径保持不变", async () => { const temp = tmpdir(); const logPath = join(temp, "my-app.log"); const yamlPath = join(temp, "abs-log.yaml"); await writeFile(yamlPath, `server:\n logging:\n file:\n path: ${JSON.stringify(logPath)}\n`); try { const result = await loadServerConfig(yamlPath); expect(result.logging.filePath).toBe(logPath); } finally { await rm(yamlPath, { force: true }); } }); test("非法 logging.level 抛出错误", async () => { const temp = tmpdir(); const yamlPath = join(temp, "bad-level.yaml"); await writeFile(yamlPath, 'server:\n logging:\n level: "invalid"\n'); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("日志等级"); } finally { await rm(yamlPath, { force: true }); } }); test("空白 logging.file.path 抛出错误", async () => { const temp = tmpdir(); const yamlPath = join(temp, "blank-path.yaml"); await writeFile(yamlPath, 'server:\n logging:\n file:\n path: " "\n'); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("日志路径不能为空字符串或空白字符串"); } finally { await rm(yamlPath, { force: true }); } }); test("非法 rotation.size 抛出错误", async () => { const temp = tmpdir(); const yamlPath = join(temp, "bad-size.yaml"); await writeFile(yamlPath, 'server:\n logging:\n file:\n rotation:\n size: "99XX"\n'); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("无效的 size 格式"); } finally { await rm(yamlPath, { force: true }); } }); test("非法 rotation.frequency 抛出错误", async () => { const temp = tmpdir(); const yamlPath = join(temp, "bad-freq.yaml"); await writeFile(yamlPath, 'server:\n logging:\n file:\n rotation:\n frequency: "yearly"\n'); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("rotation.frequency"); } finally { await rm(yamlPath, { force: true }); } }); test("非法 rotation.maxFiles 抛出错误", async () => { const temp = tmpdir(); const yamlPath = join(temp, "bad-max.yaml"); await writeFile(yamlPath, "server:\n logging:\n file:\n rotation:\n maxFiles: 0\n"); try { await loadServerConfig(yamlPath); expect.unreachable(); } catch (error) { expect((error as Error).message).toContain("maxFiles"); } finally { await rm(yamlPath, { force: true }); } }); });