WARCH1 实现企微审批最小历史留存

这个提交包含在:
2026-05-20 00:22:27 +08:00
父节点 56a8e2bb0c
当前提交 5cd960cf90
共修改 28 个文件,包含 3081 行新增和 261 行删除
@@ -10,7 +10,7 @@ const TOKEN = "callback-token";
const AES_KEY = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG";
const CORP_ID = "ww-callback-corp";
async function buildApp({ config = {}, evidenceStore } = {}) {
async function buildApp({ config = {}, evidenceStore, prisma } = {}) {
const app = Fastify();
app.decorate("config", {
wecomCorpId: CORP_ID,
@@ -18,6 +18,9 @@ async function buildApp({ config = {}, evidenceStore } = {}) {
wecomCallbackEncodingAesKey: AES_KEY,
...config,
});
if (prisma) {
app.decorate("prisma", prisma);
}
await app.register(wecomApprovalCallbackRoutes, { prefix: "/api", evidenceStore });
return app;
}
@@ -106,6 +109,61 @@ describe("wecomApprovalCallback routes", () => {
await app.close();
});
it("uses the default DB evidence store when prisma is decorated", async () => {
const prisma = buildArchiveMockPrisma();
const app = await buildApp({ prisma });
const sample = buildEncryptedApprovalEventSample({
token: TOKEN,
encodingAesKey: AES_KEY,
receiveId: CORP_ID,
timestamp: "1770000000",
nonce: "nonce",
spNo: "SPFULLIDENTIFIERSHOULDNOTLEAK",
templateId: "TPLFULLIDENTIFIERSHOULDNOTLEAK",
});
const response = await app.inject({
method: "POST",
url: `/api/wecom/approval/callback?${urlQuery(sample)}`,
headers: { "content-type": "text/xml" },
payload: sample.encryptedXml,
});
expect(response.statusCode).toBe(200);
expect(prisma.wecomApprovalCallbackEvent.upsert).toHaveBeenCalledTimes(1);
expect(prisma.wecomApprovalArchive.upsert.mock.calls[0][0].create.sp_no).toBe("SPFULLIDENTIFIERSHOULDNOTLEAK");
await app.close();
});
it("returns non-200 when default DB evidence persistence fails so WeCom can retry", async () => {
const prisma = {
$transaction: vi.fn(async () => {
throw new Error("db unavailable");
}),
};
const app = await buildApp({ prisma });
const sample = buildEncryptedApprovalEventSample({
token: TOKEN,
encodingAesKey: AES_KEY,
receiveId: CORP_ID,
timestamp: "1770000000",
nonce: "nonce",
spNo: "SPFULLIDENTIFIERSHOULDNOTLEAK",
templateId: "TPLFULLIDENTIFIERSHOULDNOTLEAK",
});
const response = await app.inject({
method: "POST",
url: `/api/wecom/approval/callback?${urlQuery(sample)}`,
headers: { "content-type": "text/xml" },
payload: sample.encryptedXml,
});
expect(response.statusCode).toBe(500);
expect(response.body).toBe("callback error");
await app.close();
});
it("reports missing callback config without treating it as pass", async () => {
const app = await buildApp({ config: { wecomCallbackToken: "" } });
const response = await app.inject({
@@ -118,3 +176,31 @@ describe("wecomApprovalCallback routes", () => {
await app.close();
});
});
function buildArchiveMockPrisma() {
const prisma = {
wecomIdentityBinding: {
findUnique: vi.fn(async () => null),
},
wecomApprovalArchive: {
findUnique: vi.fn(async () => null),
upsert: vi.fn(async (args) => ({ id: "archive-1", ...args.create })),
},
wecomApprovalFieldSummary: {
deleteMany: vi.fn(async () => ({ count: 0 })),
createMany: vi.fn(async () => ({ count: 0 })),
},
wecomApprovalParticipant: {
deleteMany: vi.fn(async () => ({ count: 0 })),
createMany: vi.fn(async () => ({ count: 0 })),
},
wecomApprovalAction: {
upsert: vi.fn(async () => ({ id: "action-1" })),
},
wecomApprovalCallbackEvent: {
upsert: vi.fn(async () => ({ id: "event-1", retry_count: 0 })),
},
};
prisma.$transaction = vi.fn(async (callback) => callback(prisma));
return prisma;
}