diff options
Diffstat (limited to 'modules/test/test-ai-fs-tools.js')
| -rw-r--r-- | modules/test/test-ai-fs-tools.js | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/modules/test/test-ai-fs-tools.js b/modules/test/test-ai-fs-tools.js new file mode 100644 index 0000000..c427783 --- /dev/null +++ b/modules/test/test-ai-fs-tools.js @@ -0,0 +1,74 @@ +// modules/test/test-ai-fs-tools.js — run: node +// Proves MAINT-011 (list_files must see the virtual /sys mount) and MAINT-012 +// (write_file must not report success when the write was refused). +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; + +const load = (file, cls) => new Function(fsMod.readFileSync(path.join(ROOT, file), 'utf8') + '\nreturn ' + cls + ';')(); +const VFS = load('virtual-fs.js', 'VirtualFileSystem'); +const AI = load('module-aiassist.js', 'WebX11AIAssistantModule'); + +function mk() { + const out = []; + const vfs = new VFS(); + vfs.saveToStorage = () => true; + // Bypass the constructor (it touches the DOM) but keep the real prototype methods. + const ai = Object.create(AI.prototype); + ai.terminal = { print: (m, t) => out.push({ text: String(m), type: t || '' }), log: () => {}, fs: vfs }; + ai.checkGuardrail = () => null; + return { ai, vfs, out }; +} + +let fails = 0, passes = 0; +function ok(name, cond, detail) { + if (cond) { passes++; return; } + fails++; + console.log('FAIL ' + name + (detail ? ' — ' + detail : '')); +} +const run = async (ai, name, args) => JSON.parse(await ai.runTool(name, args)); + +(async () => { + // A. list_files on the virtual /sys mount + { + const { ai } = mk(); + const res = await run(ai, 'list_files', { path: '/sys' }); + ok('A1 /sys lists entries instead of an error', Array.isArray(res), JSON.stringify(res).slice(0, 120)); + ok('A2 /sys contains the modules mount', Array.isArray(res) && res.some(e => e.name === 'modules'), + JSON.stringify(res).slice(0, 160)); + } + + // B. a genuinely missing directory still reports the error + { + const { ai } = mk(); + const res = await run(ai, 'list_files', { path: '/no-such-dir' }); + ok('B1 missing path reports Directory not found', !!res.error && /Directory not found/.test(res.error), JSON.stringify(res)); + } + + // C. write_file into a missing parent must not claim success + { + const { ai, vfs } = mk(); + const res = await ai.runTool('write_file', { path: '/missing/dir/f.txt', content: 'x' }); + ok('C1 refused write reports failure', typeof res === 'string' && /Write FAILED/.test(res), String(res)); + ok('C2 nothing was created', vfs.readFile('/missing/dir/f.txt') === null); + } + + // D. the same write succeeds once the parent exists + { + const { ai, vfs } = mk(); + vfs.mkdirRecursive('/missing/dir'); + const res = await ai.runTool('write_file', { path: '/missing/dir/f.txt', content: 'x' }); + ok('D1 successful write still reports success', /File written/.test(String(res)), String(res)); + ok('D2 content landed', vfs.readFile('/missing/dir/f.txt') === 'x'); + } + + if (fails === 0) { + console.log('test-ai-fs-tools: ' + passes + ' passed, 0 failed'); + } else { + console.log('test-ai-fs-tools: ' + passes + ' passed, ' + fails + ' FAILED'); + process.exit(1); + } +})(); |