const { WecomLoginAutoBindingError, autoBindExistingWecomEmployee, fetchWecomLoginIdentity, } = require("../../src/services/integrations/wecomLoginAutoBinding"); const EMPLOYEE_ID = "11111111-1111-4111-8111-111111111111"; function buildRequest() { return { id: "request-1", ip: "127.0.0.1", headers: { "user-agent": "test-agent" }, }; } function buildPrisma({ employees, useridBinding = null, employeeBinding = null, } = {}) { const tx = { employee: { findMany: vi.fn().mockResolvedValue( employees ?? [ { id: EMPLOYEE_ID, employee_no: "E0001", email: "member@example.test", status: "active", archived_at: null, token_version: 3, }, ], ), }, wecomIdentityBinding: { findUnique: vi .fn() .mockResolvedValueOnce(useridBinding) .mockResolvedValueOnce(employeeBinding), create: vi.fn().mockResolvedValue({ id: "binding-1" }), }, auditLog: { create: vi.fn().mockResolvedValue({ id: "audit-1" }) }, }; return { tx, prisma: { $transaction: vi.fn(async (callback) => callback(tx)) }, }; } describe("WeCom passwordless login auto binding", () => { it("reads and normalizes the enterprise email for the OAuth userid", async () => { const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ errcode: 0, userid: "wecom-user-1", status: 1, biz_mail: "Member@Example.Test", }), }); await expect( fetchWecomLoginIdentity({ accessToken: "access-token", externalUserId: "wecom-user-1", fetchImpl, }), ).resolves.toEqual({ externalUserId: "wecom-user-1", email: "member@example.test", }); expect(String(fetchImpl.mock.calls[0][0])).toContain("/cgi-bin/user/get"); }); it.each([ [ "inactive member", { errcode: 0, userid: "wecom-user-1", status: 2, biz_mail: "member@example.test", }, "wecom_member_inactive", ], [ "identity mismatch", { errcode: 0, userid: "another-user", status: 1, biz_mail: "member@example.test", }, "wecom_identity_mismatch", ], ])("fails closed for %s", async (_label, payload, code) => { const fetchImpl = vi .fn() .mockResolvedValue({ ok: true, json: async () => payload }); await expect( fetchWecomLoginIdentity({ accessToken: "access-token", externalUserId: "wecom-user-1", fetchImpl, }), ).rejects.toMatchObject({ code }); }); it("keeps userid matching available when the directory profile has no enterprise email", async () => { const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ errcode: 0, userid: "wecom-user-1", status: 1 }), }); await expect( fetchWecomLoginIdentity({ accessToken: "access-token", externalUserId: "wecom-user-1", fetchImpl, }), ).resolves.toEqual({ externalUserId: "wecom-user-1", email: null }); }); it("binds a unique active OA employee by enterprise email and writes a redacted audit", async () => { const { prisma, tx } = buildPrisma(); const result = await autoBindExistingWecomEmployee({ prisma, request: buildRequest(), identity: { externalUserId: "wecom-user-1", email: "member@example.test", }, externalOpenUserId: "open-user-1", }); expect(result).toEqual({ id: EMPLOYEE_ID, status: "active", token_version: 3, autoBound: true, }); expect(tx.employee.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { OR: [ { employee_no: "wecom-user-1" }, { email: { equals: "member@example.test", mode: "insensitive", }, }, ], }, }), ); expect(tx.wecomIdentityBinding.create).toHaveBeenCalledWith({ data: expect.objectContaining({ employee_id: EMPLOYEE_ID, external_user_id: "wecom-user-1", external_open_user_id: "open-user-1", }), }); expect(tx.auditLog.create).toHaveBeenCalledWith({ data: expect.objectContaining({ action: "AUTH_WECOM_AUTO_BOUND", extra: { matchBy: "email", emailMatched: true }, }), }); expect(JSON.stringify(tx.auditLog.create.mock.calls)).not.toContain( "member@example.test", ); expect(JSON.stringify(tx.auditLog.create.mock.calls)).not.toContain( "wecom-user-1", ); }); it("fails closed when userid and email point to different OA employees", async () => { const { prisma, tx } = buildPrisma({ employees: [ { id: EMPLOYEE_ID, employee_no: "wecom-user-1", email: "first@example.test", status: "active", archived_at: null, token_version: 0, }, { id: "22222222-2222-4222-8222-222222222222", employee_no: "E0002", email: "member@example.test", status: "active", archived_at: null, token_version: 0, }, ], }); await expect( autoBindExistingWecomEmployee({ prisma, request: buildRequest(), identity: { externalUserId: "wecom-user-1", email: "member@example.test", }, }), ).rejects.toMatchObject({ code: "oa_employee_conflict" }); expect(tx.wecomIdentityBinding.create).not.toHaveBeenCalled(); expect(tx.auditLog.create).not.toHaveBeenCalled(); }); it("binds by exact userid when the OA employee number already carries the userid", async () => { const { prisma, tx } = buildPrisma({ employees: [ { id: EMPLOYEE_ID, employee_no: "wecom-user-1", email: "different@example.test", status: "active", archived_at: null, token_version: 1, }, ], }); await expect( autoBindExistingWecomEmployee({ prisma, request: buildRequest(), identity: { externalUserId: "wecom-user-1", email: null }, }), ).resolves.toEqual( expect.objectContaining({ id: EMPLOYEE_ID, autoBound: true }), ); expect(tx.auditLog.create).toHaveBeenCalledWith({ data: expect.objectContaining({ extra: { matchBy: "userid", emailMatched: false }, }), }); }); it("does not overwrite an OA employee binding for another userid", async () => { const { prisma, tx } = buildPrisma({ employeeBinding: { external_user_id: "another-wecom-user" }, }); await expect( autoBindExistingWecomEmployee({ prisma, request: buildRequest(), identity: { externalUserId: "wecom-user-1", email: "member@example.test", }, }), ).rejects.toMatchObject({ code: "oa_employee_binding_conflict" }); expect(tx.wecomIdentityBinding.create).not.toHaveBeenCalled(); }); it("exposes only stable error codes", () => { const error = new WecomLoginAutoBindingError( "oa_employee_not_found", "internal detail", ); expect(error.code).toBe("oa_employee_not_found"); }); });