吸收数字员工多步审批跨端回写
This commit is contained in:
@@ -297,6 +297,84 @@ describe("digital employee sync service", () => {
|
||||
expect(result.policy.approval_required_actions).toEqual(["managed_service.restart.fileServer"]);
|
||||
});
|
||||
|
||||
it("pulls remote approval updates into local approval workflow records", async () => {
|
||||
mockState.fetch.mockResolvedValue(jsonResponse({
|
||||
success: true,
|
||||
data: {
|
||||
approvals: [
|
||||
{
|
||||
approval_id: "approval-remote-1",
|
||||
title: "高风险动作审批",
|
||||
status: "pending",
|
||||
plan_id: "plan-1",
|
||||
task_id: "task-1",
|
||||
run_id: "run-1",
|
||||
updated_at: "2026-06-07T08:03:00.000Z",
|
||||
payload: {
|
||||
workflow: {
|
||||
workflow_id: "workflow-remote-1",
|
||||
current_step_id: "step-2",
|
||||
steps: [
|
||||
{ step_id: "step-1", label: "主管审批", status: "approved" },
|
||||
{ step_id: "step-2", label: "风控复核", status: "pending", participant_label: "风控" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
const { pullDigitalEmployeeApprovalUpdates } = await import("./syncService");
|
||||
const result = await pullDigitalEmployeeApprovalUpdates({ force: true });
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
endpoint: "https://manage.example.com/api/digital-employee/approvals/updates",
|
||||
applied: 1,
|
||||
ignored: 0,
|
||||
});
|
||||
expect(mockState.fetch).toHaveBeenCalledWith(
|
||||
"https://manage.example.com/api/digital-employee/approvals/updates",
|
||||
expect.objectContaining({ method: "GET" }),
|
||||
);
|
||||
const approval = mockState.db?.approvals.get("approval-remote-1");
|
||||
expect(approval).toMatchObject({ title: "高风险动作审批", status: "pending", sync_status: "pending" });
|
||||
expect(JSON.parse(approval?.payload ?? "{}")).toMatchObject({
|
||||
workflow: {
|
||||
workflow_id: "workflow-remote-1",
|
||||
current_step_id: "step-2",
|
||||
},
|
||||
remote_updated_at: "2026-06-07T08:03:00.000Z",
|
||||
});
|
||||
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "digital_workday_approval_updates_pulled" }),
|
||||
]));
|
||||
});
|
||||
|
||||
it("submits approval decisions to management and records submission events", async () => {
|
||||
mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { accepted: true } }));
|
||||
|
||||
const { submitDigitalEmployeeApprovalDecision } = await import("./syncService");
|
||||
const result = await submitDigitalEmployeeApprovalDecision({ approval_id: "approval-1", decision: "approved" });
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
endpoint: "https://manage.example.com/api/digital-employee/approvals/decisions",
|
||||
submitted_at: "2026-06-07T08:00:00.000Z",
|
||||
});
|
||||
expect(mockState.fetch).toHaveBeenCalledWith(
|
||||
"https://manage.example.com/api/digital-employee/approvals/decisions",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: expect.stringContaining("approval-1"),
|
||||
}),
|
||||
);
|
||||
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "digital_workday_approval_decision_submitted" }),
|
||||
]));
|
||||
});
|
||||
|
||||
it.each([
|
||||
["skillPolicies"],
|
||||
["policies"],
|
||||
@@ -1501,6 +1579,8 @@ interface TestSyncEntityRow {
|
||||
sync_status: string;
|
||||
sync_error: string | null;
|
||||
last_synced_at: string | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
interface TestAttemptRow {
|
||||
@@ -1646,6 +1726,11 @@ class TestDb {
|
||||
return this.approvals.get(id);
|
||||
}
|
||||
|
||||
if (sql.startsWith("SELECT id, plan_id, task_id, run_id, title, status, payload, created_at, updated_at FROM digital_approvals")) {
|
||||
const [id] = args as [string];
|
||||
return this.approvals.get(id);
|
||||
}
|
||||
|
||||
if (sql.startsWith("SELECT key AS title, status, content AS summary, payload FROM digital_memories")) {
|
||||
const [id] = args as [string];
|
||||
return this.memories.get(id);
|
||||
@@ -1817,6 +1902,37 @@ class TestDb {
|
||||
return { changes: 1 };
|
||||
}
|
||||
|
||||
if (sql.startsWith("INSERT INTO digital_approvals")) {
|
||||
const [id, planId, taskId, runId, title, status, payload, createdAt, updatedAt] = args as [
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
string,
|
||||
];
|
||||
const previous = this.approvals.get(id);
|
||||
this.approvals.set(id, {
|
||||
id,
|
||||
plan_id: planId,
|
||||
task_id: taskId,
|
||||
run_id: runId,
|
||||
title,
|
||||
status,
|
||||
payload,
|
||||
remote_id: previous?.remote_id ?? null,
|
||||
sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending",
|
||||
sync_error: previous?.sync_error ?? null,
|
||||
last_synced_at: previous?.last_synced_at ?? null,
|
||||
created_at: previous?.created_at ?? createdAt,
|
||||
updated_at: updatedAt,
|
||||
});
|
||||
return { changes: 1 };
|
||||
}
|
||||
|
||||
if (sql.startsWith("INSERT INTO digital_sync_outbox")) {
|
||||
const [id, entityType, entityId, operation, payload, createdAt, updatedAt] = args as [
|
||||
string,
|
||||
|
||||
Reference in New Issue
Block a user