WCFB 复用工作台企微打卡同步密钥

这个提交包含在:
2026-05-17 23:07:34 +08:00
父节点 1ccd675714
当前提交 bd601a6987
共修改 12 个文件,包含 158 行新增和 15 行删除
@@ -40,6 +40,13 @@ function sanitizeCheckinApiResponse(payload) {
return Object.keys(result).length ? result : null;
}
function resolveCheckinSecret(config) {
return (
toSafeString(config?.wecomCheckinSecret, 500) ||
toSafeString(config?.wecomAgentSecret, 500)
);
}
function normalizeCheckinRecord(raw, { employeeByExternalUserId, timeZone }) {
const externalUserId = toSafeString(raw?.userid ?? raw?.UserId, 128);
const employee = employeeByExternalUserId.get(externalUserId);
@@ -82,13 +89,14 @@ async function fetchWecomCheckinData({
response: null,
};
}
if (!toSafeString(config?.wecomCheckinSecret, 500)) {
const checkinSecret = resolveCheckinSecret(config);
if (!checkinSecret) {
return {
ok: false,
skipped: true,
retryable: false,
code: "wecom_checkin_secret_missing",
message: "Missing WECOM_CHECKIN_SECRET",
message: "Missing WECOM_CHECKIN_SECRET or WECOM_AGENT_SECRET",
records: [],
response: null,
};
@@ -108,7 +116,7 @@ async function fetchWecomCheckinData({
const tokenResult = await getWecomAppAccessToken({
config: {
...config,
wecomAgentSecret: config.wecomCheckinSecret,
wecomAgentSecret: checkinSecret,
},
timeoutMs: config.wecomRequestTimeoutMs ?? 5000,
fetchImpl,
@@ -177,5 +185,6 @@ module.exports = {
fetchWecomCheckinData,
formatDateInTimezone,
normalizeCheckinRecord,
resolveCheckinSecret,
sanitizeCheckinApiResponse,
};
+122
查看文件
@@ -6,6 +6,7 @@ const { AppError } = require("../../../src/utils/AppError");
const EMPLOYEE_ID = "11111111-1111-4111-8111-111111111111";
const MANAGER_ID = "22222222-2222-4222-8222-222222222222";
const DEPARTMENT_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
const originalFetch = global.fetch;
function buildEmployee(overrides = {}) {
return {
@@ -36,6 +37,14 @@ function buildFlag(flagKey, overrideMode = "force_on") {
};
}
function buildJsonResponse(payload, overrides = {}) {
return {
ok: overrides.ok ?? true,
status: overrides.status ?? 200,
json: async () => payload,
};
}
function buildApp(options = {}) {
const app = Fastify();
app.setErrorHandler((error, request, reply) => {
@@ -91,6 +100,7 @@ function buildApp(options = {}) {
wecomCheckinSyncEnabled: false,
wecomCheckinSecret: "",
leaveHolidayCalendarEnabled: false,
...options.config,
});
app.decorate("prisma", {
featureFlag: { findUnique: featureFlagFindUnique },
@@ -179,6 +189,7 @@ describe("attendance unified routes", () => {
let currentApp = null;
afterEach(async () => {
global.fetch = originalFetch;
if (currentApp) {
await currentApp.close();
currentApp = null;
@@ -320,4 +331,115 @@ describe("attendance unified routes", () => {
}),
);
});
it("records WeCom sync as skipped when no authorized app secret is configured", async () => {
const { app, prisma } = buildApp({
config: {
wecomCheckinSyncEnabled: true,
wecomCorpId: "corp-id",
wecomAgentId: "1000001",
wecomCheckinSecret: "",
wecomAgentSecret: "",
},
});
currentApp = app;
const response = await app.inject({
method: "POST",
url: "/api/attendance/wecom/sync",
payload: {
startDate: "2026-05-11",
endDate: "2026-05-12",
},
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toMatchObject({
skipped: true,
code: "wecom_checkin_secret_missing",
employeeCount: 1,
boundUserCount: 1,
recordCount: 0,
});
expect(prisma.wecomCheckinSyncRun.update).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
status: "skipped",
error_code: "wecom_checkin_secret_missing",
}),
}),
);
});
it("uses the authorized workbench app secret fallback for WeCom checkin sync", async () => {
global.fetch = vi
.fn()
.mockResolvedValueOnce(buildJsonResponse({ errcode: 0, access_token: "token-from-workbench" }))
.mockResolvedValueOnce(
buildJsonResponse({
errcode: 0,
checkindata: [
{
userid: "wecom-user-1",
checkin_time: 1778461200,
checkin_type: "上班打卡",
exception_type: "",
source_type: "app",
result: "正常",
},
],
}),
);
const { app, prisma } = buildApp({
config: {
wecomCheckinSyncEnabled: true,
wecomCorpId: "corp-id",
wecomAgentId: "1000001",
wecomCheckinSecret: "",
wecomAgentSecret: "workbench-secret",
},
});
currentApp = app;
const response = await app.inject({
method: "POST",
url: "/api/attendance/wecom/sync",
payload: {
startDate: "2026-05-11",
endDate: "2026-05-12",
},
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toMatchObject({
skipped: false,
code: "wecom_checkin_fetched",
employeeCount: 1,
boundUserCount: 1,
recordCount: 1,
});
expect(String(global.fetch.mock.calls[0][0])).toContain("corpsecret=workbench-secret");
expect(String(global.fetch.mock.calls[1][0])).toContain("/cgi-bin/checkin/getcheckindata");
expect(prisma.wecomCheckinRecord.createMany).toHaveBeenCalledWith(
expect.objectContaining({
data: [
expect.objectContaining({
employee_id: EMPLOYEE_ID,
external_user_id: "wecom-user-1",
checkin_type: "上班打卡",
result: "正常",
}),
],
}),
);
expect(prisma.wecomCheckinSyncRun.update).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
status: "success",
record_count: 1,
error_code: null,
}),
}),
);
});
});