import { expect, test } from "vitest"; import { createPlainleafBackup, parsePlainleafBackup, plainleafBackupFormat, restorePlainleafBackup, } from "./plainleaf_backup.ts"; test("creates and parses a Markdown-only backup", () => { const backup = createPlainleafBackup( [{ path: "任务/收件箱.md", text: "- [ ] 测试\n" }], "2026-08-12T12:00:00.000Z", ); expect(parsePlainleafBackup(JSON.stringify(backup))).toEqual(backup); expect(backup.format).toBe(plainleafBackupFormat); }); test("rejects malformed backup payloads and unsafe paths", () => { expect(() => parsePlainleafBackup("not json")).toThrow("有效的 JSON"); expect(() => parsePlainleafBackup('{"format":"wrong","files":[]}')).toThrow( "Plainleaf Markdown 备份包", ); for (const path of [ "../secret.md", "/absolute.md", "notes.txt", "a\\b.md", "a//b.md", "a/./b.md", ]) { expect(() => parsePlainleafBackup( JSON.stringify({ format: plainleafBackupFormat, exportedAt: "", files: [{ path, text: "x" }], }), ), ).toThrow("无效的 Markdown 路径"); } }); test("allows dots in names but rejects duplicate backup destinations", () => { expect( parsePlainleafBackup( JSON.stringify({ format: plainleafBackupFormat, files: [{ path: "版本/1..2.md", text: "ok" }], }), ).files[0].path, ).toBe("版本/1..2.md"); expect(() => parsePlainleafBackup( JSON.stringify({ format: plainleafBackupFormat, files: [ { path: "重复.md", text: "first" }, { path: "重复.md", text: "second" }, ], }), ), ).toThrow("重复路径"); }); test("restores listed Markdown without deleting unrelated files", async () => { const files = new Map([ ["已有.md", "旧内容"], ["保留.md", "不要删除"], ]); const backup = createPlainleafBackup([ { path: "已有.md", text: "新内容" }, { path: "新增.md", text: "新增内容" }, ]); await expect( restorePlainleafBackup(backup, { listPaths: async () => [...files.keys()], read: async (path) => files.get(path)!, write: async (path, text) => { files.set(path, text); }, remove: async (path) => { files.delete(path); }, }), ).resolves.toBe(2); expect(files).toEqual( new Map([ ["已有.md", "新内容"], ["保留.md", "不要删除"], ["新增.md", "新增内容"], ]), ); }); test("rolls back overwritten and newly created pages when restore fails", async () => { const files = new Map([["已有.md", "旧内容"]]); let failing = true; const backup = createPlainleafBackup([ { path: "已有.md", text: "新内容" }, { path: "新增.md", text: "新增内容" }, { path: "失败.md", text: "不会写入" }, ]); await expect( restorePlainleafBackup(backup, { listPaths: async () => [...files.keys()], read: async (path) => files.get(path)!, write: async (path, text) => { if (path === "失败.md" && failing) { failing = false; throw new Error("磁盘已满"); } files.set(path, text); }, remove: async (path) => { files.delete(path); }, }), ).rejects.toThrow("磁盘已满"); expect(files).toEqual(new Map([["已有.md", "旧内容"]])); });