吸收数字员工周期调度闭环
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mockState = vi.hoisted(() => ({
|
||||
schedules: [] as Array<Record<string, unknown>>,
|
||||
startedRuns: [] as Array<Record<string, unknown>>,
|
||||
finishedRuns: [] as Array<Record<string, unknown>>,
|
||||
settings: {
|
||||
enabled: true,
|
||||
catch_up_on_startup: true,
|
||||
max_run_history: 100,
|
||||
max_catch_up_runs: 5,
|
||||
max_concurrent_schedule_runs: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../startupPorts", () => ({
|
||||
getConfiguredPorts: () => ({ agent: 60006 }),
|
||||
}));
|
||||
|
||||
vi.mock("../constants", () => ({
|
||||
LOCALHOST_HOSTNAME: "127.0.0.1",
|
||||
}));
|
||||
|
||||
vi.mock("./stateService", () => ({
|
||||
readDigitalEmployeeCronSettings: () => mockState.settings,
|
||||
listDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
listDueDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
readDigitalEmployeeScheduleRuns: () => [],
|
||||
upsertDigitalEmployeeSchedule: vi.fn(),
|
||||
recordDigitalEmployeeScheduleRunStarted: (input: Record<string, unknown>) => {
|
||||
const run = { id: `${input.scheduleId}:${input.dueAt}:1`, ...input };
|
||||
mockState.startedRuns.push(run);
|
||||
return run;
|
||||
},
|
||||
finishDigitalEmployeeScheduleRun: (id: string, status: string, options: Record<string, unknown>) => {
|
||||
mockState.finishedRuns.push({ id, status, ...options });
|
||||
return { id, status, ...options };
|
||||
},
|
||||
}));
|
||||
|
||||
describe("digital employee scheduler service", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockState.startedRuns = [];
|
||||
mockState.finishedRuns = [];
|
||||
mockState.schedules = [{
|
||||
id: "schedule-1",
|
||||
planId: "plan-1",
|
||||
name: "晨间巡检",
|
||||
description: "每日巡检",
|
||||
cronExpr: "0 9 * * *",
|
||||
timezone: "system",
|
||||
status: "enabled",
|
||||
enabled: true,
|
||||
prompt: "执行晨间巡检",
|
||||
command: null,
|
||||
payload: {},
|
||||
nextRunAt: "2026-06-07T01:00:00.000Z",
|
||||
lastRunAt: null,
|
||||
catchUpOnStartup: true,
|
||||
maxCatchUpRuns: 5,
|
||||
maxRunHistory: 100,
|
||||
failureCount: 0,
|
||||
lastError: null,
|
||||
syncStatus: "pending",
|
||||
createdAt: "2026-06-07T00:00:00.000Z",
|
||||
updatedAt: "2026-06-07T00:00:00.000Z",
|
||||
deletedAt: null,
|
||||
}];
|
||||
vi.stubGlobal("fetch", vi.fn(async () => ({
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({ code: "0000", success: true, data: { request_id: "r1" } }),
|
||||
})));
|
||||
});
|
||||
|
||||
it("computes the next 5-field cron run", async () => {
|
||||
const { nextCronRun } = await import("./schedulerService");
|
||||
expect(nextCronRun("30 9 * * *", new Date("2026-06-07T01:29:30.000Z")).toISOString())
|
||||
.toBe("2026-06-07T01:30:00.000Z");
|
||||
});
|
||||
|
||||
it("activates due schedules through local ComputerServer", async () => {
|
||||
const { runDigitalEmployeeSchedulerCheck } = await import("./schedulerService");
|
||||
|
||||
const result = await runDigitalEmployeeSchedulerCheck(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result.activated).toBe(1);
|
||||
expect(mockState.startedRuns).toHaveLength(1);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"http://127.0.0.1:60006/computer/chat",
|
||||
expect.objectContaining({ method: "POST" }),
|
||||
);
|
||||
expect(mockState.finishedRuns[0]).toMatchObject({ status: "completed" });
|
||||
});
|
||||
|
||||
it("records failed runs with retry next run", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => ({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: async () => "boom",
|
||||
})));
|
||||
const { runDigitalEmployeeSchedulerCheck } = await import("./schedulerService");
|
||||
|
||||
const result = await runDigitalEmployeeSchedulerCheck(new Date("2026-06-07T01:00:10.000Z"));
|
||||
|
||||
expect(result.errors).toHaveLength(1);
|
||||
expect(mockState.finishedRuns[0]).toMatchObject({ status: "failed", error: "ComputerServer 500: boom" });
|
||||
expect(mockState.finishedRuns[0].nextRunAt).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user