local createLibTest = require('/apis/libtest'); local createMcpComputer = require('/apis/libmcpcomputer'); local testlib = createLibTest({ ... }); local function fakeOs(id, label) return { getComputerID = function() return id; end, getComputerLabel = function() return label; end, }; end local function fakeFs() local files = {}; return { files = files, open = function(path, mode) if mode == 'r' then if files[path] == nil then return nil, 'No such file'; end return { readAll = function() return files[path]; end, close = function() end, }; end if mode ~= 'w' then return nil, 'unsupported mode'; end if string.find(path, 'missing-parent', 1, true) then return nil, 'No such file'; end local buffer = {}; return { write = function(value) buffer[#buffer + 1] = value; end, close = function() files[path] = table.concat(buffer); end, }; end, }; end local function fsWithFiles(files) local fsLike = fakeFs(); for path, content in pairs(files) do fsLike.files[path] = content; end return fsLike; end testlib.test('formatPong includes computer id and label', function() local mcpComputer = createMcpComputer(); testlib.assertEquals(mcpComputer.formatPong(fakeOs(12, 'base-turtle')), 'pong from 12 (Label: base-turtle)'); end); testlib.test('formatPong renders missing or empty labels as null', function() local mcpComputer = createMcpComputer(); testlib.assertEquals(mcpComputer.formatPong(fakeOs(7, nil)), 'pong from 7 (Label: null)'); testlib.assertEquals(mcpComputer.formatPong(fakeOs(8, '')), 'pong from 8 (Label: null)'); end); testlib.test('executeLua captures output and return values', function() local mcpComputer = createMcpComputer(); local result = mcpComputer.executeLua("print('hello', 'world'); write('tail'); return 2 + 3, nil, 'ok'"); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.output, 'hello\tworld\ntail'); testlib.assertEquals(result.returns[1].type, 'number'); testlib.assertEquals(result.returns[1].value, 5); testlib.assertEquals(result.returns[2].type, 'nil'); testlib.assertEquals(result.returns[3].type, 'string'); testlib.assertEquals(result.returns[3].value, 'ok'); end); testlib.test('executeLua reports runtime errors with captured output', function() local mcpComputer = createMcpComputer(); local result = mcpComputer.executeLua("print('before'); error('boom', 0)"); testlib.assertEquals(result.ok, false); testlib.assertEquals(result.output, 'before\n'); testlib.assertTrue(string.find(result.error, 'boom', 1, true)); end); testlib.test('executeLua reports syntax errors', function() local mcpComputer = createMcpComputer(); local result = mcpComputer.executeLua("print('unterminated'\nreturn 1"); testlib.assertEquals(result.ok, false); testlib.assertEquals(result.output, ''); testlib.assertTrue(string.find(result.error, 'expected', 1, true) or string.find(result.error, 'near', 1, true)); end); testlib.test('executeLua serializes non-json return values as descriptors', function() local mcpComputer = createMcpComputer(); local result = mcpComputer.executeLua('return { answer = 42 }, function() end, coroutine.create(function() end)'); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.returns[1].type, 'table'); testlib.assertTrue(type(result.returns[1].repr) == 'string'); testlib.assertEquals(result.returns[2].type, 'function'); testlib.assertTrue(type(result.returns[2].repr) == 'string'); testlib.assertEquals(result.returns[3].type, 'thread'); testlib.assertTrue(type(result.returns[3].repr) == 'string'); end); testlib.test('executeLua leaves direct terminal writes out of captured output', function() local mcpComputer = createMcpComputer(); local result = mcpComputer.executeLua("term.write('visible'); return 'done'"); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.output, ''); testlib.assertEquals(result.returns[1].type, 'string'); testlib.assertEquals(result.returns[1].value, 'done'); end); testlib.test('executeLua injects require and package into the environment', function() local seenEnv, seenDir; local mcpComputer = createMcpComputer({ requireFactory = function(env, dir) seenEnv, seenDir = env, dir; return function() return 'REQUIRED'; end, { loaded = {} }; end, }); local result = mcpComputer.executeLua("return require('whatever'), type(package)"); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.returns[1].type, 'string'); testlib.assertEquals(result.returns[1].value, 'REQUIRED'); testlib.assertEquals(result.returns[2].type, 'string'); testlib.assertEquals(result.returns[2].value, 'table'); testlib.assertEquals(type(seenEnv), 'table'); testlib.assertEquals(seenDir, ''); end); testlib.test('executeLua passes the configured requireDir to the factory', function() local seenDir; local mcpComputer = createMcpComputer({ requireDir = '/apis', requireFactory = function(_, dir) seenDir = dir; return function() end, {}; end, }); mcpComputer.executeLua('return 1'); testlib.assertEquals(seenDir, '/apis'); end); testlib.test('executeLua leaves require unset when injection is disabled', function() local mcpComputer = createMcpComputer({ requireFactory = false }); local result = mcpComputer.executeLua('return require == nil, package == nil'); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.returns[1].type, 'boolean'); testlib.assertEquals(result.returns[1].value, true); testlib.assertEquals(result.returns[2].type, 'boolean'); testlib.assertEquals(result.returns[2].value, true); end); testlib.test('executeFile injects require into the file environment', function() local mcpComputer = createMcpComputer({ requireFactory = function() return function() return 'FROM_FILE'; end, {}; end, }); local fsLike = fsWithFiles({ ['/needs-require.lua'] = "return require('x')", }); local result = mcpComputer.executeFile('/needs-require.lua', fsLike); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.returns[1].type, 'string'); testlib.assertEquals(result.returns[1].value, 'FROM_FILE'); end); testlib.test('executeFile captures output and return values from file', function() local mcpComputer = createMcpComputer(); local fsLike = fsWithFiles({ ['/helloworld.lua'] = "print('hello', 'file'); write('tail'); return 42, 'ok'", }); local result = mcpComputer.executeFile('/helloworld.lua', fsLike); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.output, 'hello\tfile\ntail'); testlib.assertEquals(result.returns[1].type, 'number'); testlib.assertEquals(result.returns[1].value, 42); testlib.assertEquals(result.returns[2].type, 'string'); testlib.assertEquals(result.returns[2].value, 'ok'); end); testlib.test('executeFile reports validation and missing file errors', function() local mcpComputer = createMcpComputer(); local fsLike = fakeFs(); local missingPath = mcpComputer.executeFile('', fsLike); local missingFile = mcpComputer.executeFile('/missing.lua', fsLike); testlib.assertEquals(missingPath.ok, false); testlib.assertTrue(string.find(missingPath.error, 'path', 1, true)); testlib.assertEquals(missingPath.output, ''); testlib.assertEquals(missingFile.ok, false); testlib.assertTrue(string.find(missingFile.error, 'No such file', 1, true)); testlib.assertEquals(missingFile.output, ''); end); testlib.test('executeFile reports syntax errors with file chunk name', function() local mcpComputer = createMcpComputer(); local fsLike = fsWithFiles({ ['/broken.lua'] = "print('unterminated'\nreturn 1", }); local result = mcpComputer.executeFile('/broken.lua', fsLike); testlib.assertEquals(result.ok, false); testlib.assertEquals(result.output, ''); testlib.assertTrue(string.find(result.error, '/broken.lua', 1, true)); end); testlib.test('executeFile reports runtime errors with captured output', function() local mcpComputer = createMcpComputer(); local fsLike = fsWithFiles({ ['/boom.lua'] = "print('before'); error('boom', 0)", }); local result = mcpComputer.executeFile('/boom.lua', fsLike); testlib.assertEquals(result.ok, false); testlib.assertEquals(result.output, 'before\n'); testlib.assertTrue(string.find(result.error, 'boom', 1, true)); end); testlib.test('executeFile leaves direct terminal writes out of captured output', function() local mcpComputer = createMcpComputer(); local fsLike = fsWithFiles({ ['/screen.lua'] = "term.write('visible'); return 'done'", }); local result = mcpComputer.executeFile('/screen.lua', fsLike); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.output, ''); testlib.assertEquals(result.returns[1].type, 'string'); testlib.assertEquals(result.returns[1].value, 'done'); end); testlib.test('writeFile writes and overwrites file content', function() local mcpComputer = createMcpComputer(); local fsLike = fakeFs(); local first = mcpComputer.writeFile('/tmp/note.txt', 'hello', fsLike); local second = mcpComputer.writeFile('/tmp/note.txt', 'goodbye', fsLike); testlib.assertEquals(first.ok, true); testlib.assertEquals(first.path, '/tmp/note.txt'); testlib.assertEquals(first.bytes, 5); testlib.assertEquals(second.ok, true); testlib.assertEquals(second.bytes, 7); testlib.assertEquals(fsLike.files['/tmp/note.txt'], 'goodbye'); end); testlib.test('writeFile allows empty content', function() local mcpComputer = createMcpComputer(); local fsLike = fakeFs(); local result = mcpComputer.writeFile('/tmp/empty.txt', '', fsLike); testlib.assertEquals(result.ok, true); testlib.assertEquals(result.bytes, 0); testlib.assertEquals(fsLike.files['/tmp/empty.txt'], ''); end); testlib.test('writeFile validates path and content', function() local mcpComputer = createMcpComputer(); local fsLike = fakeFs(); local missingPath = mcpComputer.writeFile('', 'hello', fsLike); local invalidContent = mcpComputer.writeFile('/tmp/note.txt', 12, fsLike); local missingParent = mcpComputer.writeFile('/missing-parent/note.txt', 'hello', fsLike); testlib.assertEquals(missingPath.ok, false); testlib.assertTrue(string.find(missingPath.error, 'path', 1, true)); testlib.assertEquals(invalidContent.ok, false); testlib.assertTrue(string.find(invalidContent.error, 'content', 1, true)); testlib.assertEquals(missingParent.ok, false); testlib.assertTrue(string.find(missingParent.error, 'No such file', 1, true)); end); testlib.run();