Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
import { describe, expect, it, vi } from "vitest";
import type { LobsterRunner } from "./lobster-runner.js";
import { resumeManagedLobsterFlow, runManagedLobsterFlow } from "./lobster-taskflow.js";
import { createFakeTaskFlow } from "./taskflow-test-helpers.js";
function expectManagedFlowFailure(
result: Awaited<ReturnType<typeof runManagedLobsterFlow | typeof resumeManagedLobsterFlow>>,
) {
expect(result.ok).toBe(false);
if (result.ok) {
throw new Error("Expected managed Lobster flow to fail");
}
return result;
}
function createRunner(result: Awaited<ReturnType<LobsterRunner["run"]>>): LobsterRunner {
return {
run: vi.fn().mockResolvedValue(result),
};
}
function createRunFlowParams(
taskFlow: ReturnType<typeof createFakeTaskFlow>,
runner: LobsterRunner,
): Parameters<typeof runManagedLobsterFlow>[0] {
return {
taskFlow,
runner,
runnerParams: {
action: "run",
pipeline: "noop",
cwd: process.cwd(),
timeoutMs: 1000,
maxStdoutBytes: 4096,
},
controllerId: "tests/lobster",
goal: "Run Lobster workflow",
};
}
function createResumeFlowParams(
taskFlow: ReturnType<typeof createFakeTaskFlow>,
runner: LobsterRunner,
): Parameters<typeof resumeManagedLobsterFlow>[0] {
return {
taskFlow,
runner,
flowId: "flow-1",
expectedRevision: 4,
runnerParams: {
action: "resume",
token: "resume-1",
approve: true,
cwd: process.cwd(),
timeoutMs: 1000,
maxStdoutBytes: 4096,
},
};
}
describe("runManagedLobsterFlow", () => {
it("creates a flow and finishes it when Lobster succeeds", async () => {
const taskFlow = createFakeTaskFlow();
const runner = createRunner({
ok: true,
status: "ok",
output: [{ id: "result-1" }],
requiresApproval: null,
});
const result = await runManagedLobsterFlow(createRunFlowParams(taskFlow, runner));
expect(result.ok).toBe(true);
expect(taskFlow.createManaged).toHaveBeenCalledWith({
controllerId: "tests/lobster",
goal: "Run Lobster workflow",
currentStep: "run_lobster",
});
expect(taskFlow.finish).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 1,
});
});
it("moves the flow to waiting when Lobster requests approval", async () => {
const taskFlow = createFakeTaskFlow();
const createdAt = new Date("2026-04-05T21:00:00.000Z");
const runner = createRunner({
ok: true,
status: "needs_approval",
output: [],
requiresApproval: {
type: "approval_request",
prompt: "Approve this?",
items: [{ id: "item-1", createdAt, count: 2n, skip: undefined }],
resumeToken: "resume-1",
},
});
const result = await runManagedLobsterFlow(createRunFlowParams(taskFlow, runner));
expect(result.ok).toBe(true);
expect(taskFlow.setWaiting).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 1,
currentStep: "await_lobster_approval",
waitJson: {
kind: "lobster_approval",
prompt: "Approve this?",
items: [{ id: "item-1", createdAt: createdAt.toISOString(), count: "2" }],
resumeToken: "resume-1",
},
});
});
it("fails the flow when Lobster returns an error envelope", async () => {
const taskFlow = createFakeTaskFlow();
const runner = createRunner({
ok: false,
error: {
type: "runtime_error",
message: "boom",
},
});
const result = expectManagedFlowFailure(
await runManagedLobsterFlow(createRunFlowParams(taskFlow, runner)),
);
expect(result.error.message).toBe("boom");
expect(taskFlow.fail).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 1,
});
});
it("fails the flow when the runner throws", async () => {
const taskFlow = createFakeTaskFlow();
const runner: LobsterRunner = {
run: vi.fn().mockRejectedValue(new Error("crashed")),
};
const result = expectManagedFlowFailure(
await runManagedLobsterFlow(createRunFlowParams(taskFlow, runner)),
);
expect(result.error.message).toBe("crashed");
expect(taskFlow.fail).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 1,
});
});
});
describe("resumeManagedLobsterFlow", () => {
it("resumes the flow and finishes it on success", async () => {
const taskFlow = createFakeTaskFlow();
const runner = createRunner({
ok: true,
status: "ok",
output: [],
requiresApproval: null,
});
const result = await resumeManagedLobsterFlow(createResumeFlowParams(taskFlow, runner));
expect(result.ok).toBe(true);
expect(taskFlow.resume).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 4,
status: "running",
currentStep: "resume_lobster",
});
expect(taskFlow.finish).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 5,
});
});
it("returns a mutation error when taskFlow resume is rejected", async () => {
const taskFlow = createFakeTaskFlow({
resume: vi.fn().mockReturnValue({
applied: false,
code: "revision_conflict",
}),
});
const runner = createRunner({
ok: true,
status: "ok",
output: [],
requiresApproval: null,
});
const result = expectManagedFlowFailure(
await resumeManagedLobsterFlow(createResumeFlowParams(taskFlow, runner)),
);
expect(result.error.message).toMatch(/revision_conflict/);
expect(runner.run).not.toHaveBeenCalled();
});
it("returns to waiting when the resumed Lobster run needs approval again", async () => {
const taskFlow = createFakeTaskFlow();
const runner = createRunner({
ok: true,
status: "needs_approval",
output: [],
requiresApproval: {
type: "approval_request",
prompt: "Approve this too?",
items: [{ id: "item-2" }],
resumeToken: "resume-2",
},
});
const result = await resumeManagedLobsterFlow(createResumeFlowParams(taskFlow, runner));
expect(result.ok).toBe(true);
expect(taskFlow.setWaiting).toHaveBeenCalledWith({
flowId: "flow-1",
expectedRevision: 5,
currentStep: "await_lobster_approval",
waitJson: {
kind: "lobster_approval",
prompt: "Approve this too?",
items: [{ id: "item-2" }],
resumeToken: "resume-2",
},
});
});
});
¤ Dauer der Verarbeitung: 0.0 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland
|
|