吸收数字员工PlanStep依赖图派发策略
This commit is contained in:
@@ -14,6 +14,12 @@ const mockState = vi.hoisted(() => ({
|
||||
max_catch_up_runs: 5,
|
||||
max_concurrent_schedule_runs: 1,
|
||||
},
|
||||
dispatchPolicy: {
|
||||
enabled: true,
|
||||
max_per_sweep: 3,
|
||||
auto_dispatch_ready_steps: true,
|
||||
updated_at: null as string | null,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../startupPorts", () => ({
|
||||
@@ -26,6 +32,7 @@ vi.mock("../constants", () => ({
|
||||
|
||||
vi.mock("./stateService", () => ({
|
||||
readDigitalEmployeeCronSettings: () => mockState.settings,
|
||||
readDigitalEmployeePlanStepDispatchPolicy: () => mockState.dispatchPolicy,
|
||||
listDigitalEmployeeReadyPlanStepDispatchCandidates: () => mockState.readyCandidates,
|
||||
listDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
listDueDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
@@ -62,6 +69,12 @@ describe("digital employee scheduler service", () => {
|
||||
mockState.finishedRuns = [];
|
||||
mockState.dispatchEvents = [];
|
||||
mockState.governanceCommands = [];
|
||||
mockState.dispatchPolicy = {
|
||||
enabled: true,
|
||||
max_per_sweep: 3,
|
||||
auto_dispatch_ready_steps: true,
|
||||
updated_at: null,
|
||||
};
|
||||
mockState.schedules = [{
|
||||
id: "schedule-1",
|
||||
planId: "plan-1",
|
||||
@@ -165,6 +178,45 @@ describe("digital employee scheduler service", () => {
|
||||
expect(mockState.dispatchEvents.map((event) => event.status)).toEqual(["started", "failed"]);
|
||||
expect(mockState.governanceCommands).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("honors disabled ready step dispatch policy", async () => {
|
||||
mockState.readyCandidates = [readyStepCandidate()];
|
||||
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, enabled: false };
|
||||
const { dispatchReadyPlanSteps } = await import("./schedulerService");
|
||||
|
||||
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result).toMatchObject({ candidates: 1, dispatched: 0, skipped: 1, failed: 0 });
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
expect(mockState.dispatchEvents).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("honors ready step auto dispatch switch", async () => {
|
||||
mockState.readyCandidates = [readyStepCandidate()];
|
||||
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, auto_dispatch_ready_steps: false };
|
||||
const { dispatchReadyPlanSteps } = await import("./schedulerService");
|
||||
|
||||
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result).toMatchObject({ candidates: 1, dispatched: 0, skipped: 1, failed: 0 });
|
||||
expect(fetch).not.toHaveBeenCalled();
|
||||
expect(mockState.dispatchEvents).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("limits ready step dispatches by policy max per sweep", async () => {
|
||||
mockState.readyCandidates = [
|
||||
readyStepCandidate(),
|
||||
{ ...readyStepCandidate(), stepId: "step-ready-2", taskId: "task-2" },
|
||||
];
|
||||
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, max_per_sweep: 1 };
|
||||
const { dispatchReadyPlanSteps } = await import("./schedulerService");
|
||||
|
||||
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result).toMatchObject({ candidates: 2, dispatched: 1, skipped: 1, failed: 0 });
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(mockState.dispatchEvents.map((event) => event.stepId)).toEqual(["step-ready", "step-ready"]);
|
||||
});
|
||||
});
|
||||
|
||||
function readyStepCandidate(): Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user