import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { promises as fs } from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { executeMcpCall } from './tool-executor.js'; describe('executeMcpCall', () => { let workspace: string; beforeEach(async () => { workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-exec-')); }); afterEach(async () => { await fs.rm(workspace, { recursive: true, force: true }); }); function baseCtx() { return { workspacePath: workspace, ownerId: 'u1', jobId: 'j1', config: { maxBinarySizeMb: 1, maxOutputFilesPerJob: 5, maxOutputSizeMbPerJob: 10, callTimeoutSeconds: 30, }, quotaState: { files: 0, bytes: 0 }, }; } it('concatenates text content into output', async () => { const fakeClient = { callTool: vi.fn().mockResolvedValue({ content: [ { type: 'text', text: 'hello ' }, { type: 'text', text: 'world' }, ], }), }; const res = await executeMcpCall({ client: fakeClient as never, serverId: 'canva', toolName: 'ping', input: {}, ctx: baseCtx(), }); expect(res.isError).toBeFalsy(); expect(res.output).toContain('hello world'); }); it('saves image content to output/mcp', async () => { const PNG = Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), Buffer.alloc(4, 0)]); const fakeClient = { callTool: vi.fn().mockResolvedValue({ content: [{ type: 'image', data: PNG.toString('base64'), mimeType: 'image/png' }], }), }; const res = await executeMcpCall({ client: fakeClient as never, serverId: 'canva', toolName: 'render', input: {}, ctx: baseCtx(), }); expect(res.isError).toBeFalsy(); expect(res.output).toMatch(/Saved: output\/mcp\/canva\/render-/); // Image must NOT be pushed to images[] (context-bloat guard) expect((res as { images?: unknown }).images).toBeUndefined(); }); it('returns isError when callTool throws', async () => { const fakeClient = { callTool: vi.fn().mockRejectedValue(new Error('boom')), }; const res = await executeMcpCall({ client: fakeClient as never, serverId: 'canva', toolName: 'x', input: {}, ctx: baseCtx(), }); expect(res.isError).toBe(true); expect(res.output).toMatch(/boom/); }); it('writes a raw JSON log under logs/mcp/{serverId}/ on success', async () => { const fakeClient = { callTool: vi.fn().mockResolvedValue({ content: [{ type: 'text', text: 'pong' }], }), }; await executeMcpCall({ client: fakeClient as never, serverId: 'canva', toolName: 'ping', input: { q: 'hi' }, ctx: baseCtx(), }); const files = await fs.readdir(path.join(workspace, 'logs', 'mcp', 'canva')); expect(files.some((f) => f.startsWith('ping-') && f.endsWith('.json'))).toBe(true); const history = await fs.readFile(path.join(workspace, 'logs', 'mcp-history.jsonl'), 'utf-8'); expect(history).toMatch(/"toolName":"ping"/); }); it('writes a raw JSON log under logs/mcp/{serverId}/ on failure', async () => { const fakeClient = { callTool: vi.fn().mockRejectedValue(new Error('boom')), }; await executeMcpCall({ client: fakeClient as never, serverId: 'canva', toolName: 'fail', input: {}, ctx: baseCtx(), }); const files = await fs.readdir(path.join(workspace, 'logs', 'mcp', 'canva')); expect(files.some((f) => f.startsWith('fail-') && f.endsWith('.json'))).toBe(true); const body = JSON.parse( await fs.readFile( path.join(workspace, 'logs', 'mcp', 'canva', files.find((f) => f.startsWith('fail-'))!), 'utf-8', ), ); expect(body.isError).toBe(true); expect(body.output).toMatch(/boom/); }); });