吸收数字员工通知跨端同步

This commit is contained in:
baiyanyun
2026-06-11 20:27:10 +08:00
parent 4c862ef4b7
commit 8278fdc81c
15 changed files with 745 additions and 193 deletions

View File

@@ -446,6 +446,157 @@ describe("digital employee sync service", () => {
]));
});
it("pulls remote notification updates into local notification overlays", async () => {
mockState.settings.set("digital_employee_ui_state_v1", {
notificationStateById: {
"task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" },
},
});
mockState.fetch.mockResolvedValue(jsonResponse({
success: true,
data: {
notifications: [
{
notification_id: "task:need-input:1",
status: "read",
read_at: "2026-06-07T08:01:00.000Z",
remote_updated_at: "2026-06-07T08:02:00.000Z",
source: "management-inbox",
},
{
notificationId: "task:reply:1",
status: "resolved",
reply: "已补充客户编号",
repliedAt: "2026-06-07T08:03:00.000Z",
},
],
},
}));
const { pullDigitalEmployeeNotificationUpdates } = await import("./syncService");
const { readDigitalEmployeeUiState } = await import("./stateService");
const result = await pullDigitalEmployeeNotificationUpdates({ force: true });
expect(result).toMatchObject({
ok: true,
endpoint: "https://manage.example.com/api/digital-employee/notifications/updates",
applied: 2,
ignored: 0,
pulled_at: "2026-06-07T08:00:00.000Z",
});
expect(mockState.fetch).toHaveBeenCalledWith(
"https://manage.example.com/api/digital-employee/notifications/updates",
expect.objectContaining({ method: "GET" }),
);
expect(readDigitalEmployeeUiState().notificationStateById).toMatchObject({
"task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" },
"task:need-input:1": {
status: "read",
read_at: "2026-06-07T08:01:00.000Z",
remote_updated_at: "2026-06-07T08:02:00.000Z",
source: "management-inbox",
},
"task:reply:1": {
status: "resolved",
reply: "已补充客户编号",
replied_at: "2026-06-07T08:03:00.000Z",
source: "management-api",
},
});
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "digital_workday_notification_updates_pulled" }),
]));
});
it("keeps local notification overlays when remote notification update pull fails", async () => {
mockState.settings.set("digital_employee_ui_state_v1", {
notificationStateById: {
"task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" },
},
});
mockState.fetch.mockResolvedValue({
ok: false,
status: 503,
text: vi.fn().mockResolvedValue(JSON.stringify({ message: "notification service unavailable" })),
} as unknown as Response);
const { pullDigitalEmployeeNotificationUpdates } = await import("./syncService");
const { readDigitalEmployeeUiState } = await import("./stateService");
const result = await pullDigitalEmployeeNotificationUpdates({ force: true });
expect(result).toMatchObject({
ok: false,
endpoint: "https://manage.example.com/api/digital-employee/notifications/updates",
applied: 0,
ignored: 0,
error: "notification service unavailable",
});
expect(readDigitalEmployeeUiState().notificationStateById).toMatchObject({
"task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" },
});
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "digital_workday_notification_updates_pull_failed" }),
]));
});
it("submits notification actions to management and records submission events", async () => {
mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { accepted: true } }));
const { submitDigitalEmployeeNotificationAction } = await import("./syncService");
const result = await submitDigitalEmployeeNotificationAction({
notification_id: "task:need-input:1",
action: "reply",
status: "read",
reply: "已补充客户编号",
replied_at: "2026-06-07T08:03:00.000Z",
});
expect(result).toMatchObject({
ok: true,
endpoint: "https://manage.example.com/api/digital-employee/notifications/actions",
submitted_at: "2026-06-07T08:00:00.000Z",
});
expect(mockState.fetch).toHaveBeenCalledWith(
"https://manage.example.com/api/digital-employee/notifications/actions",
expect.objectContaining({
method: "POST",
body: expect.stringContaining("task:need-input:1"),
}),
);
expect(lastSyncRequestBody()).toMatchObject({
notification_id: "task:need-input:1",
action: "reply",
status: "read",
reply: "已补充客户编号",
submitted_at: "2026-06-07T08:00:00.000Z",
device_id: "device-001",
});
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "digital_workday_notification_action_submitted" }),
]));
});
it("records notification action submit failures without throwing", async () => {
mockState.fetch.mockResolvedValue({
ok: false,
status: 500,
text: vi.fn().mockResolvedValue(JSON.stringify({ message: "notification action rejected" })),
} as unknown as Response);
const { submitDigitalEmployeeNotificationAction } = await import("./syncService");
const result = await submitDigitalEmployeeNotificationAction({ notification_id: "task:need-input:1", action: "read" });
expect(result).toMatchObject({
ok: false,
endpoint: "https://manage.example.com/api/digital-employee/notifications/actions",
submitted_at: "2026-06-07T08:00:00.000Z",
error: "notification action rejected",
});
expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([
expect.objectContaining({ kind: "digital_workday_notification_action_submit_failed" }),
]));
});
it.each([
["skillPolicies"],
["policies"],