吸收数字员工步骤自动调度
This commit is contained in:
@@ -2,8 +2,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
schedules: [] as Array<Record<string, unknown>>,
|
||||
readyCandidates: [] as Array<Record<string, unknown>>,
|
||||
startedRuns: [] as Array<Record<string, unknown>>,
|
||||
finishedRuns: [] as Array<Record<string, unknown>>,
|
||||
dispatchEvents: [] as Array<Record<string, unknown>>,
|
||||
governanceCommands: [] as Array<Record<string, unknown>>,
|
||||
settings: {
|
||||
enabled: true,
|
||||
catch_up_on_startup: true,
|
||||
@@ -23,6 +26,7 @@ vi.mock("../constants", () => ({
|
||||
|
||||
vi.mock("./stateService", () => ({
|
||||
readDigitalEmployeeCronSettings: () => mockState.settings,
|
||||
listDigitalEmployeeReadyPlanStepDispatchCandidates: () => mockState.readyCandidates,
|
||||
listDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
listDueDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
readDigitalEmployeeScheduleRuns: () => [],
|
||||
@@ -36,13 +40,28 @@ vi.mock("./stateService", () => ({
|
||||
mockState.finishedRuns.push({ id, status, ...options });
|
||||
return { id, status, ...options };
|
||||
},
|
||||
recordDigitalEmployeePlanStepDispatch: (input: Record<string, unknown>) => {
|
||||
mockState.dispatchEvents.push(input);
|
||||
return { eventId: `dispatch-${input.status}`, kind: `plan_step_dispatch_${input.status}`, payload: input };
|
||||
},
|
||||
recordDigitalEmployeeGovernanceCommand: (input: Record<string, unknown>) => {
|
||||
mockState.governanceCommands.push(input);
|
||||
return {
|
||||
planId: input.planId,
|
||||
taskId: input.taskId,
|
||||
runId: input.runId || `${input.taskId}:run:start_run:${input.commandId}`,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe("digital employee scheduler service", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockState.readyCandidates = [];
|
||||
mockState.startedRuns = [];
|
||||
mockState.finishedRuns = [];
|
||||
mockState.dispatchEvents = [];
|
||||
mockState.governanceCommands = [];
|
||||
mockState.schedules = [{
|
||||
id: "schedule-1",
|
||||
planId: "plan-1",
|
||||
@@ -107,4 +126,59 @@ describe("digital employee scheduler service", () => {
|
||||
expect(mockState.finishedRuns[0]).toMatchObject({ status: "failed", error: "ComputerServer 500: boom" });
|
||||
expect(mockState.finishedRuns[0].nextRunAt).toBeTruthy();
|
||||
});
|
||||
|
||||
it("dispatches ready plan steps through local ComputerServer", async () => {
|
||||
mockState.schedules = [];
|
||||
mockState.readyCandidates = [readyStepCandidate()];
|
||||
const { runDigitalEmployeeSchedulerCheck } = await import("./schedulerService");
|
||||
|
||||
const result = await runDigitalEmployeeSchedulerCheck(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result.readyStepDispatch).toMatchObject({ candidates: 1, dispatched: 1, skipped: 0, failed: 0 });
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"http://127.0.0.1:60006/computer/chat",
|
||||
expect.objectContaining({ method: "POST" }),
|
||||
);
|
||||
expect(mockState.dispatchEvents.map((event) => event.status)).toEqual(["started", "completed"]);
|
||||
expect(mockState.governanceCommands[0]).toMatchObject({
|
||||
commandKind: "start_run",
|
||||
planId: "plan-1",
|
||||
taskId: "task-1",
|
||||
stepId: "step-ready",
|
||||
});
|
||||
});
|
||||
|
||||
it("marks ready step dispatch failures", async () => {
|
||||
mockState.schedules = [];
|
||||
mockState.readyCandidates = [readyStepCandidate()];
|
||||
vi.stubGlobal("fetch", vi.fn(async () => ({
|
||||
ok: false,
|
||||
status: 502,
|
||||
text: async () => "bad gateway",
|
||||
})));
|
||||
const { runDigitalEmployeeSchedulerCheck } = await import("./schedulerService");
|
||||
|
||||
const result = await runDigitalEmployeeSchedulerCheck(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result.readyStepDispatch).toMatchObject({ candidates: 1, dispatched: 0, failed: 1 });
|
||||
expect(result.readyStepDispatch.errors[0]).toMatchObject({ stepId: "step-ready", error: "ComputerServer 502: bad gateway" });
|
||||
expect(mockState.dispatchEvents.map((event) => event.status)).toEqual(["started", "failed"]);
|
||||
expect(mockState.governanceCommands).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
function readyStepCandidate(): Record<string, unknown> {
|
||||
return {
|
||||
stepId: "step-ready",
|
||||
planId: "plan-1",
|
||||
taskId: "task-1",
|
||||
title: "汇总客户跟进",
|
||||
taskTitle: "执行客户跟进汇总",
|
||||
prompt: "执行客户跟进汇总",
|
||||
preferredSkillIds: ["qimingclaw-acp-session"],
|
||||
dispatchable: true,
|
||||
step: { id: "step-ready", payload: {} },
|
||||
task: { id: "task-1", payload: {} },
|
||||
plan: { id: "plan-1" },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user