171 行
5.1 KiB
JavaScript
171 行
5.1 KiB
JavaScript
const {
|
|
buildDetailUrl,
|
|
buildImDedupeKey,
|
|
enqueueImNotifications,
|
|
resolveRecipientRoleCodes,
|
|
} = require("../../../src/services/notifications/imEnqueue");
|
|
|
|
describe("IM enqueue", () => {
|
|
it("builds detail url from APP_ORIGIN", () => {
|
|
expect(buildDetailUrl({ appOrigin: "https://app.example.com" }, "/workflow/tasks/1")).toBe(
|
|
"https://app.example.com/workflow/tasks/1",
|
|
);
|
|
expect(buildDetailUrl({ appOrigin: "" }, "/workflow/tasks/1")).toBeNull();
|
|
});
|
|
|
|
it("builds dedupe key with target id first", () => {
|
|
expect(
|
|
buildImDedupeKey({
|
|
notificationType: "workflow.task_assigned",
|
|
targetType: "workflow_task",
|
|
targetId: "task-1",
|
|
targetPath: "/workflow/tasks/task-1",
|
|
channelId: "channel-1",
|
|
}),
|
|
).toBe("workflow.task_assigned:workflow_task:task-1:channel-1");
|
|
});
|
|
|
|
it("resolves unique recipient role codes", async () => {
|
|
const roles = await resolveRecipientRoleCodes(
|
|
{
|
|
employeeRole: {
|
|
findMany: vi.fn().mockResolvedValue([
|
|
{ role_code: "manager" },
|
|
{ role_code: "manager" },
|
|
{ role_code: "finance_mgr" },
|
|
]),
|
|
},
|
|
},
|
|
["employee-1", "employee-2"],
|
|
);
|
|
|
|
expect(roles).toEqual(["manager", "finance_mgr"]);
|
|
});
|
|
|
|
it("enqueues one outbox event per matched channel", async () => {
|
|
const tx = {
|
|
employeeRole: {
|
|
findMany: vi.fn().mockResolvedValue([{ role_code: "manager" }]),
|
|
},
|
|
notificationChannel: {
|
|
findMany: vi.fn().mockResolvedValue([
|
|
{
|
|
id: "channel-1",
|
|
channel_type: "wecom",
|
|
name: "manager-workflow",
|
|
enabled: true,
|
|
config_json: {
|
|
routing: {
|
|
roleCodes: ["manager"],
|
|
domainScopes: ["workflow"],
|
|
allowedTypes: ["workflow.task_assigned"],
|
|
},
|
|
},
|
|
template_json: null,
|
|
},
|
|
{
|
|
id: "channel-2",
|
|
channel_type: "wechat_work",
|
|
name: "all-workflow",
|
|
enabled: true,
|
|
config_json: {
|
|
routing: {
|
|
roleCodes: [],
|
|
domainScopes: ["workflow"],
|
|
allowedTypes: [],
|
|
},
|
|
},
|
|
template_json: null,
|
|
},
|
|
]),
|
|
},
|
|
integrationOutbox: {
|
|
findUnique: vi
|
|
.fn()
|
|
.mockResolvedValueOnce(null)
|
|
.mockResolvedValueOnce({ id: "outbox-2", status: "pending" }),
|
|
create: vi.fn().mockImplementation(async ({ data }) => ({ id: `${data.dedupe_key}-id`, status: "pending" })),
|
|
},
|
|
};
|
|
|
|
const result = await enqueueImNotifications(tx, {
|
|
config: {
|
|
imNotifierEnabled: true,
|
|
integrationOutboxMaxAttempts: 5,
|
|
appOrigin: "https://app.example.com",
|
|
},
|
|
logger: { warn: vi.fn() },
|
|
recipientEmployeeIds: ["employee-1"],
|
|
type: "workflow.task_assigned",
|
|
title: "【待办】采购申请待审批",
|
|
body: "采购申请已流转到你,请及时处理。",
|
|
targetType: "workflow_task",
|
|
targetId: "task-1",
|
|
targetPath: "/workflow/tasks/task-1",
|
|
entityType: "purchase_request",
|
|
entityId: "pr-1",
|
|
actorId: "actor-1",
|
|
});
|
|
|
|
expect(result.queued).toBe(1);
|
|
expect(result.duplicated).toBe(1);
|
|
expect(result.matchedChannels).toEqual(["channel-1", "channel-2"]);
|
|
expect(tx.integrationOutbox.create).toHaveBeenCalledTimes(1);
|
|
expect(tx.integrationOutbox.create.mock.calls[0][0].data.payload.detailUrl).toBe(
|
|
"https://app.example.com/workflow/tasks/task-1",
|
|
);
|
|
});
|
|
|
|
it("safely skips when no channel matches", async () => {
|
|
const tx = {
|
|
employeeRole: {
|
|
findMany: vi.fn().mockResolvedValue([{ role_code: "manager" }]),
|
|
},
|
|
notificationChannel: {
|
|
findMany: vi.fn().mockResolvedValue([
|
|
{
|
|
id: "channel-3",
|
|
channel_type: "wecom",
|
|
name: "finance-only",
|
|
enabled: true,
|
|
config_json: {
|
|
routing: {
|
|
roleCodes: ["finance_mgr"],
|
|
domainScopes: ["procurement"],
|
|
allowedTypes: ["purchase_request.approved"],
|
|
},
|
|
},
|
|
template_json: null,
|
|
},
|
|
]),
|
|
},
|
|
integrationOutbox: {
|
|
findUnique: vi.fn(),
|
|
create: vi.fn(),
|
|
},
|
|
};
|
|
|
|
const result = await enqueueImNotifications(tx, {
|
|
config: {
|
|
imNotifierEnabled: true,
|
|
integrationOutboxMaxAttempts: 5,
|
|
appOrigin: "https://app.example.com",
|
|
},
|
|
logger: { warn: vi.fn() },
|
|
recipientEmployeeIds: ["employee-1"],
|
|
type: "workflow.task_assigned",
|
|
title: "title",
|
|
body: "body",
|
|
targetType: "workflow_task",
|
|
targetId: "task-1",
|
|
targetPath: "/workflow/tasks/task-1",
|
|
entityType: "purchase_request",
|
|
entityId: "pr-1",
|
|
actorId: "actor-1",
|
|
});
|
|
|
|
expect(result.skippedReason).toBe("no_matching_channel");
|
|
expect(tx.integrationOutbox.create).not.toHaveBeenCalled();
|
|
});
|
|
});
|