const { assertSendAllowed, buildApprovalConfigChecks, buildEntryOauthCheck, 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, wecomPcWebLoginEnabled: false, 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("reports the independently gated PC Web login entry without blocking workbench readiness", () => { const disabled = buildEntryOauthCheck(baseConfig()); expect(disabled.status).toBe("OK"); expect(disabled.evidence).toEqual(expect.objectContaining({ enabled: true, pcWebLoginEnabled: false, pcLoginUrl: null, })); const enabled = buildEntryOauthCheck(baseConfig({ wecomPcWebLoginEnabled: true })); expect(enabled.status).toBe("OK"); expect(enabled.evidence.pcLoginUrl).toBe( "/api/auth/wecom/login?returnTo=%2Fportal&entrySource=pc_qr", ); }); 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("uses QA-only checkin read mode when sync switch is disabled", async () => { const fetchCheckinImpl = vi.fn(async ({ requireSyncEnabled }) => { expect(requireSyncEnabled).toBe(false); return { ok: true, code: "wecom_checkin_fetched", records: [], response: { checkindataCount: 0 }, }; }); const report = await executeRuntimeReady(baseOptions(), { config: baseConfig({ wecomCheckinSyncEnabled: false }), env: {}, oaRows: [boundEmployee()], fetchProfilesImpl: vi.fn(async () => ({ tokenSource: { contact: "WECOM_CONTACT_SECRET", hr: "WECOM_AGENT_SECRET" }, profiles: [wecomProfile()], })), fetchCheckinImpl, generatedAt: "2026-05-19T01:00:00.000Z", }); expect(report.status).toBe("PASS_WITH_CONFIG_GAPS"); expect(report.checks.secretMatrix.matrix.checkin).toMatchObject({ syncEnabled: false, runtimeReadyMode: "qa_read_only_sync_disabled", }); expect(report.checks.checkinRead).toMatchObject({ status: "OK", syncEnabled: false, runtimeReadyMode: "qa_read_only_sync_disabled", readiness: "readable_sync_disabled", }); }); it("keeps real WeCom checkin API failures blocking in QA-only mode", async () => { const report = await executeRuntimeReady(baseOptions(), { config: baseConfig({ wecomCheckinSyncEnabled: false }), env: {}, oaRows: [boundEmployee()], fetchProfilesImpl: vi.fn(async () => ({ tokenSource: { contact: "WECOM_CONTACT_SECRET", hr: "WECOM_AGENT_SECRET" }, profiles: [wecomProfile()], })), fetchCheckinImpl: vi.fn(async ({ requireSyncEnabled }) => { expect(requireSyncEnabled).toBe(false); return { ok: false, code: "wecom_checkin_api_error", records: [], response: { errcode: 301062, errmsg: "permission denied" }, message: "WeCom checkin errcode 301062", }; }), generatedAt: "2026-05-19T01:00:00.000Z", }); expect(report.status).toBe("SKIP"); expect(report.conclusion).toBe("BLOCKED_BY_EXTERNAL_CONFIG"); expect(report.mainBlockers).toEqual(expect.arrayContaining([ expect.objectContaining({ id: "checkin_short_window_read", reason: "wecom_checkin_api_error", }), ])); expect(report.checks.checkinRead).toMatchObject({ status: "BLOCKED", readiness: "blocked", }); }); 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_APPROVAL_CALLBACK_URL: "https://oa.hyinda.com/api/wecom/approval/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_APPROVAL_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_APPROVAL_CALLBACK_URL: "https://oa.hyinda.com/api/wecom/approval/callback", WECOM_CALLBACK_TOKEN: SENSITIVE_CALLBACK_TOKEN, WECOM_CALLBACK_ENCODING_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", }); }); });