Files
memind/services/orchestrator/contracts.test.mjs
2026-07-24 23:31:29 +08:00

99 lines
3.2 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildRunEvent,
normalizeRunSpec,
stableRolloutBucket,
} from './contracts.mjs';
import {
createRemoteWorkflowEngine,
createWorkflowEngineRegistry,
} from './engine-registry.mjs';
test('orchestrator contracts normalize run specs without exposing framework types', () => {
const spec = normalizeRunSpec({
runId: 'run-1',
requestId: 'request-1',
workflow: { name: 'code-run-v1', version: 2 },
subject: { userId: 'user-1' },
input: { instruction: 'fix tests' },
});
assert.equal(spec.version, 'orchestrator-run-v1');
assert.equal(spec.workflow.name, 'code-run-v1');
assert.equal(spec.workflow.version, 2);
assert.equal(spec.subject.userId, 'user-1');
const event = buildRunEvent({
runId: spec.runId,
sequence: 1,
type: 'workflow.started',
});
assert.equal(event.version, 'orchestrator-event-v1');
assert.equal(event.type, 'workflow.started');
assert.equal(stableRolloutBucket('run-1'), stableRolloutBucket('run-1'));
});
test('workflow engine registry enforces the framework-neutral contract', () => {
const engine = {
id: 'native',
start() {},
resume() {},
cancel() {},
getState() {},
async *streamEvents() {},
};
const registry = createWorkflowEngineRegistry([engine]);
assert.equal(registry.has('native'), true);
assert.equal(registry.get('native'), engine);
assert.throws(
() => registry.register({ id: 'broken' }),
/missing methods/,
);
});
test('remote workflow engine speaks only the versioned orchestrator HTTP contract', async () => {
const calls = [];
const engine = createRemoteWorkflowEngine({
baseUrl: 'http://orchestrator.internal',
fetchImpl: async (url, init) => {
calls.push({ url, init });
return {
ok: true,
async json() {
return url.includes('/events')
? { events: [{ type: 'run.succeeded' }] }
: { ok: true };
},
};
},
});
await engine.start({ runId: 'run-1' });
await engine.resume('run-1', { approvalId: 'approval-1', decision: 'approve' });
await engine.cancel('run-1');
await engine.getState('run-1');
const events = [];
for await (const event of engine.streamEvents('run-1')) events.push(event);
await engine.getExecutorJob('run-1:executor-preview');
const executorEvents = [];
for await (
const event of engine.streamExecutorJobEvents('run-1:executor-preview', 2)
) {
executorEvents.push(event);
}
assert.deepEqual(calls.map((call) => [call.init?.method ?? 'GET', call.url]), [
['POST', 'http://orchestrator.internal/v1/runs'],
['POST', 'http://orchestrator.internal/v1/runs/run-1/resume'],
['POST', 'http://orchestrator.internal/v1/runs/run-1/cancel'],
['GET', 'http://orchestrator.internal/v1/runs/run-1'],
['GET', 'http://orchestrator.internal/v1/runs/run-1/events'],
['GET', 'http://orchestrator.internal/v1/executor-jobs/run-1%3Aexecutor-preview'],
[
'GET',
'http://orchestrator.internal/v1/executor-jobs/run-1%3Aexecutor-preview/events?after=2',
],
]);
assert.deepEqual(events, [{ type: 'run.succeeded' }]);
assert.deepEqual(executorEvents, [{ type: 'run.succeeded' }]);
});