完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)
汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
这个提交包含在:
@@ -891,3 +891,56 @@ describe('notification inbox routes', () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('notification business error reasons', () => {
|
||||
it.each([
|
||||
['GET', '/notifications?page=0', undefined, 'notification.list_query_invalid', 'page'],
|
||||
['GET', '/notifications/deliveries?limit=0', undefined, 'notification.delivery_query_invalid', 'limit'],
|
||||
['PATCH', '/notifications/channels/not-an-id', { name: '合成渠道' }, 'notification.id_invalid', null],
|
||||
['POST', '/notifications/channels', { channelType: 'wecom', name: '' }, 'notification.channel_input_invalid', 'name'],
|
||||
])('keeps %s %s errors precise and side-effect-free', async (method, url, payload, reasonCode, field) => {
|
||||
const { prisma, auditEntries, stateChannels } = createPrismaState();
|
||||
const app = await buildApp({ prisma });
|
||||
try {
|
||||
const response = await app.inject({ method, url: `/api${url}`, headers: { authorization: 'Bearer synthetic' }, ...(payload ? { payload } : {}) });
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error.code).toBe('VALID_001');
|
||||
expect(response.json().error.details.reasonCode).toBe(reasonCode);
|
||||
if (field) expect(response.json().error.details.fieldErrors[field].length).toBeGreaterThan(0);
|
||||
else expect(response.json().error.details.id).toBe('invalid_uuid');
|
||||
expect(prisma.notificationChannel.create).not.toHaveBeenCalled();
|
||||
expect(prisma.notificationChannel.update).not.toHaveBeenCalled();
|
||||
expect(prisma.notification.updateMany).not.toHaveBeenCalled();
|
||||
expect(prisma.$transaction).not.toHaveBeenCalled();
|
||||
expect(prisma.notificationDelivery.findMany).not.toHaveBeenCalled();
|
||||
expect(stateChannels).toEqual([]);
|
||||
expect(auditEntries).toEqual([]);
|
||||
} finally { await app.close(); }
|
||||
});
|
||||
it('keeps duplicate channel conflict and has no successful audit', async () => {
|
||||
const channel = buildChannelRecord({ name: '合成重复渠道' });
|
||||
const { prisma, stateChannels, auditEntries } = createPrismaState({ channels: [channel] });
|
||||
const app = await buildApp({ prisma });
|
||||
try {
|
||||
const response = await app.inject({ method: 'POST', url: '/api/notifications/channels', headers: { authorization: 'Bearer synthetic' }, payload: { channelType: 'wecom', name: channel.name, configJson: { mode: 'mock' } } });
|
||||
expect(response.statusCode).toBe(409);
|
||||
expect(response.json().error.details).toEqual({ reasonCode: 'notification.channel_name_duplicate', name: 'duplicate_channel_name' });
|
||||
expect(stateChannels).toHaveLength(1);
|
||||
expect(auditEntries).toEqual([]);
|
||||
} finally { await app.close(); }
|
||||
});
|
||||
it('reports missing encryption capability without exposing the submitted synthetic secret', async () => {
|
||||
const { prisma, auditEntries } = createPrismaState();
|
||||
const app = await buildApp({ prisma });
|
||||
const secret = 'synthetic-do-not-echo';
|
||||
try {
|
||||
const response = await app.inject({ method: 'POST', url: '/api/notifications/channels', headers: { authorization: 'Bearer synthetic' }, payload: { channelType: 'wecom', name: '合成渠道', configJson: { mode: 'wecom_webhook', webhookUrl: 'https://example.invalid/synthetic', secret } } });
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json().error.details).toEqual({ reasonCode: 'notification.encryption_key_unavailable', secret: 'field_encryption_key_missing' });
|
||||
expect(response.body.includes(secret)).toBe(false);
|
||||
expect(prisma.notificationChannel.create).not.toHaveBeenCalled();
|
||||
expect(auditEntries).toEqual([]);
|
||||
} finally { await app.close(); }
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户