45 lines
2.0 KiB
TypeScript
45 lines
2.0 KiB
TypeScript
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("普通备注");
|
|
});
|
|
});
|