吸收数字员工周期调度闭环

This commit is contained in:
baiyanyun
2026-06-09 11:06:25 +08:00
parent f35d945d9c
commit 5ebc0a640b
18 changed files with 2083 additions and 145 deletions

View File

@@ -394,6 +394,51 @@ describe("digital employee sync service", () => {
sync_error: "步骤同步被拒绝",
});
});
it("syncs schedule and schedule run business views", async () => {
insertEntity("schedule", "schedule-1");
insertEntity("schedule_run", "schedule-run-1");
insertOutbox("schedule:upsert:schedule-1", "schedule", "schedule-1");
insertOutbox("schedule_run:upsert:schedule-run-1", "schedule_run", "schedule-run-1");
mockState.fetch.mockResolvedValue(
jsonResponse({
success: true,
acked: [
{ entity_type: "schedule", entity_id: "schedule-1", remote_id: "remote-schedule-1" },
{ entity_type: "schedule_run", entity_id: "schedule-run-1", remote_id: "remote-schedule-run-1" },
],
}),
);
const { flushDigitalEmployeeSyncOutbox } = await import("./syncService");
const status = await flushDigitalEmployeeSyncOutbox({ force: true });
expect(status.failed).toBe(0);
expect(lastSyncRequestBody().items).toEqual([
expect.objectContaining({
entity_type: "schedule",
business_view: expect.objectContaining({
title: "同步测试计划 schedule-1",
status: "pending",
}),
}),
expect.objectContaining({
entity_type: "schedule_run",
business_view: expect.objectContaining({
title: "同步测试计划 schedule-run-1",
status: "pending",
}),
}),
]);
expect(readEntity("schedule", "schedule-1")).toMatchObject({
sync_status: "synced",
remote_id: "remote-schedule-1",
});
expect(readEntity("schedule_run", "schedule-run-1")).toMatchObject({
sync_status: "synced",
remote_id: "remote-schedule-run-1",
});
});
});
function insertPlan(id: string): void {
@@ -456,6 +501,8 @@ function entityMap(
if (!mockState.db) return undefined;
if (entityType === "plan") return mockState.db.plans;
if (entityType === "plan_step") return mockState.db.planSteps;
if (entityType === "schedule") return mockState.db.schedules;
if (entityType === "schedule_run") return mockState.db.scheduleRuns;
if (entityType === "artifact") return mockState.db.artifacts;
if (entityType === "approval") return mockState.db.approvals;
return undefined;
@@ -522,6 +569,8 @@ class TestDb {
outbox = new Map<string, TestOutboxRow>();
plans = new Map<string, TestSyncEntityRow>();
planSteps = new Map<string, TestSyncEntityRow>();
schedules = new Map<string, TestSyncEntityRow>();
scheduleRuns = new Map<string, TestSyncEntityRow>();
artifacts = new Map<string, TestSyncEntityRow>();
approvals = new Map<string, TestSyncEntityRow>();
attempts: TestAttemptRow[] = [];
@@ -629,6 +678,16 @@ class TestDb {
return this.planSteps.get(id);
}
if (sql.startsWith("SELECT name AS title, status, description AS objective, payload FROM digital_schedules")) {
const [id] = args as [string];
return this.schedules.get(id);
}
if (sql.startsWith("SELECT id AS title, status, error AS objective, payload FROM digital_schedule_runs")) {
const [id] = args as [string];
return this.scheduleRuns.get(id);
}
if (
sql.includes("COUNT(*) AS count FROM digital_sync_outbox WHERE status = ?")
) {
@@ -726,7 +785,7 @@ class TestDb {
return { changes: before - this.attempts.length };
}
if (/^UPDATE digital_(plans|plan_steps|artifacts|approvals) SET sync_status = 'synced'/.test(sql)) {
if (/^UPDATE digital_(plans|plan_steps|schedules|schedule_runs|artifacts|approvals) SET sync_status = 'synced'/.test(sql)) {
const [remoteId, lastSyncedAt, id] = args as [string | null, string, string];
const row = this.entityRowsForUpdate(sql).get(id);
if (row) {
@@ -738,7 +797,7 @@ class TestDb {
return { changes: row ? 1 : 0 };
}
if (/^UPDATE digital_(plans|plan_steps|artifacts|approvals) SET sync_status = 'failed'/.test(sql)) {
if (/^UPDATE digital_(plans|plan_steps|schedules|schedule_runs|artifacts|approvals) SET sync_status = 'failed'/.test(sql)) {
const [syncError, id] = args as [string, string];
const row = this.entityRowsForUpdate(sql).get(id);
if (row) {
@@ -754,6 +813,8 @@ class TestDb {
private entityRowsForUpdate(sql: string): Map<string, TestSyncEntityRow> {
if (sql.startsWith("UPDATE digital_artifacts")) return this.artifacts;
if (sql.startsWith("UPDATE digital_approvals")) return this.approvals;
if (sql.startsWith("UPDATE digital_schedule_runs")) return this.scheduleRuns;
if (sql.startsWith("UPDATE digital_schedules")) return this.schedules;
if (sql.startsWith("UPDATE digital_plan_steps")) return this.planSteps;
return this.plans;
}