// modules/test/test-fs-mv-cp-guard.js — run: node // Proves MAINT-010 (mv/cp must not destroy a directory node) and MAINT-016 (ls -l ) // against the real classes, no browser. const fsMod = require('fs'), path = require('path'); const ROOT = path.join(__dirname, '..', 'core'); const ls = {}; global.localStorage = { getItem: k => (k in ls ? ls[k] : null), setItem: (k, v) => { ls[k] = String(v); }, removeItem: k => { delete ls[k]; } }; global.indexedDB = { open() { throw new Error('no idb'); }, deleteDatabase() { throw new Error('no idb'); } }; global.window = global; function load(file, cls) { return new Function(fsMod.readFileSync(path.join(ROOT, file), 'utf8') + '\nreturn ' + cls + ';')(); } const VFS = load('virtual-fs.js', 'VirtualFileSystem'); const FSModule = load('module-fs.js', 'FileSystemModule'); function mk() { const out = []; const vfs = new VFS(); vfs.saveToStorage = () => true; const term = { print: (m, t) => out.push({ text: String(m), type: t || '' }), fs: vfs }; const mod = new FSModule(); mod.terminal = term; mod.registerCommands(); return { vfs, mod, out }; } let fails = 0, passes = 0; function ok(name, cond, detail) { if (cond) { passes++; return; } fails++; console.log('FAIL ' + name + (detail ? ' — ' + detail : '')); } // A. mv a file over a directory must refuse and leave the children alone { const { vfs, mod, out } = mk(); vfs.mkdirRecursive('/docs'); vfs.writeFile('/docs/keep.txt', 'precious'); vfs.writeFile('/report.txt', 'x'); out.length = 0; mod.commands['mv'](['/report.txt', '/docs']); const msg = out.map(o => o.text).join(' '); ok('A1 mv onto a directory is refused', /is a directory/.test(msg), msg); ok('A2 destination children intact', vfs.readFile('/docs/keep.txt') === 'precious'); ok('A3 source still there', vfs.readFile('/report.txt') === 'x'); } // B. cp over a directory must refuse too { const { vfs, mod, out } = mk(); vfs.mkdirRecursive('/docs'); vfs.writeFile('/docs/keep.txt', 'precious'); vfs.writeFile('/report.txt', 'x'); out.length = 0; mod.commands['cp'](['/report.txt', '/docs']); const msg = out.map(o => o.text).join(' '); ok('B1 cp onto a directory is refused', /is a directory/.test(msg), msg); ok('B2 destination children intact', vfs.readFile('/docs/keep.txt') === 'precious'); } // C. mv a a is a no-op, not a delete { const { vfs } = mk(); vfs.writeFile('/a.txt', 'still here'); ok('C1 mv onto itself returns true', vfs.rename('/a.txt', '/a.txt') === true); ok('C2 file survived', vfs.readFile('/a.txt') === 'still here'); } // D. overwriting a file with a file still works (documented mv behaviour) { const { vfs } = mk(); vfs.writeFile('/one.txt', '1'); vfs.writeFile('/two.txt', '2'); ok('D1 mv over a file succeeds', vfs.rename('/one.txt', '/two.txt') === true); ok('D2 new content in place', vfs.readFile('/two.txt') === '1'); ok('D3 source gone', vfs.readFile('/one.txt') === null); } // E. ls -l lists that path, not the cwd (MAINT-016) { const { vfs, mod, out } = mk(); vfs.mkdirRecursive('/work'); vfs.writeFile('/work/inside.txt', 'x'); vfs.writeFile('/cwd-only.txt', 'y'); vfs.currentPath = []; out.length = 0; mod.commands['ls'](['-l', '/work']); const text = out.map(o => o.text).join('\n'); ok('E1 flag-first form lists the given path', /inside\.txt/.test(text) && !/cwd-only\.txt/.test(text), text.replace(/\n/g, ' | ')); } // F. path-first form keeps working { const { vfs, mod, out } = mk(); vfs.mkdirRecursive('/work'); vfs.writeFile('/work/inside.txt', 'x'); out.length = 0; mod.commands['ls'](['/work', '-l']); const text = out.map(o => o.text).join('\n'); ok('F1 path-first form still lists the path', /inside\.txt/.test(text), text.replace(/\n/g, ' | ')); } if (fails === 0) { console.log('test-fs-mv-cp-guard: ' + passes + ' passed, 0 failed'); } else { console.log('test-fs-mv-cp-guard: ' + passes + ' passed, ' + fails + ' FAILED'); process.exit(1); }