支持企微通讯录缺失手机号(07-26-internal-ops-recovery-baseline)
这个提交包含在:
@@ -0,0 +1,169 @@
|
||||
const ExcelJS = require("exceljs");
|
||||
|
||||
const {
|
||||
DirectoryExportError,
|
||||
buildSafeReport,
|
||||
parseArgs,
|
||||
parseDirectoryExport,
|
||||
} = require("../../../src/scripts/import-wecom-directory-export");
|
||||
|
||||
async function buildDirectoryWorkbook(rows) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet("通讯录");
|
||||
worksheet.addRow(["填写须知:本行为企微导出说明"]);
|
||||
worksheet.addRow([]);
|
||||
worksheet.addRow([
|
||||
"姓名", "账号", "别名", "职务", "部门", "性别", "手机", "座机",
|
||||
"邮箱", "地址", "企业邮箱", "视频号", "激活状态", "禁用状态", "微信插件",
|
||||
]);
|
||||
for (const row of rows) {
|
||||
worksheet.addRow([
|
||||
row.name,
|
||||
row.account,
|
||||
"",
|
||||
row.title ?? "员工",
|
||||
row.department ?? "总部",
|
||||
"",
|
||||
row.phone ?? "",
|
||||
"",
|
||||
row.email ?? "",
|
||||
"",
|
||||
row.businessEmail,
|
||||
"",
|
||||
"已激活",
|
||||
"",
|
||||
"未关注",
|
||||
]);
|
||||
}
|
||||
return Buffer.from(await workbook.xlsx.writeBuffer());
|
||||
}
|
||||
|
||||
describe("WeCom directory export importer", () => {
|
||||
it("requires stdin, the apply environment gate, and an actor for apply", () => {
|
||||
const previous = process.env.WECOM_DIRECTORY_EXPORT_APPLY;
|
||||
delete process.env.WECOM_DIRECTORY_EXPORT_APPLY;
|
||||
try {
|
||||
expect(() => parseArgs(["--input=directory.xlsx"])).toThrowError(
|
||||
expect.objectContaining({ code: "stdin_required" }),
|
||||
);
|
||||
expect(() => parseArgs(["--apply", "--actor-employee-no=HYD000001"])).toThrowError(
|
||||
expect.objectContaining({ code: "apply_confirmation_required" }),
|
||||
);
|
||||
process.env.WECOM_DIRECTORY_EXPORT_APPLY = "1";
|
||||
expect(() => parseArgs(["--apply"])).toThrowError(
|
||||
expect.objectContaining({ code: "actor_required" }),
|
||||
);
|
||||
expect(parseArgs(["--apply", "--actor-employee-no=HYD000001"])).toEqual(
|
||||
expect.objectContaining({ input: "-", mode: "apply", actorEmployeeNo: "HYD000001" }),
|
||||
);
|
||||
} finally {
|
||||
if (previous == null) delete process.env.WECOM_DIRECTORY_EXPORT_APPLY;
|
||||
else process.env.WECOM_DIRECTORY_EXPORT_APPLY = previous;
|
||||
}
|
||||
});
|
||||
|
||||
it("finds the official Chinese header after instruction rows and accepts one missing phone", async () => {
|
||||
const rows = Array.from({ length: 6 }, (_, index) => ({
|
||||
name: `合成员${index + 1}`,
|
||||
account: `member-${index + 1}`,
|
||||
phone: index === 5 ? "" : `1990000000${index + 1}`,
|
||||
businessEmail: `member-${index + 1}@example.test`,
|
||||
}));
|
||||
const parsed = await parseDirectoryExport(await buildDirectoryWorkbook(rows));
|
||||
|
||||
expect(parsed.sourceSummary).toEqual({
|
||||
memberCount: 6,
|
||||
activeCount: 6,
|
||||
phonePresentCount: 5,
|
||||
phoneMissingCount: 1,
|
||||
emailPresentCount: 6,
|
||||
incompleteCount: 0,
|
||||
});
|
||||
expect(parsed.profiles[5].phone).toBeNull();
|
||||
expect(parsed.profiles[5].missingRequiredFields).toEqual([]);
|
||||
|
||||
const report = buildSafeReport(
|
||||
{ mode: "dry_run" },
|
||||
parsed,
|
||||
{ summary: { created: 5, updated: 1, planned: 6 } },
|
||||
);
|
||||
const serialized = JSON.stringify(report);
|
||||
expect(report.status).toBe("PASS_DIRECTORY_EXPORT_DRY_RUN");
|
||||
for (const row of rows) {
|
||||
expect(serialized).not.toContain(row.name);
|
||||
expect(serialized).not.toContain(row.account);
|
||||
expect(serialized).not.toContain(row.businessEmail);
|
||||
if (row.phone) expect(serialized).not.toContain(row.phone);
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
["account", { account: "member-1", phone: "19900000002", businessEmail: "[email protected]" }],
|
||||
["phone", { account: "member-2", phone: "19900000001", businessEmail: "[email protected]" }],
|
||||
["email", { account: "member-2", phone: "19900000002", businessEmail: "[email protected]" }],
|
||||
])("rejects duplicate %s values without returning the duplicated value", async (_field, duplicateRow) => {
|
||||
const buffer = await buildDirectoryWorkbook([
|
||||
{
|
||||
name: "合成员一",
|
||||
account: "member-1",
|
||||
phone: "19900000001",
|
||||
businessEmail: "[email protected]",
|
||||
},
|
||||
{ name: "合成员二", ...duplicateRow },
|
||||
]);
|
||||
|
||||
let caught;
|
||||
try {
|
||||
await parseDirectoryExport(buffer);
|
||||
} catch (error) {
|
||||
caught = error;
|
||||
}
|
||||
expect(caught).toBeInstanceOf(DirectoryExportError);
|
||||
expect(caught.code).toBe("duplicate_values");
|
||||
const serialized = JSON.stringify(caught);
|
||||
expect(serialized).not.toContain("[email protected]");
|
||||
expect(serialized).not.toContain("19900000001");
|
||||
});
|
||||
|
||||
it("requires a valid enterprise email even when a personal email is present", async () => {
|
||||
const parsed = await parseDirectoryExport(await buildDirectoryWorkbook([{
|
||||
name: "合成员一",
|
||||
account: "member-1",
|
||||
phone: "19900000001",
|
||||
email: "[email protected]",
|
||||
businessEmail: "",
|
||||
}]));
|
||||
|
||||
expect(parsed.sourceSummary.incompleteCount).toBe(1);
|
||||
expect(parsed.profiles[0].missingRequiredFields).toContain("email");
|
||||
});
|
||||
|
||||
it("blocks a supplied invalid phone instead of treating it as an absent phone", async () => {
|
||||
const parsed = await parseDirectoryExport(await buildDirectoryWorkbook([{
|
||||
name: "合成员一",
|
||||
account: "member-1",
|
||||
phone: "not-a-phone",
|
||||
businessEmail: "[email protected]",
|
||||
}]));
|
||||
|
||||
expect(parsed.sourceSummary.phoneMissingCount).toBe(1);
|
||||
expect(parsed.sourceSummary.incompleteCount).toBe(1);
|
||||
expect(parsed.profiles[0].missingRequiredFields).toContain("phone_invalid");
|
||||
});
|
||||
|
||||
it("uses a distinct success status for an apply report", async () => {
|
||||
const parsed = await parseDirectoryExport(await buildDirectoryWorkbook([{
|
||||
name: "合成员一",
|
||||
account: "member-1",
|
||||
phone: "",
|
||||
businessEmail: "[email protected]",
|
||||
}]));
|
||||
|
||||
const report = buildSafeReport(
|
||||
{ mode: "apply" },
|
||||
parsed,
|
||||
{ summary: { created: 1, success: 1 } },
|
||||
);
|
||||
expect(report.status).toBe("PASS_DIRECTORY_EXPORT_APPLY");
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户