feat(smartfs.directory): feat(smartfs.directory): add directory copy/move with conflict handling and options
This commit is contained in:
@@ -367,6 +367,189 @@ tap.test('treeHash of empty directory should return consistent hash', async () =
|
||||
expect(hash1.length).toEqual(64);
|
||||
});
|
||||
|
||||
// --- Directory copy/move tests ---
|
||||
|
||||
tap.test('should copy a directory', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-dir-source');
|
||||
const destPath = path.join(tempDir, 'copy-dir-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file1.txt')).write('content1');
|
||||
await smartFs.file(path.join(sourcePath, 'file2.txt')).write('content2');
|
||||
|
||||
await smartFs.directory(sourcePath).copy(destPath);
|
||||
|
||||
// Source should still exist
|
||||
const sourceExists = await smartFs.directory(sourcePath).exists();
|
||||
expect(sourceExists).toEqual(true);
|
||||
|
||||
// Destination should exist with same files
|
||||
const destExists = await smartFs.directory(destPath).exists();
|
||||
expect(destExists).toEqual(true);
|
||||
|
||||
const destContent1 = await smartFs.file(path.join(destPath, 'file1.txt')).encoding('utf8').read();
|
||||
const destContent2 = await smartFs.file(path.join(destPath, 'file2.txt')).encoding('utf8').read();
|
||||
|
||||
expect(destContent1).toEqual('content1');
|
||||
expect(destContent2).toEqual('content2');
|
||||
});
|
||||
|
||||
tap.test('should copy a directory recursively with nested subdirectories', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-recursive-source');
|
||||
const destPath = path.join(tempDir, 'copy-recursive-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'root.txt')).write('root');
|
||||
await smartFs.directory(path.join(sourcePath, 'sub1')).create();
|
||||
await smartFs.file(path.join(sourcePath, 'sub1', 'nested1.txt')).write('nested1');
|
||||
await smartFs.directory(path.join(sourcePath, 'sub1', 'sub2')).create();
|
||||
await smartFs.file(path.join(sourcePath, 'sub1', 'sub2', 'deep.txt')).write('deep');
|
||||
|
||||
await smartFs.directory(sourcePath).copy(destPath);
|
||||
|
||||
// Verify all files copied
|
||||
expect(await smartFs.file(path.join(destPath, 'root.txt')).exists()).toEqual(true);
|
||||
expect(await smartFs.file(path.join(destPath, 'sub1', 'nested1.txt')).exists()).toEqual(true);
|
||||
expect(await smartFs.file(path.join(destPath, 'sub1', 'sub2', 'deep.txt')).exists()).toEqual(true);
|
||||
|
||||
const deepContent = await smartFs.file(path.join(destPath, 'sub1', 'sub2', 'deep.txt')).encoding('utf8').read();
|
||||
expect(deepContent).toEqual('deep');
|
||||
});
|
||||
|
||||
tap.test('should copy directory with filter applied', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-filter-source');
|
||||
const destPath = path.join(tempDir, 'copy-filter-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file.ts')).write('typescript');
|
||||
await smartFs.file(path.join(sourcePath, 'file.js')).write('javascript');
|
||||
await smartFs.file(path.join(sourcePath, 'file.txt')).write('text');
|
||||
|
||||
// Copy only .ts files
|
||||
await smartFs.directory(sourcePath).filter(/\.ts$/).copy(destPath);
|
||||
|
||||
expect(await smartFs.file(path.join(destPath, 'file.ts')).exists()).toEqual(true);
|
||||
expect(await smartFs.file(path.join(destPath, 'file.js')).exists()).toEqual(false);
|
||||
expect(await smartFs.file(path.join(destPath, 'file.txt')).exists()).toEqual(false);
|
||||
});
|
||||
|
||||
tap.test('should copy all files when applyFilter(false)', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-no-filter-source');
|
||||
const destPath = path.join(tempDir, 'copy-no-filter-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file.ts')).write('typescript');
|
||||
await smartFs.file(path.join(sourcePath, 'file.js')).write('javascript');
|
||||
|
||||
// Filter is set but applyFilter(false) ignores it
|
||||
await smartFs.directory(sourcePath).filter(/\.ts$/).applyFilter(false).copy(destPath);
|
||||
|
||||
expect(await smartFs.file(path.join(destPath, 'file.ts')).exists()).toEqual(true);
|
||||
expect(await smartFs.file(path.join(destPath, 'file.js')).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should copy with overwrite(true) replacing existing files', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-overwrite-source');
|
||||
const destPath = path.join(tempDir, 'copy-overwrite-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file.txt')).write('new content');
|
||||
|
||||
await smartFs.directory(destPath).create();
|
||||
await smartFs.file(path.join(destPath, 'file.txt')).write('old content');
|
||||
|
||||
await smartFs.directory(sourcePath).overwrite(true).copy(destPath);
|
||||
|
||||
const content = await smartFs.file(path.join(destPath, 'file.txt')).encoding('utf8').read();
|
||||
expect(content).toEqual('new content');
|
||||
});
|
||||
|
||||
tap.test('should throw error when onConflict is error and target exists', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-conflict-error-source');
|
||||
const destPath = path.join(tempDir, 'copy-conflict-error-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file.txt')).write('content');
|
||||
await smartFs.directory(destPath).create();
|
||||
|
||||
let threw = false;
|
||||
try {
|
||||
await smartFs.directory(sourcePath).onConflict('error').copy(destPath);
|
||||
} catch (e: any) {
|
||||
threw = true;
|
||||
expect(e.message).toInclude('EEXIST');
|
||||
}
|
||||
expect(threw).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should replace target when onConflict is replace', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-replace-source');
|
||||
const destPath = path.join(tempDir, 'copy-replace-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'new.txt')).write('new');
|
||||
|
||||
await smartFs.directory(destPath).create();
|
||||
await smartFs.file(path.join(destPath, 'old.txt')).write('old');
|
||||
|
||||
await smartFs.directory(sourcePath).onConflict('replace').copy(destPath);
|
||||
|
||||
// Old file should be gone, new file should exist
|
||||
expect(await smartFs.file(path.join(destPath, 'old.txt')).exists()).toEqual(false);
|
||||
expect(await smartFs.file(path.join(destPath, 'new.txt')).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should move a directory', async () => {
|
||||
const sourcePath = path.join(tempDir, 'move-dir-source');
|
||||
const destPath = path.join(tempDir, 'move-dir-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'file1.txt')).write('content1');
|
||||
await smartFs.file(path.join(sourcePath, 'file2.txt')).write('content2');
|
||||
|
||||
await smartFs.directory(sourcePath).move(destPath);
|
||||
|
||||
// Source should no longer exist
|
||||
const sourceExists = await smartFs.directory(sourcePath).exists();
|
||||
expect(sourceExists).toEqual(false);
|
||||
|
||||
// Destination should exist with files
|
||||
const destExists = await smartFs.directory(destPath).exists();
|
||||
expect(destExists).toEqual(true);
|
||||
|
||||
const destContent1 = await smartFs.file(path.join(destPath, 'file1.txt')).encoding('utf8').read();
|
||||
expect(destContent1).toEqual('content1');
|
||||
});
|
||||
|
||||
tap.test('should move directory recursively', async () => {
|
||||
const sourcePath = path.join(tempDir, 'move-recursive-source');
|
||||
const destPath = path.join(tempDir, 'move-recursive-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.file(path.join(sourcePath, 'root.txt')).write('root');
|
||||
await smartFs.directory(path.join(sourcePath, 'sub')).create();
|
||||
await smartFs.file(path.join(sourcePath, 'sub', 'nested.txt')).write('nested');
|
||||
|
||||
await smartFs.directory(sourcePath).move(destPath);
|
||||
|
||||
// Source should not exist
|
||||
expect(await smartFs.directory(sourcePath).exists()).toEqual(false);
|
||||
|
||||
// All files should be at destination
|
||||
expect(await smartFs.file(path.join(destPath, 'root.txt')).exists()).toEqual(true);
|
||||
expect(await smartFs.file(path.join(destPath, 'sub', 'nested.txt')).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('should copy empty directory', async () => {
|
||||
const sourcePath = path.join(tempDir, 'copy-empty-source');
|
||||
const destPath = path.join(tempDir, 'copy-empty-dest');
|
||||
|
||||
await smartFs.directory(sourcePath).create();
|
||||
await smartFs.directory(sourcePath).copy(destPath);
|
||||
|
||||
expect(await smartFs.directory(destPath).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
tap.test('cleanup temp directory', async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
expect(true).toEqual(true);
|
||||
|
||||
Reference in New Issue
Block a user