mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-06-18 15:54:08 +08:00
e0e50143c9
- 将 output 目录迁移改为 worker 异步执行,并补充扫描、复制、校验、回滚阶段进度 - 设置页增加迁移进度展示,支持恢复进行中的状态并展示当前文件/传输体积 - 迁移完成后自动清理旧备份目录,并同步源码/桌面端版本号到 v1.7.10
188 lines
6.3 KiB
JavaScript
188 lines
6.3 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
|
|
const {
|
|
cleanupOutputDirectoryBackup,
|
|
getDefaultOutputDirPath,
|
|
getEffectiveOutputDirPath,
|
|
migrateOutputDirectory,
|
|
normalizeDirectoryPath,
|
|
rollbackOutputDirectoryChange,
|
|
} = require("../src/output-dir.cjs");
|
|
|
|
function makeTempDir() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), "wda-output-"));
|
|
}
|
|
|
|
function cleanupDir(dirPath) {
|
|
try {
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
} catch {}
|
|
}
|
|
|
|
test("normalizeDirectoryPath requires absolute paths", () => {
|
|
assert.throws(() => normalizeDirectoryPath("relative/path"), /绝对路径/);
|
|
});
|
|
|
|
test("getEffectiveOutputDirPath prefers env, then settings, then default", () => {
|
|
const root = makeTempDir();
|
|
const envDir = path.join(root, "env-output");
|
|
const settingsDir = path.join(root, "settings-output");
|
|
const defaultDir = path.join(root, "data", "output");
|
|
|
|
try {
|
|
assert.equal(
|
|
getEffectiveOutputDirPath({
|
|
dataDir: path.join(root, "data"),
|
|
envOutputDir: envDir,
|
|
settingsOutputDir: settingsDir,
|
|
}),
|
|
path.resolve(envDir)
|
|
);
|
|
assert.equal(
|
|
getEffectiveOutputDirPath({
|
|
dataDir: path.join(root, "data"),
|
|
envOutputDir: "",
|
|
settingsOutputDir: settingsDir,
|
|
}),
|
|
path.resolve(settingsDir)
|
|
);
|
|
assert.equal(getDefaultOutputDirPath(path.join(root, "data")), path.resolve(defaultDir));
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("migrateOutputDirectory switches empty source to a new directory", async () => {
|
|
const root = makeTempDir();
|
|
const currentDir = path.join(root, "current-output");
|
|
const nextDir = path.join(root, "custom-output");
|
|
|
|
try {
|
|
fs.mkdirSync(currentDir, { recursive: true });
|
|
const result = await migrateOutputDirectory({ currentDir, nextDir });
|
|
assert.equal(result.changed, true);
|
|
assert.equal(result.sourceWasEmpty, true);
|
|
assert.equal(result.backupDir, "");
|
|
assert.ok(fs.existsSync(nextDir));
|
|
assert.equal(fs.existsSync(currentDir), true);
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("migrateOutputDirectory blocks non-empty targets", async () => {
|
|
const root = makeTempDir();
|
|
const currentDir = path.join(root, "current-output");
|
|
const nextDir = path.join(root, "custom-output");
|
|
|
|
try {
|
|
fs.mkdirSync(path.join(currentDir, "logs"), { recursive: true });
|
|
fs.writeFileSync(path.join(currentDir, "runtime_settings.json"), "{}");
|
|
fs.mkdirSync(nextDir, { recursive: true });
|
|
fs.writeFileSync(path.join(nextDir, "existing.txt"), "occupied");
|
|
|
|
await assert.rejects(
|
|
migrateOutputDirectory({ currentDir, nextDir }),
|
|
/已有内容/
|
|
);
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("migrateOutputDirectory blocks invalid current paths", async () => {
|
|
const root = makeTempDir();
|
|
const currentDir = path.join(root, "current-output");
|
|
const nextDir = path.join(root, "custom-output");
|
|
|
|
try {
|
|
fs.writeFileSync(currentDir, "not-a-directory");
|
|
await assert.rejects(
|
|
migrateOutputDirectory({ currentDir, nextDir }),
|
|
/不是目录/
|
|
);
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("migrateOutputDirectory copies data and leaves the old directory as a backup", async () => {
|
|
const root = makeTempDir();
|
|
const currentDir = path.join(root, "current-output");
|
|
const nextDir = path.join(root, "custom-output");
|
|
|
|
try {
|
|
fs.mkdirSync(path.join(currentDir, "databases", "wxid_test"), { recursive: true });
|
|
fs.writeFileSync(path.join(currentDir, "runtime_settings.json"), "{\"backend_port\":10392}");
|
|
fs.writeFileSync(path.join(currentDir, "databases", "wxid_test", "session.db"), "session");
|
|
fs.writeFileSync(path.join(currentDir, "databases", "wxid_test", "contact.db"), "contact");
|
|
|
|
const progressEvents = [];
|
|
const result = await migrateOutputDirectory({
|
|
currentDir,
|
|
nextDir,
|
|
now: new Date("2026-03-30T08:00:00Z"),
|
|
onProgress: (progress) => progressEvents.push(progress),
|
|
});
|
|
assert.equal(result.changed, true);
|
|
assert.equal(result.sourceWasEmpty, false);
|
|
assert.match(path.basename(result.backupDir), /^current-output\.backup-\d{14}$/);
|
|
assert.ok(fs.existsSync(nextDir));
|
|
assert.ok(fs.existsSync(path.join(nextDir, "runtime_settings.json")));
|
|
assert.ok(fs.existsSync(path.join(nextDir, "databases", "wxid_test", "session.db")));
|
|
assert.ok(fs.existsSync(result.backupDir));
|
|
assert.equal(fs.existsSync(currentDir), false);
|
|
assert.ok(progressEvents.some((event) => event.stage === "scanning"));
|
|
assert.ok(progressEvents.some((event) => event.stage === "copying" && event.percent > 0));
|
|
assert.ok(progressEvents.some((event) => event.stage === "complete" && event.percent === 100));
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("rollbackOutputDirectoryChange restores the previous directory", () => {
|
|
const root = makeTempDir();
|
|
const previousDir = path.join(root, "current-output");
|
|
const currentDir = path.join(root, "custom-output");
|
|
const backupDir = path.join(root, "current-output.backup-20260330080100");
|
|
|
|
try {
|
|
fs.mkdirSync(path.join(currentDir, "databases"), { recursive: true });
|
|
fs.writeFileSync(path.join(currentDir, "databases", "temp.db"), "temp");
|
|
fs.mkdirSync(path.join(backupDir, "databases"), { recursive: true });
|
|
fs.writeFileSync(path.join(backupDir, "databases", "session.db"), "restored");
|
|
|
|
rollbackOutputDirectoryChange({
|
|
previousDir,
|
|
currentDir,
|
|
backupDir,
|
|
sourceWasEmpty: false,
|
|
});
|
|
|
|
assert.equal(fs.existsSync(currentDir), false);
|
|
assert.ok(fs.existsSync(path.join(previousDir, "databases", "session.db")));
|
|
assert.equal(fs.existsSync(backupDir), false);
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|
|
|
|
test("cleanupOutputDirectoryBackup removes a completed migration backup directory", () => {
|
|
const root = makeTempDir();
|
|
const backupDir = path.join(root, "current-output.backup-20260330080100");
|
|
|
|
try {
|
|
fs.mkdirSync(path.join(backupDir, "databases"), { recursive: true });
|
|
fs.writeFileSync(path.join(backupDir, "databases", "session.db"), "restored");
|
|
|
|
assert.equal(cleanupOutputDirectoryBackup(backupDir), true);
|
|
assert.equal(fs.existsSync(backupDir), false);
|
|
} finally {
|
|
cleanupDir(root);
|
|
}
|
|
});
|