W1-W3 实现企业微信个人应用消息通知

这个提交包含在:
2026-05-11 09:17:00 +08:00
父节点 805e050ba8
当前提交 b1ae41fa99
共修改 12 个文件,包含 869 行新增和 111 行删除
@@ -262,6 +262,9 @@ function createPrismaState({ channels = [], deliveries = [], notifications = []
return { id: `audit-${auditEntries.length}` };
}),
},
wecomIdentityBinding: {
findMany: vi.fn().mockResolvedValue([]),
},
$transaction: vi.fn().mockImplementation(async (callback) => callback(transactionClient)),
},
stateChannels,
@@ -423,6 +426,48 @@ describe('notifications channel routes', () => {
await app.close();
});
it('creates wecom app message channel without webhook or secret', async () => {
const { prisma, stateChannels } = createPrismaState();
const app = await buildApp({
prisma,
config: { fieldEncryptionKey: validFieldEncryptionKey },
});
const response = await app.inject({
method: 'POST',
url: '/api/notifications/channels',
headers: { authorization: 'Bearer test-token' },
payload: {
channelType: 'wecom',
name: '企微个人应用消息',
enabled: true,
configJson: {
mode: 'wecom_app_message',
timeoutMs: 800,
routing: {
roleCodes: ['manager'],
domainScopes: ['workflow'],
allowedTypes: ['workflow.task_assigned'],
},
},
},
});
expect(response.statusCode).toBe(200);
expect(response.json().data.channel.configJson).toEqual(
expect.objectContaining({
mode: 'wecom_app_message',
webhookUrl: null,
secretConfigured: false,
secretMasked: null,
}),
);
expect(stateChannels[0].config_json.secretEncrypted).toBeUndefined();
expect(stateChannels[0].config_json.webhookUrl).toBeNull();
await app.close();
});
it('rejects secret write when FIELD_ENCRYPTION_KEY is missing', async () => {
const { prisma } = createPrismaState();
const app = await buildApp({ prisma, config: { fieldEncryptionKey: '' } });
@@ -585,6 +630,44 @@ describe('notifications channel routes', () => {
await app.close();
});
it('tests wecom app message channel against current bound admin when available', async () => {
const { prisma } = createPrismaState({
channels: [
buildChannelRecord({
config_json: {
mode: 'wecom_app_message',
webhookUrl: null,
timeoutMs: 800,
routing: {},
},
}),
],
});
const app = await buildApp({ prisma });
const response = await app.inject({
method: 'POST',
url: '/api/notifications/channels/11111111-1111-4111-8111-111111111111/test',
headers: { authorization: 'Bearer test-token' },
payload: {},
});
expect(response.statusCode).toBe(200);
expect(response.json().data.testResult).toEqual(
expect.objectContaining({
ok: true,
code: 'wecom_app_no_bound_recipients',
}),
);
expect(prisma.wecomIdentityBinding.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: { employee_id: { in: ['employee-1'] } },
}),
);
await app.close();
});
it('lists grouped delivery summaries with latest status and attempt count', async () => {
const notification = buildNotificationRecord({
id: '55555555-5555-4555-8555-555555555555',