Files
memind/services/orchestrator/executor-adapters.test.mjs

180 lines
5.8 KiB
JavaScript

import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
createAiderExecutorAdapter,
createGoosedExecutorAdapter,
createOpenHandsExecutorAdapter,
} from './executor-adapters.mjs';
function request(executor = 'goosed') {
return {
executor,
task: {
instruction: 'Fix the validation',
workspaceRef: { kind: 'workspace-alias', id: 'canary' },
},
};
}
function sseResponse(events) {
const encoder = new TextEncoder();
const body = new ReadableStream({
start(controller) {
for (const event of events) {
controller.enqueue(encoder.encode(
`event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`,
));
}
controller.close();
},
});
return new Response(body, {
status: 200,
headers: { 'content-type': 'text/event-stream' },
});
}
test('Goosed adapter uses the current session API, streams bounded metadata and returns refs', async () => {
const calls = [];
const emitted = [];
const adapter = createGoosedExecutorAdapter({
baseUrl: 'http://127.0.0.1:18006',
secret: 'secret',
workspaceResolver: async () => '/tmp/canary',
fetchImpl: async (url, init) => {
calls.push({ url, init });
if (url.endsWith('/agent/start')) {
return Response.json({ id: 'session-1' });
}
if (url.endsWith('/events')) {
return sseResponse([
{ type: 'message', data: { type: 'message', secret: 'not-projected' } },
{ type: 'finish', data: { type: 'finish' } },
]);
}
if (url.endsWith('/reply')) return Response.json({ ok: true });
if (url.endsWith('/status')) return Response.json({ ok: true });
throw new Error(`Unexpected URL: ${url}`);
},
});
const result = await adapter.submit(request(), {
jobId: 'job-1',
emit: async (type, data) => emitted.push({ type, data }),
});
assert.deepEqual(result.artifactRefs, [{ kind: 'goosed-session', id: 'session-1' }]);
assert.equal(result.metrics.eventCount, 2);
assert.equal(calls[0].url, 'http://127.0.0.1:18006/agent/start');
assert.equal(calls[0].init.headers['X-Secret-Key'], 'secret');
assert.equal(JSON.parse(calls[0].init.body).working_dir, '/tmp/canary');
assert.equal(
JSON.parse(calls.find((call) => call.url.endsWith('/reply')).init.body)
.user_message.content[0].text,
'Fix the validation',
);
assert.deepEqual(
emitted.map((event) => event.type),
[
'executor_job_session_created',
'executor_job_adapter_event',
'executor_job_adapter_event',
],
);
assert.equal(JSON.stringify(emitted).includes('not-projected'), false);
assert.deepEqual(await adapter.health(), { ok: true, status: 200 });
});
test('Goosed adapter rejects remote targets unless explicitly allowed', () => {
assert.throws(
() => createGoosedExecutorAdapter({ baseUrl: 'https://executor.example.com' }),
/explicit allowRemote/,
);
assert.doesNotThrow(
() => createGoosedExecutorAdapter({
baseUrl: 'https://executor.example.com',
allowRemote: true,
}),
);
assert.throws(
() => createGoosedExecutorAdapter({
baseUrl: 'https://executor.example.com',
allowRemote: true,
tlsInsecure: true,
}),
/restricted to local targets/,
);
assert.doesNotThrow(
() => createGoosedExecutorAdapter({
baseUrl: 'https://host.docker.internal:18006',
allowRemote: true,
tlsInsecure: true,
}),
);
});
test('Aider process adapter resolves a workspace alias and never invokes a shell', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-executor-'));
const workspace = path.join(root, 'canary');
await fs.mkdir(workspace);
const spawnCalls = [];
const emitted = [];
const spawnImpl = (command, args, options) => {
spawnCalls.push({ command, args, options });
const child = new EventEmitter();
child.stdout = new EventEmitter();
child.stderr = new EventEmitter();
child.kill = () => {};
queueMicrotask(() => {
child.stdout.emit('data', Buffer.from('done'));
child.emit('exit', 0, null);
});
return child;
};
try {
const adapter = createAiderExecutorAdapter({
command: '/usr/local/bin/aider',
allowedRoot: root,
workspaceAliases: { canary: workspace },
spawnImpl,
});
const result = await adapter.submit(request('aider'), {
jobId: 'job-1',
emit: async (type, data) => emitted.push({ type, data }),
});
assert.equal(result.outcome, 'completed');
assert.deepEqual(result.artifactRefs, [{ kind: 'workspace-alias', id: 'canary' }]);
assert.equal(spawnCalls[0].options.cwd, await fs.realpath(workspace));
assert.equal(spawnCalls[0].options.shell, false);
assert.deepEqual(
spawnCalls[0].args,
['--yes-always', '--message', 'Fix the validation'],
);
assert.equal(emitted[0].data.stream, 'stdout');
assert.equal('HOME' in spawnCalls[0].options.env, false);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test('OpenHands adapter refuses workspace aliases outside its isolated root', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-executor-root-'));
const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'memind-executor-outside-'));
try {
const adapter = createOpenHandsExecutorAdapter({
command: '/usr/local/bin/openhands',
allowedRoot: root,
workspaceAliases: { canary: outside },
});
await assert.rejects(
adapter.submit(request('openhands'), { jobId: 'job-1' }),
(error) => error.code === 'EXECUTOR_WORKSPACE_OUTSIDE_ROOT',
);
} finally {
await fs.rm(root, { recursive: true, force: true });
await fs.rm(outside, { recursive: true, force: true });
}
});