import { randomUUID } from "node:crypto"; import { INestApplication } from "@nestjs/common"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, type AuthedTokens } from "./setup.js"; import type { CnpjLookupResult } from "@eden/integrations"; /** * POST /v1/resellers/:id/cnpj-lookup — mirrors * client-registrations-cnpj-lookup.spec.ts. `lookupCnpj` is mocked * throughout (no network calls to cnpj.ws/BrasilAPI in this suite); a real * call against the internet is done manually, once, outside of vitest. */ const lookupCnpjMock = vi.fn<(cnpj: string) => Promise>(); vi.mock("@eden/integrations", async () => { const actual = await vi.importActual("@eden/integrations"); return { ...actual, lookupCnpj: (cnpj: string) => lookupCnpjMock(cnpj), }; }); const FOUND_RESULT: CnpjLookupResult = { companyName: "qa-test- Revenda Lookup LTDA", tradeName: "qa-test- Revenda Lookup", openingDate: "2012-07-19", legalNature: "Sociedade Empresária Limitada", cnpjSituation: "ATIVA", mainActivity: "Comércio varejista de equipamentos de telecomunicação", address: { zip: "04547-000", street: "Av. Brigadeiro Faria Lima", number: "2000", complement: "Conjunto 20", neighborhood: "Itaim Bibi", city: "São Paulo", state: "SP", }, stateRegistration: "987.654.321.114", partners: [ { name: "Ciclana de Tal", document: "***111222**", qualification: "Sócio-Administrador" }, { name: "Sicrano da Silva", document: "***333444**", qualification: "Sócio" }, ], }; describe("resellers — CNPJ lookup autofill", () => { let app: INestApplication; let admin: AuthedTokens; beforeAll(async () => { app = await createTestApp(); admin = await loginSuperAdmin(app); }); beforeEach(() => { lookupCnpjMock.mockReset(); }); afterAll(async () => { await closeTestApp(app); }); async function createReseller(name: string) { const res = await agent(app) .post("/api/v1/resellers") .set(...bearer(admin.accessToken)) .send({ name }) .expect(201); return res.body.id as string; } it("applies company data and creates partners when the CNPJ is found", async () => { lookupCnpjMock.mockResolvedValue(FOUND_RESULT); const id = await createReseller("qa-test- Revenda A"); const res = await agent(app) .post(`/api/v1/resellers/${id}/cnpj-lookup`) .set(...bearer(admin.accessToken)) .send({ cnpj: "11.222.333/0001-81" }) .expect(201); expect(lookupCnpjMock).toHaveBeenCalledWith("11.222.333/0001-81"); expect(res.body.reseller.document).toBe("11222333000181"); expect(res.body.reseller.trade_name).toBe(FOUND_RESULT.tradeName); expect(res.body.reseller.state_registration).toBe(FOUND_RESULT.stateRegistration); expect(res.body.reseller.legal_nature).toBe(FOUND_RESULT.legalNature); expect(res.body.reseller.cnpj_situation).toBe(FOUND_RESULT.cnpjSituation); expect(res.body.reseller.main_activity).toBe(FOUND_RESULT.mainActivity); expect(res.body.reseller.address_zip).toBe(FOUND_RESULT.address.zip); expect(res.body.reseller.address_street).toBe(FOUND_RESULT.address.street); expect(res.body.reseller.address_city).toBe(FOUND_RESULT.address.city); expect(res.body.reseller.address_state).toBe(FOUND_RESULT.address.state); expect(res.body.partners).toHaveLength(2); expect(res.body.partners.map((p: { name: string }) => p.name).sort()).toEqual( ["Ciclana de Tal", "Sicrano da Silva"].sort(), ); expect(res.body.partners.every((p: { source: string }) => p.source === "cnpj_lookup")).toBe(true); expect(res.body.partners.every((p: { will_sign: boolean }) => p.will_sign === false)).toBe(true); const getRes = await agent(app) .get(`/api/v1/resellers/${id}`) .set(...bearer(admin.accessToken)) .expect(200); expect(getRes.body.document).toBe("11222333000181"); const partnersRes = await agent(app) .get(`/api/v1/resellers/${id}/partners`) .set(...bearer(admin.accessToken)) .expect(200); expect(partnersRes.body).toHaveLength(2); }); it("running the lookup twice does not duplicate partners (dedup by document)", async () => { lookupCnpjMock.mockResolvedValue(FOUND_RESULT); const id = await createReseller("qa-test- Revenda B"); await agent(app) .post(`/api/v1/resellers/${id}/cnpj-lookup`) .set(...bearer(admin.accessToken)) .send({ cnpj: "11222333000181" }) .expect(201); const second = await agent(app) .post(`/api/v1/resellers/${id}/cnpj-lookup`) .set(...bearer(admin.accessToken)) .send({ cnpj: "11222333000181" }) .expect(201); expect(second.body.partners).toHaveLength(2); const partnersRes = await agent(app) .get(`/api/v1/resellers/${id}/partners`) .set(...bearer(admin.accessToken)) .expect(200); expect(partnersRes.body).toHaveLength(2); }); it("returns 404 and applies no changes when the CNPJ is not found in either source", async () => { lookupCnpjMock.mockResolvedValue(null); const id = await createReseller("qa-test- Revenda C"); await agent(app) .post(`/api/v1/resellers/${id}/cnpj-lookup`) .set(...bearer(admin.accessToken)) .send({ cnpj: "99999999000199" }) .expect(404); const getRes = await agent(app) .get(`/api/v1/resellers/${id}`) .set(...bearer(admin.accessToken)) .expect(200); expect(getRes.body.document).toBeNull(); expect(getRes.body.trade_name).toBeNull(); const partnersRes = await agent(app) .get(`/api/v1/resellers/${id}/partners`) .set(...bearer(admin.accessToken)) .expect(200); expect(partnersRes.body).toHaveLength(0); }); it("returns 404 for a reseller id that doesn't exist", async () => { await agent(app) .post(`/api/v1/resellers/${randomUUID()}/cnpj-lookup`) .set(...bearer(admin.accessToken)) .send({ cnpj: "11222333000181" }) .expect(404); expect(lookupCnpjMock).not.toHaveBeenCalled(); }); it("supports manual partner CRUD: create, list, update", async () => { const id = await createReseller("qa-test- Revenda D"); const created = await agent(app) .post(`/api/v1/resellers/${id}/partners`) .set(...bearer(admin.accessToken)) .send({ name: "qa-test- Sócio Manual", document: "12345678909", qualification: "Sócio" }) .expect(201); expect(created.body.source).toBe("manual"); expect(created.body.will_sign).toBe(false); const listRes = await agent(app) .get(`/api/v1/resellers/${id}/partners`) .set(...bearer(admin.accessToken)) .expect(200); expect(listRes.body).toHaveLength(1); const updated = await agent(app) .patch(`/api/v1/resellers/${id}/partners/${created.body.id}`) .set(...bearer(admin.accessToken)) .send({ cpf: "12345678909", willSign: true, signatureEmail: "socio@example.com" }) .expect(200); expect(updated.body.cpf).toBe("12345678909"); expect(updated.body.will_sign).toBe(true); expect(updated.body.signature_email).toBe("socio@example.com"); }); it("returns 404 for a partner update targeting a partner that doesn't belong to the reseller", async () => { const idA = await createReseller("qa-test- Revenda E1"); const idB = await createReseller("qa-test- Revenda E2"); const created = await agent(app) .post(`/api/v1/resellers/${idA}/partners`) .set(...bearer(admin.accessToken)) .send({ name: "qa-test- Sócio Cruzado" }) .expect(201); await agent(app) .patch(`/api/v1/resellers/${idB}/partners/${created.body.id}`) .set(...bearer(admin.accessToken)) .send({ willSign: true }) .expect(404); }); it("sets and updates the legal representative fields, unlocked (unlike client_registrations' pj_rep_cpf)", async () => { const id = await createReseller("qa-test- Revenda Representante"); const created = await agent(app) .patch(`/api/v1/resellers/${id}`) .set(...bearer(admin.accessToken)) .send({ repFullName: "qa-test- Representante Um", repCpf: "52998224725", repRole: "Diretor", repEmail: "representante@example.com", repMobilePhone: "11999990000", repAuthorizationAck: true, }) .expect(200); expect(created.body.rep_full_name).toBe("qa-test- Representante Um"); expect(created.body.rep_cpf).toBe("52998224725"); expect(created.body.rep_authorization_ack).toBe(true); const updated = await agent(app) .patch(`/api/v1/resellers/${id}`) .set(...bearer(admin.accessToken)) .send({ repCpf: "11144477735" }) .expect(200); expect(updated.body.rep_cpf).toBe("11144477735"); expect(updated.body.rep_full_name).toBe("qa-test- Representante Um"); // untouched field survives COALESCE }); });