import { describe, expect, it } from "vitest"; import { amountToCents, centsToAmount, exportRequestSchema } from "../shared/contracts.js"; import { zonedMonthBounds } from "../server/app.js"; import { safeExcelText } from "../server/exporter.js"; import { safeStoragePath, sanitizeOriginalName } from "../server/files.js"; describe("金额", () => { it("按分精确转换并格式化", () => { expect(amountToCents("12.3")).toBe(1230); expect(amountToCents("0.01")).toBe(1); expect(centsToAmount(1234)).toBe("12.34"); expect(() => amountToCents("12.345")).toThrow(); expect(() => amountToCents("0")).toThrow(); }); }); describe("时区月份", () => { it("按 Asia/Shanghai 返回 UTC 月份边界", () => { const [start, end] = zonedMonthBounds("2026-08", "Asia/Shanghai"); expect(new Date(start).toISOString()).toBe("2026-07-31T16:00:00.000Z"); expect(new Date(end).toISOString()).toBe("2026-08-31T16:00:00.000Z"); }); }); describe("导出选项", () => { it("默认不包含 manifest.json,并支持显式开启", () => { expect(exportRequestSchema.parse({ ids: ["00000000-0000-4000-8000-000000000001"] }).includeManifest).toBe(false); expect(exportRequestSchema.parse({ month: "2026-08", status: "unreimbursed" }).includeManifest).toBe(false); expect(exportRequestSchema.parse({ ids: ["00000000-0000-4000-8000-000000000001"], includeManifest: true }).includeManifest).toBe(true); }); }); describe("文件和导出安全", () => { it("不让用户文件名参与路径", () => { expect(sanitizeOriginalName("../../秘密\u0000.png")).toBe("秘密.png"); expect(safeStoragePath("/tmp/tallynote-files", "ab/example.png")).toBe("/tmp/tallynote-files/ab/example.png"); expect(() => safeStoragePath("/tmp/tallynote-files", "../outside")).toThrow(); }); it("阻止 Excel 公式注入", () => { expect(safeExcelText("=HYPERLINK(\"https://example.com\")")).toBe("'=HYPERLINK(\"https://example.com\")"); expect(safeExcelText("普通备注")).toBe("普通备注"); }); });