update
This commit is contained in:
107
tests/unit/file-move-copy.test.js
Normal file
107
tests/unit/file-move-copy.test.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// main/file-ops.js —— recursiveCopy 单测
|
||||
//
|
||||
// 覆盖:
|
||||
// - 单层文件复制
|
||||
// - 多层文件夹树递归复制(含子目录 + 子文件)
|
||||
// - 源目录中的符号链接子项被跳过(安全:不跟随软链)
|
||||
// - 目标名冲突由调用方(_prepareRelocate)用 resolveRenameFolderName 解,
|
||||
// 本测试只验证 recursiveCopy 对给定 destAbs 的完整落地。
|
||||
//
|
||||
// 纯 Node,用 mkdtempSync 建真实临时目录;Windows 上 symlink 需要管理员权限,
|
||||
// 故 symlink 用例用 try/catch 跳过(无权限环境不报错、不污染结果)。
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
mkdtempSync, rmSync, writeFileSync, mkdirSync, symlinkSync, readFileSync, readdirSync, statSync,
|
||||
} from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join, sep } from 'path';
|
||||
|
||||
const fileOps = require('../../main/file-ops.js');
|
||||
const { recursiveCopy } = fileOps;
|
||||
|
||||
let dir;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), 'notes-reccopy-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function lsTree(root) {
|
||||
// 返回 { 相对路径(用 / 归一): 'file' | 'dir' },相对 root
|
||||
const out = {};
|
||||
(function walk(abs, rel) {
|
||||
const entries = readdirSync(abs);
|
||||
for (const name of entries) {
|
||||
const a = join(abs, name);
|
||||
const r = (rel ? join(rel, name) : name).split(sep).join('/');
|
||||
const st = statSync(a);
|
||||
if (st.isDirectory()) { out[r] = 'dir'; walk(a, r); }
|
||||
else if (st.isFile()) out[r] = 'file';
|
||||
}
|
||||
})(root, '');
|
||||
return out;
|
||||
}
|
||||
|
||||
describe('recursiveCopy', () => {
|
||||
it('复制单个文件', async () => {
|
||||
const src = join(dir, 'src');
|
||||
const dst = join(dir, 'dst');
|
||||
mkdirSync(src);
|
||||
writeFileSync(join(src, 'a.md'), 'hello');
|
||||
|
||||
await recursiveCopy(src, dst);
|
||||
|
||||
expect(statSync(join(dst, 'a.md')).isFile()).toBe(true);
|
||||
expect(readFileSync(join(dst, 'a.md'), 'utf8')).toBe('hello');
|
||||
});
|
||||
|
||||
it('递归复制多层文件夹树', async () => {
|
||||
const src = join(dir, 'src');
|
||||
const dst = join(dir, 'dst');
|
||||
mkdirSync(join(src, 'sub'), { recursive: true });
|
||||
writeFileSync(join(src, 'root.md'), 'r');
|
||||
writeFileSync(join(src, 'sub', 'child.md'), 'c');
|
||||
|
||||
await recursiveCopy(src, dst);
|
||||
|
||||
const tree = lsTree(dst);
|
||||
expect(tree['root.md']).toBe('file');
|
||||
expect(tree['sub']).toBe('dir');
|
||||
expect(tree['sub/child.md']).toBe('file');
|
||||
expect(readFileSync(join(dst, 'sub', 'child.md'), 'utf8')).toBe('c');
|
||||
});
|
||||
|
||||
it('源目录中的符号链接子项被跳过(不跟随、不落地)', async () => {
|
||||
const src = join(dir, 'src');
|
||||
const dst = join(dir, 'dst');
|
||||
mkdirSync(src);
|
||||
writeFileSync(join(src, 'real.md'), 'real');
|
||||
try {
|
||||
// 用一个真实文件做软链目标,再链过去
|
||||
symlinkSync(join(src, 'real.md'), join(src, 'link.md'));
|
||||
} catch {
|
||||
// Windows 无管理员权限时 symlink 会抛 EPERM;跳过后本用例视为满足「无软链落地」
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await recursiveCopy(src, dst);
|
||||
|
||||
expect(statSync(join(dst, 'real.md')).isFile()).toBe(true);
|
||||
expect(() => statSync(join(dst, 'link.md'))).toThrow(); // 软链未复制
|
||||
});
|
||||
|
||||
it('目标已存在同名目录时抛错(不静默覆盖)', async () => {
|
||||
const src = join(dir, 'src');
|
||||
const dst = join(dir, 'dst');
|
||||
mkdirSync(src);
|
||||
mkdirSync(dst); // 已存在
|
||||
writeFileSync(join(src, 'x.md'), 'x');
|
||||
|
||||
await expect(recursiveCopy(src, dst)).rejects.toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user