This commit is contained in:
2026-09-12 13:59:12 +08:00
commit 941ce4baab
71 changed files with 13037 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
'use strict';
/**
* 数据目录降级测试 —— 解决「默认 D:\Clipboard Data 不可用就启动失败」的兼容问题。
*
* 被测对象是 config.js 的 isDirUsable / defaultDataDir。三条契约
* 1. isDirUsable 能正确判断目录真的能写入;
* 2. defaultDataDir 在默认目录不可用时降级到 home 下的 Clipboard Data
* 3. 探针文件不会残留(每次探测都自清理)。
*
* 模拟「D 盘不存在」的方法:临时替换 fs.mkdirSync让它对 D:\\ 开头的路径
* 抛 ENOENT —— 跟项目里「不引入 mock 框架、靠临时替换内置模块方法」的风格
* 一致。restore 严格用 finally / afterEach 兜住,避免污染其它测试。
*/
const assert = require('node:assert');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
let passed = 0;
function check(name, fn) {
try { fn(); console.log(' ok -', name); passed++; }
catch (e) { console.error(' FAIL -', name, '\n', e.message); process.exitCode = 1; }
}
// 用临时 HOME 隔离 —— defaultDataDir() 降级时是 %USERPROFILE%\Clipboard Data
// 我们希望探针 / 副作用都落在临时目录,不污染真实 home。
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'clip-fb-'));
process.env.USERPROFILE = tmpHome;
process.env.HOME = tmpHome;
// 不预设 CLIPBOARD_APP_DATA_DIR —— 本测试就是要验证它没设时的降级行为
delete process.env.CLIPBOARD_APP_DATA_DIR;
const cfg = require('../config.js');
// 「D 盘不存在」的 mockfs.mkdirSync 对 D:\ 前缀抛 ENOENT。
// 不影响其它调用 —— D: 在大多数机器上不一定存在,我们的判断「按前缀」
// 已经足够准;同时不影响 home 下的真实写入。
const originalMkdir = fs.mkdirSync;
function mockNoDDrive() {
fs.mkdirSync = function patched(p, opts) {
if (typeof p === 'string' && /^[dD]:[\\/]/.test(p)) {
const e = new Error(`ENOENT: no such file or directory, mkdir '${p}'`);
e.code = 'ENOENT';
throw e;
}
return originalMkdir.call(fs, p, opts);
};
}
function restoreFs() {
fs.mkdirSync = originalMkdir;
}
process.on('exit', restoreFs);
check('isDirUsable: 真实可写目录返回 true', () => {
// tmpHome 必然可写
assert.strictEqual(cfg.isDirUsable(tmpHome), true);
});
check('isDirUsable: 不存在的盘符D:\\__no_such__在 mock 下返回 false', () => {
mockNoDDrive();
try {
assert.strictEqual(cfg.isDirUsable('D:\\__no_such_drive_xyz__'), false);
} finally { restoreFs(); }
});
check('isDirUsable: 空字符串返回 false不打 fs.mkdirSync 空路径)', () => {
assert.strictEqual(cfg.isDirUsable(''), false);
assert.strictEqual(cfg.isDirUsable(null), false);
assert.strictEqual(cfg.isDirUsable(undefined), false);
});
check('isDirUsable: 探针文件被清理,不在目录里残留', () => {
// 探针名字固定 —— 跑两次探测,第二次还能成功说明第一次 unlink 了。
const probePath = path.join(tmpHome, '.clipboard-write-test');
assert.ok(!fs.existsSync(probePath), '开始前不应有残留');
assert.strictEqual(cfg.isDirUsable(tmpHome), true);
assert.ok(!fs.existsSync(probePath), '探测后不应残留探针文件');
assert.strictEqual(cfg.isDirUsable(tmpHome), true, '第二次探测照样过 → 第一次 unlink 干净');
});
check('defaultDataDir: D 盘不可用时降级到 home 下的 Clipboard Data', () => {
mockNoDDrive();
try {
// 重新加载模块,让 defaultDataDir() 重新走一遍降级逻辑
delete require.cache[require.resolve('../config.js')];
const cfg2 = require('../config.js');
const dir = cfg2.defaultDataDir();
// 降级路径必须等于 home + Clipboard Data
assert.strictEqual(
path.resolve(dir),
path.resolve(tmpHome, 'Clipboard Data'),
'D 盘被 mock 掉时必须降级到 home\\Clipboard Data'
);
// 降级目录真的可用 —— 否则还是炸
assert.strictEqual(cfg2.isDirUsable(dir), true);
} finally {
restoreFs();
delete require.cache[require.resolve('../config.js')];
require('../config.js'); // 重新挂回正常状态给后面的 case
}
});
check('defaultDataDir: env 覆盖在降级链路里仍最高优先级', () => {
mockNoDDrive();
const custom = path.join(tmpHome, 'env-override-dir');
process.env.CLIPBOARD_APP_DATA_DIR = custom;
try {
delete require.cache[require.resolve('../config.js')];
const cfg2 = require('../config.js');
assert.strictEqual(path.resolve(cfg2.defaultDataDir()), path.resolve(custom),
'env 覆盖必须胜过 D 盘 / home 的降级选择');
} finally {
restoreFs();
delete process.env.CLIPBOARD_APP_DATA_DIR;
delete require.cache[require.resolve('../config.js')];
require('../config.js');
}
});
check('defaultDataDir: D 盘可用时首选 D 盘(契约保留)', () => {
// 不 mock —— 用真实环境。仅当 D 盘真的可用时断言,否则跳过。
if (!cfg.isDirUsable('D:\\Clipboard Data')) {
console.log(' (D:\\Clipboard Data 在本机不可用,跳过)');
return;
}
// 不设 env 时必须首选 D 盘
const saved = process.env.CLIPBOARD_APP_DATA_DIR;
delete process.env.CLIPBOARD_APP_DATA_DIR;
delete require.cache[require.resolve('../config.js')];
try {
const cfg2 = require('../config.js');
assert.strictEqual(path.resolve(cfg2.defaultDataDir()), path.resolve('D:\\Clipboard Data'));
} finally {
process.env.CLIPBOARD_APP_DATA_DIR = saved;
delete require.cache[require.resolve('../config.js')];
require('../config.js');
}
});
// 清理
fs.rmSync(tmpHome, { recursive: true, force: true });
console.log(`\n${passed} checks passed`);