文件
hyb-oa/server/tests/unit/scripts/resolve-wecom-oa-user-consistency.test.js

226 行
8.3 KiB
JavaScript

const {
applyPlanWithClient,
assertApplyAllowed,
buildDryRunResult,
buildFixPlan,
normalizeEmployeeNoList,
parseArgs,
} = require("../../../src/scripts/resolve-wecom-oa-user-consistency");
function auditReport(overrides = {}) {
return {
status: "PASS_WITH_FINDINGS",
generatedAt: "2026-05-18T14:25:44.000Z",
findings: {
oaActiveMissingInWecom: [
{ employeeNo: "TEST001", status: "active", roles: ["employee"] },
{ employeeNo: "TEST002", status: "active", roles: ["employee"] },
],
missingRequiredFields: [
{
employee: { employeeNo: "REAL001", status: "active", roles: ["employee"] },
wecom: { externalUserIdPrefix: "wecom_re***" },
fields: ["email", "phone", "department"],
},
],
inactiveWithBinding: [
{ employeeNo: "HRWDTEST-20260517214548", status: "archived", archived: true, roles: ["employee"] },
],
matchedActive: [],
oaOnlySmokeExempted: [],
wecomActiveMissingInOa: [],
oaActiveMissingBinding: [],
statusMismatch: [],
matchConflict: [],
},
...overrides,
};
}
function employeeRow(overrides = {}) {
return {
id: overrides.id ?? "11111111-1111-4111-8111-111111111111",
employee_no: overrides.employeeNo ?? "TEST001",
status: overrides.status ?? "active",
employment_status: overrides.employmentStatus ?? "active",
archived: overrides.archived ?? false,
token_version: overrides.tokenVersion ?? 1,
roles: overrides.roles ?? ["employee"],
has_wecom_binding: overrides.hasWecomBinding ?? false,
};
}
function buildClient(rows) {
const calls = [];
return {
calls,
async query(sql, params) {
const text = String(sql);
calls.push({ sql: text, params });
if (text.includes("FROM employees e")) {
return { rows };
}
return { rows: [], rowCount: 1 };
},
};
}
function sqlCalls(client, pattern) {
return client.calls.filter((call) => call.sql.includes(pattern));
}
describe("resolve-wecom-oa-user-consistency", () => {
afterEach(() => {
delete process.env.WOUA_FIX_APPLY;
});
it("builds a default plan that archives missing OA test employees and accepts missing WeCom fields", () => {
const plan = buildFixPlan(auditReport());
const dryRun = buildDryRunResult(plan);
const serialized = JSON.stringify(dryRun);
expect(plan.archiveOaTestEmployees.map((item) => item.employeeNo)).toEqual(["TEST001", "TEST002"]);
expect(plan.acceptedMissingRequiredFields).toEqual([
expect.objectContaining({
employeeNo: "REAL001",
action: "accepted_no_write",
fields: ["department", "email", "phone"],
}),
]);
expect(plan.inactiveWithBinding[0]).toEqual(expect.objectContaining({
employeeNo: "HRWDTEST-20260517214548",
action: "accepted_no_write",
}));
expect(dryRun.totals.wecomWrites).toBe(0);
expect(serialized).not.toContain("13800000001");
expect(serialized).not.toContain("[email protected]");
expect(serialized).not.toContain("wecom_user_001_very_long");
});
it("supports allowlist narrowing to avoid touching unlisted employees", () => {
const plan = buildFixPlan(auditReport(), { allowEmployeeNos: ["TEST002"] });
expect(plan.archiveOaTestEmployees.map((item) => item.employeeNo)).toEqual(["TEST002"]);
expect(plan.excludedByAllowlist.map((item) => item.employeeNo)).toEqual(["TEST001"]);
});
it("exempts source-classified OA-only smoke accounts from archive plans", () => {
const plan = buildFixPlan(auditReport({
findings: {
...auditReport().findings,
oaOnlySmokeExempted: [
{ employeeNo: "TEST001", status: "active", roles: ["admin", "employee"] },
],
},
}));
expect(plan.archiveOaTestEmployees.map((item) => item.employeeNo)).toEqual(["TEST002"]);
expect(plan.oaOnlySmokeExempted.map((item) => item.employeeNo)).toEqual(["TEST001"]);
});
it("supports explicit OA-only smoke employee-no exemption for legacy reports", () => {
const plan = buildFixPlan(auditReport(), { oaOnlySmokeEmployeeNos: ["TEST002"] });
expect(plan.archiveOaTestEmployees.map((item) => item.employeeNo)).toEqual(["TEST001"]);
expect(plan.oaOnlySmokeExempted.map((item) => item.employeeNo)).toEqual(["TEST002"]);
});
it("requires explicit environment approval for apply mode", () => {
expect(() => assertApplyAllowed({ mode: "apply" })).toThrow(/WOUA_FIX_APPLY=1/);
process.env.WOUA_FIX_APPLY = "1";
expect(() => assertApplyAllowed({ mode: "apply" })).not.toThrow();
});
it("parses mode and repeated employee allowlist arguments", () => {
const options = parseArgs([
"--report=summary.json",
"--apply",
"--allow-employee-no=TEST001,TEST002",
"--allow-employee-no",
"TEST001",
"--oa-only-smoke-employee-no=TEST003",
"--repair-manager-effects",
"--manager-fallback-employee-no=HYD000007",
"--format=json",
]);
expect(options.mode).toBe("apply");
expect(options.reportPath).toBe("summary.json");
expect(options.format).toBe("json");
expect(options.allowEmployeeNos).toEqual(["TEST001", "TEST002"]);
expect(options.oaOnlySmokeEmployeeNos).toEqual(["TEST003"]);
expect(options.repairManagerEffects).toBe(true);
expect(options.managerFallbackEmployeeNo).toBe("HYD000007");
});
it("deduplicates normalized employee numbers", () => {
expect(normalizeEmployeeNoList([" TEST001 ", "TEST001", "", "TEST002"])).toEqual(["TEST001", "TEST002"]);
});
it("dry-runs inside a rolled-back transaction and does not update employees", async () => {
const plan = buildFixPlan(auditReport(), { allowEmployeeNos: ["TEST001"] });
const client = buildClient([employeeRow({ employeeNo: "TEST001" })]);
const result = await applyPlanWithClient(client, plan, {
mode: "dry_run",
now: new Date("2026-05-18T00:00:00.000Z"),
});
expect(result.status).toBe("DRY_RUN");
expect(result.totals.archivedPlanned).toBe(1);
expect(sqlCalls(client, "UPDATE employees")).toHaveLength(0);
expect(sqlCalls(client, "ROLLBACK")).toHaveLength(1);
expect(sqlCalls(client, "COMMIT")).toHaveLength(0);
});
it("archives active test employees in apply mode and increments token_version", async () => {
const plan = buildFixPlan(auditReport(), { allowEmployeeNos: ["TEST001"] });
const client = buildClient([employeeRow({ employeeNo: "TEST001" })]);
const result = await applyPlanWithClient(client, plan, {
mode: "apply",
now: new Date("2026-05-18T00:00:00.000Z"),
});
expect(result.status).toBe("APPLIED");
expect(result.totals.archivedApplied).toBe(1);
expect(sqlCalls(client, "UPDATE employees")).toHaveLength(1);
expect(sqlCalls(client, "token_version = token_version + 1")).toHaveLength(1);
expect(sqlCalls(client, "INSERT INTO employee_lifecycle_events")).toHaveLength(1);
expect(sqlCalls(client, "COMMIT")).toHaveLength(1);
expect(sqlCalls(client, "ROLLBACK")).toHaveLength(0);
});
it("keeps inactive WeCom bindings by default and clears them only with explicit flag", async () => {
const plan = buildFixPlan(auditReport(), { clearInactiveBindings: true });
const dryClient = buildClient([
employeeRow({ employeeNo: "TEST001" }),
employeeRow({
id: "22222222-2222-4222-8222-222222222222",
employeeNo: "HRWDTEST-20260517214548",
status: "archived",
employmentStatus: "inactive",
archived: true,
hasWecomBinding: true,
}),
]);
const defaultPlan = buildFixPlan(auditReport());
const defaultClient = buildClient([
employeeRow({ employeeNo: "TEST001" }),
]);
const defaultResult = await applyPlanWithClient(defaultClient, defaultPlan, {
mode: "apply",
now: new Date("2026-05-18T00:00:00.000Z"),
});
const explicitResult = await applyPlanWithClient(dryClient, plan, {
mode: "apply",
clearInactiveBindings: true,
now: new Date("2026-05-18T00:00:00.000Z"),
});
expect(defaultResult.totals.inactiveBindingAccepted).toBe(1);
expect(sqlCalls(defaultClient, "DELETE FROM wecom_identity_bindings")).toHaveLength(0);
expect(explicitResult.totals.inactiveBindingApplied).toBe(1);
expect(sqlCalls(dryClient, "DELETE FROM wecom_identity_bindings")).toHaveLength(1);
});
});