WRV1 收口企微运行优先门禁
这个提交包含在:
@@ -0,0 +1,298 @@
|
||||
const {
|
||||
assertSendAllowed,
|
||||
buildApprovalConfigChecks,
|
||||
buildSecretMatrix,
|
||||
evaluateConclusion,
|
||||
executeRuntimeReady,
|
||||
parseArgs,
|
||||
resolveCheckinSecretSource,
|
||||
} = require("../../../src/scripts/check-wecom-runtime-ready");
|
||||
|
||||
const SENSITIVE_AGENT_CREDENTIAL = ["agent", "credential", "should", "never", "leak"].join("-");
|
||||
const SENSITIVE_CONTACT_CREDENTIAL = ["contact", "credential", "should", "never", "leak"].join("-");
|
||||
const SENSITIVE_LEGACY_HR_CREDENTIAL = ["legacy", "hr", "credential", "should", "not", "be", "used"].join("-");
|
||||
const SENSITIVE_CHECKIN_CREDENTIAL = ["checkin", "credential", "override"].join("-");
|
||||
const SENSITIVE_CALLBACK_TOKEN = ["callback", "credential", "token"].join("-");
|
||||
const SENSITIVE_CALLBACK_AES_KEY = ["callback", "credential", "aes"].join("-");
|
||||
const SENSITIVE_USER_ID = ["oa", "smoke002", "full", "userid", "should", "not", "leak"].join("_");
|
||||
const SENSITIVE_PHONE = ["138", "0000", "0001"].join("");
|
||||
const SENSITIVE_EMAIL = ["employee", "example.com"].join("@");
|
||||
|
||||
function baseConfig(overrides = {}) {
|
||||
return {
|
||||
appOrigin: "https://oa.hyinda.com",
|
||||
appTimezone: "Asia/Shanghai",
|
||||
wecomEnabled: true,
|
||||
wecomEntryEnabled: true,
|
||||
wecomOauthEnabled: true,
|
||||
wecomCorpId: "corp-runtime",
|
||||
wecomAgentId: "1000002",
|
||||
wecomAgentSecret: SENSITIVE_AGENT_CREDENTIAL,
|
||||
wecomContactSecret: SENSITIVE_CONTACT_CREDENTIAL,
|
||||
wecomCheckinSecret: "",
|
||||
wecomCheckinSyncEnabled: true,
|
||||
wecomRedirectUri: "https://oa.hyinda.com/api/auth/wecom/callback",
|
||||
wecomAllowedReturnHosts: "oa.hyinda.com",
|
||||
wecomRequestTimeoutMs: 1000,
|
||||
wecomHrAssistantSecret: SENSITIVE_LEGACY_HR_CREDENTIAL,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function baseOptions(overrides = {}) {
|
||||
return {
|
||||
live: true,
|
||||
format: "json",
|
||||
limit: 2,
|
||||
reportDir: "/tmp/wecom-runtime-ready-test",
|
||||
sendTestMessage: false,
|
||||
messageRecipientEmployeeNo: "",
|
||||
help: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function boundEmployee(overrides = {}) {
|
||||
return {
|
||||
id: "employee-1",
|
||||
employeeNo: "SMOKE002",
|
||||
status: "active",
|
||||
archived: false,
|
||||
wecomExternalUserId: SENSITIVE_USER_ID,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function wecomProfile(overrides = {}) {
|
||||
return {
|
||||
externalUserId: SENSITIVE_USER_ID,
|
||||
sourceStatus: "active",
|
||||
missingRequiredFields: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("check-wecom-runtime-ready", () => {
|
||||
it("parses runner options and enforces message send double confirmation", () => {
|
||||
const options = parseArgs([
|
||||
"--live",
|
||||
"--format=json",
|
||||
"--limit=500",
|
||||
"--send-test-message",
|
||||
"--message-recipient-employee-no=SMOKE002",
|
||||
]);
|
||||
|
||||
expect(options.live).toBe(true);
|
||||
expect(options.format).toBe("json");
|
||||
expect(options.limit).toBe(200);
|
||||
expect(options.sendTestMessage).toBe(true);
|
||||
expect(options.messageRecipientEmployeeNo).toBe("SMOKE002");
|
||||
expect(() => assertSendAllowed(options, {})).toThrow(/WECOM_RUNTIME_TEST_SEND=1/);
|
||||
expect(() => assertSendAllowed(options, { WECOM_RUNTIME_TEST_SEND: "1" })).not.toThrow();
|
||||
});
|
||||
|
||||
it("documents fixed Secret ownership without HR assistant fallback", () => {
|
||||
const matrix = buildSecretMatrix(baseConfig({
|
||||
wecomCheckinSecret: SENSITIVE_CHECKIN_CREDENTIAL,
|
||||
}));
|
||||
|
||||
expect(matrix.contact).toMatchObject({
|
||||
source: "WECOM_CONTACT_SECRET",
|
||||
endpoint: "POST /cgi-bin/user/list_id",
|
||||
configured: true,
|
||||
});
|
||||
expect(matrix.hrAssistant).toMatchObject({
|
||||
source: "WECOM_AGENT_SECRET",
|
||||
endpoint: "POST /cgi-bin/hr/get_staff_info",
|
||||
configured: true,
|
||||
legacyNotDefault: "WECOM_HR_ASSISTANT_SECRET",
|
||||
});
|
||||
expect(matrix.checkin).toMatchObject({
|
||||
source: "WECOM_CHECKIN_SECRET",
|
||||
override: true,
|
||||
});
|
||||
expect(resolveCheckinSecretSource(baseConfig({ wecomCheckinSecret: "" }))).toBe("WECOM_AGENT_SECRET");
|
||||
});
|
||||
|
||||
it("returns PASS_WITH_CONFIG_GAPS when runtime chain works but approval/callback config is missing", async () => {
|
||||
const fetchProfilesImpl = vi.fn(async ({ config, userids, limit }) => {
|
||||
expect(userids).toEqual([]);
|
||||
expect(limit).toBe(2);
|
||||
expect(config.wecomContactSecret).toBe(SENSITIVE_CONTACT_CREDENTIAL);
|
||||
expect(config.wecomAgentSecret).toBe(SENSITIVE_AGENT_CREDENTIAL);
|
||||
expect(config.wecomHrAssistantSecret).toBe(SENSITIVE_LEGACY_HR_CREDENTIAL);
|
||||
return {
|
||||
tokenSource: {
|
||||
contact: "WECOM_CONTACT_SECRET",
|
||||
hr: "WECOM_AGENT_SECRET",
|
||||
},
|
||||
profiles: [wecomProfile()],
|
||||
};
|
||||
});
|
||||
const fetchCheckinImpl = vi.fn(async ({ config, externalUserIds }) => {
|
||||
expect(resolveCheckinSecretSource(config)).toBe("WECOM_AGENT_SECRET");
|
||||
expect(externalUserIds).toEqual([SENSITIVE_USER_ID]);
|
||||
return {
|
||||
ok: true,
|
||||
code: "wecom_checkin_fetched",
|
||||
records: [],
|
||||
response: { checkindataCount: 0 },
|
||||
};
|
||||
});
|
||||
const sendMessageImpl = vi.fn();
|
||||
|
||||
const report = await executeRuntimeReady(baseOptions(), {
|
||||
config: baseConfig(),
|
||||
env: {},
|
||||
oaRows: [boundEmployee()],
|
||||
fetchProfilesImpl,
|
||||
fetchCheckinImpl,
|
||||
sendMessageImpl,
|
||||
generatedAt: "2026-05-19T01:00:00.000Z",
|
||||
now: new Date("2026-05-19T01:00:00.000Z"),
|
||||
});
|
||||
|
||||
expect(report.status).toBe("PASS_WITH_CONFIG_GAPS");
|
||||
expect(report.conclusion).toBe("PASS_WITH_CONFIG_GAPS");
|
||||
expect(report.checks.contactAndHrRead.status).toBe("OK");
|
||||
expect(report.checks.contactAndHrRead.contact.endpoint).toBe("POST /cgi-bin/user/list_id");
|
||||
expect(report.checks.contactAndHrRead.hrAssistant.endpoint).toBe("POST /cgi-bin/hr/get_staff_info");
|
||||
expect(report.checks.contactAndHrRead.tokenSource).toEqual({
|
||||
contact: "WECOM_CONTACT_SECRET",
|
||||
hr: "WECOM_AGENT_SECRET",
|
||||
});
|
||||
expect(report.checks.checkinRead.status).toBe("OK");
|
||||
expect(report.checks.message.status).toBe("DRY_RUN_READY");
|
||||
expect(sendMessageImpl).not.toHaveBeenCalled();
|
||||
expect(report.configGaps.map((item) => item.id)).toEqual([
|
||||
"approval_template_id",
|
||||
"approval_test_sp_no",
|
||||
"approval_callback_url",
|
||||
"callback_token",
|
||||
"callback_encoding_aes_key",
|
||||
]);
|
||||
});
|
||||
|
||||
it("blocks instead of falling back when contact Secret is missing", async () => {
|
||||
const report = await executeRuntimeReady(baseOptions(), {
|
||||
config: baseConfig({ wecomContactSecret: "" }),
|
||||
env: {},
|
||||
oaRows: [boundEmployee()],
|
||||
fetchProfilesImpl: vi.fn(),
|
||||
fetchCheckinImpl: vi.fn(async () => ({ ok: true, code: "wecom_checkin_fetched", records: [], response: {} })),
|
||||
generatedAt: "2026-05-19T01:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(report.status).toBe("SKIP");
|
||||
expect(report.conclusion).toBe("BLOCKED_BY_EXTERNAL_CONFIG");
|
||||
expect(report.mainBlockers.some((item) => item.id === "secret_matrix")).toBe(true);
|
||||
expect(report.checks.contactAndHrRead).toMatchObject({
|
||||
status: "BLOCKED",
|
||||
missing: ["WECOM_CONTACT_SECRET"],
|
||||
});
|
||||
});
|
||||
|
||||
it("classifies missing approval and callback values as NEEDS_CONFIG", () => {
|
||||
const checks = buildApprovalConfigChecks({
|
||||
WECOM_APPROVAL_TEMPLATE_ID: "approval-template-id",
|
||||
WECOM_CALLBACK_URL: "https://oa.hyinda.com/api/wecom/callback",
|
||||
});
|
||||
|
||||
expect(checks.find((item) => item.id === "approval_template_id")).toMatchObject({
|
||||
status: "CONFIGURED",
|
||||
configuredEnv: "WECOM_APPROVAL_TEMPLATE_ID",
|
||||
});
|
||||
expect(checks.find((item) => item.id === "approval_callback_url")).toMatchObject({
|
||||
status: "CONFIGURED",
|
||||
configuredEnv: "WECOM_CALLBACK_URL",
|
||||
valuePreview: "https://oa.hyinda.com/...",
|
||||
});
|
||||
expect(checks.find((item) => item.id === "approval_test_sp_no")).toMatchObject({
|
||||
status: "NEEDS_CONFIG",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends a test message only after double confirmation", async () => {
|
||||
const sendMessageImpl = vi.fn(async () => ({
|
||||
ok: true,
|
||||
code: "wecom_app_sent",
|
||||
retryable: false,
|
||||
response: { errcode: 0, recipientCount: 1, invaliduser: SENSITIVE_USER_ID },
|
||||
message: null,
|
||||
}));
|
||||
const report = await executeRuntimeReady(baseOptions({
|
||||
sendTestMessage: true,
|
||||
messageRecipientEmployeeNo: "SMOKE002",
|
||||
}), {
|
||||
config: baseConfig(),
|
||||
env: {
|
||||
WECOM_RUNTIME_TEST_SEND: "1",
|
||||
WECOM_APPROVAL_TEMPLATE_ID: "tpl",
|
||||
WECOM_APPROVAL_TEST_SP_NO: "sp",
|
||||
WECOM_CALLBACK_URL: "https://oa.hyinda.com/api/wecom/callback",
|
||||
WECOM_CALLBACK_TOKEN: SENSITIVE_CALLBACK_TOKEN,
|
||||
WECOM_CALLBACK_AES_KEY: SENSITIVE_CALLBACK_AES_KEY,
|
||||
},
|
||||
oaRows: [boundEmployee()],
|
||||
fetchProfilesImpl: vi.fn(async () => ({
|
||||
tokenSource: { contact: "WECOM_CONTACT_SECRET", hr: "WECOM_AGENT_SECRET" },
|
||||
profiles: [wecomProfile()],
|
||||
})),
|
||||
fetchCheckinImpl: vi.fn(async () => ({ ok: true, code: "wecom_checkin_fetched", records: [], response: {} })),
|
||||
sendMessageImpl,
|
||||
generatedAt: "2026-05-19T01:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(report.status).toBe("PASS_RUNTIME_READY");
|
||||
expect(sendMessageImpl).toHaveBeenCalledTimes(1);
|
||||
expect(report.checks.message.status).toBe("OK");
|
||||
expect(report.checks.message.result.response).toMatchObject({
|
||||
errcode: 0,
|
||||
recipientCount: 1,
|
||||
invaliduserPresent: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps report output redacted", async () => {
|
||||
const report = await executeRuntimeReady(baseOptions(), {
|
||||
config: baseConfig(),
|
||||
env: {},
|
||||
oaRows: [boundEmployee()],
|
||||
fetchProfilesImpl: vi.fn(async () => ({
|
||||
tokenSource: { contact: "WECOM_CONTACT_SECRET", hr: "WECOM_AGENT_SECRET" },
|
||||
profiles: [wecomProfile()],
|
||||
})),
|
||||
fetchCheckinImpl: vi.fn(async () => ({
|
||||
ok: true,
|
||||
code: "wecom_checkin_fetched",
|
||||
records: [],
|
||||
response: { checkindataCount: 0 },
|
||||
})),
|
||||
generatedAt: "2026-05-19T01:00:00.000Z",
|
||||
});
|
||||
const serialized = JSON.stringify(report);
|
||||
|
||||
expect(serialized).not.toContain(SENSITIVE_USER_ID);
|
||||
expect(serialized).not.toContain(SENSITIVE_AGENT_CREDENTIAL);
|
||||
expect(serialized).not.toContain(SENSITIVE_CONTACT_CREDENTIAL);
|
||||
expect(serialized).not.toContain(SENSITIVE_LEGACY_HR_CREDENTIAL);
|
||||
expect(serialized).not.toContain(SENSITIVE_PHONE);
|
||||
expect(serialized).not.toContain(SENSITIVE_EMAIL);
|
||||
expect(serialized).toContain("oa_smoke***");
|
||||
});
|
||||
|
||||
it("evaluates final conclusion categories", () => {
|
||||
expect(evaluateConclusion({ mainBlockers: [{ id: "secret_matrix" }], configGaps: [], live: true })).toEqual({
|
||||
status: "SKIP",
|
||||
conclusion: "BLOCKED_BY_EXTERNAL_CONFIG",
|
||||
});
|
||||
expect(evaluateConclusion({ mainBlockers: [], configGaps: [{ id: "approval_template_id" }], live: true })).toEqual({
|
||||
status: "PASS_WITH_CONFIG_GAPS",
|
||||
conclusion: "PASS_WITH_CONFIG_GAPS",
|
||||
});
|
||||
expect(evaluateConclusion({ mainBlockers: [], configGaps: [], live: true })).toEqual({
|
||||
status: "PASS_RUNTIME_READY",
|
||||
conclusion: "PASS_RUNTIME_READY",
|
||||
});
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户