89 lines
2.8 KiB
JavaScript
89 lines
2.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { MemorySaver } from '@langchain/langgraph';
|
|
import { createLangGraphOrchestratorRuntime } from './runtime.mjs';
|
|
|
|
function runSpec(overrides = {}) {
|
|
return {
|
|
runId: 'run-shadow-1',
|
|
requestId: 'request-shadow-1',
|
|
workflow: { name: 'code-run-v1', version: 1 },
|
|
subject: { userId: 'user-1' },
|
|
input: {
|
|
instruction: 'Add an idempotency test',
|
|
taskType: 'code-change',
|
|
},
|
|
policy: {
|
|
executionMode: 'observe-only',
|
|
sideEffectsAllowed: false,
|
|
},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('LangGraph runtime checkpoints a deterministic observe-only code workflow', async () => {
|
|
const runtime = createLangGraphOrchestratorRuntime({
|
|
checkpointer: new MemorySaver(),
|
|
checkpointKind: 'memory',
|
|
durable: false,
|
|
});
|
|
|
|
const state = await runtime.start(runSpec());
|
|
assert.equal(state.runId, 'run-shadow-1');
|
|
assert.equal(state.status, 'succeeded');
|
|
assert.equal(state.phase, 'completed');
|
|
assert.equal(state.result.observed, true);
|
|
assert.equal(state.result.executed, false);
|
|
assert.equal(state.plan.executorAdapter, 'native-agent-run');
|
|
|
|
const restored = await runtime.getState('run-shadow-1');
|
|
assert.deepEqual(restored, state);
|
|
|
|
const eventPage = await runtime.listEvents('run-shadow-1');
|
|
assert.deepEqual(
|
|
eventPage.events.map((event) => event.type),
|
|
['workflow_validated', 'workflow_planned', 'workflow_completed'],
|
|
);
|
|
assert.deepEqual(
|
|
eventPage.events.map((event) => event.sequence),
|
|
[1, 2, 3],
|
|
);
|
|
assert.equal(eventPage.nextCursor, 3);
|
|
assert.equal((await runtime.listEvents('run-shadow-1', { after: 2 })).events.length, 1);
|
|
});
|
|
|
|
test('LangGraph runtime is idempotent by run id', async () => {
|
|
const runtime = createLangGraphOrchestratorRuntime({
|
|
checkpointer: new MemorySaver(),
|
|
});
|
|
|
|
const first = await runtime.start(runSpec());
|
|
const second = await runtime.start(runSpec({
|
|
input: { instruction: 'This must not replace the first checkpoint' },
|
|
}));
|
|
|
|
assert.deepEqual(second, first);
|
|
assert.equal((await runtime.listEvents('run-shadow-1')).events.length, 3);
|
|
});
|
|
|
|
test('LangGraph runtime rejects execution-enabled and unsupported workflows', async () => {
|
|
const runtime = createLangGraphOrchestratorRuntime({
|
|
checkpointer: new MemorySaver(),
|
|
});
|
|
|
|
await assert.rejects(
|
|
runtime.start(runSpec({
|
|
runId: 'run-active',
|
|
policy: { executionMode: 'active', sideEffectsAllowed: true },
|
|
})),
|
|
(error) => error.code === 'WORKFLOW_OBSERVE_ONLY_REQUIRED',
|
|
);
|
|
await assert.rejects(
|
|
runtime.start(runSpec({
|
|
runId: 'run-unsupported',
|
|
workflow: { name: 'research-v1', version: 1 },
|
|
})),
|
|
(error) => error.code === 'WORKFLOW_NOT_SUPPORTED' && error.status === 422,
|
|
);
|
|
});
|