Ao digitar/já ter um CNPJ num cadastro de cliente PJ, busca automática contra a Receita Federal (cnpj.ws primário, BrasilAPI fallback) e preenche razão social, endereço e inscrição estadual, além de criar os sócios encontrados (sem duplicar em buscas repetidas) — pra usar depois na confecção do contrato/assinatura. Fecha uma lacuna real que já estava prevista mas nunca implementada: pj_cnpj não tinha nenhuma forma de ser gravado (UpdateClientRegistrationDto o excluía deliberadamente, esperando por "a dedicated re-verification flow" — exatamente o que este endpoint é). O servidor sempre refaz a própria consulta autoritativa a partir do CNPJ recebido, nunca confia em dado "já encontrado" vindo do cliente. Suíte: 100/100 (95→100). pnpm turbo run typecheck build: 28/28. Testado ao vivo contra CNPJs reais (Petrobras, Banco do Brasil) e em navegador real via Playwright. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
184 lines
6.8 KiB
TypeScript
184 lines
6.8 KiB
TypeScript
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/client-registrations/:id/cnpj-lookup — the whole test suite
|
|
* mocks `lookupCnpj` (network calls to cnpj.ws/BrasilAPI are NOT allowed
|
|
* in this suite; a real call against the internet is done manually, once,
|
|
* outside of vitest — see the task notes).
|
|
*/
|
|
const lookupCnpjMock = vi.fn<(cnpj: string) => Promise<CnpjLookupResult | null>>();
|
|
|
|
vi.mock("@eden/integrations", async () => {
|
|
const actual = await vi.importActual<typeof import("@eden/integrations")>("@eden/integrations");
|
|
return {
|
|
...actual,
|
|
lookupCnpj: (cnpj: string) => lookupCnpjMock(cnpj),
|
|
};
|
|
});
|
|
|
|
const FOUND_RESULT: CnpjLookupResult = {
|
|
companyName: "qa-test- Empresa Lookup LTDA",
|
|
tradeName: "qa-test- Lookup",
|
|
openingDate: "2010-05-04",
|
|
legalNature: "Sociedade Empresária Limitada",
|
|
cnpjSituation: "ATIVA",
|
|
mainActivity: "Desenvolvimento de programas de computador sob encomenda",
|
|
address: {
|
|
zip: "01310-100",
|
|
street: "Av. Paulista",
|
|
number: "1000",
|
|
complement: "Sala 10",
|
|
neighborhood: "Bela Vista",
|
|
city: "São Paulo",
|
|
state: "SP",
|
|
},
|
|
stateRegistration: "123.456.789.110",
|
|
partners: [
|
|
{ name: "Fulano de Tal", document: "***123456**", qualification: "Sócio-Administrador" },
|
|
{ name: "Beltrano da Silva", document: "***654321**", qualification: "Sócio" },
|
|
],
|
|
};
|
|
|
|
describe("client-registrations — 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 createDirectRegistration(personType: "pf" | "pj") {
|
|
const res = await agent(app)
|
|
.post("/api/v1/client-registrations/direct")
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ personType, isPortability: false })
|
|
.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 createDirectRegistration("pj");
|
|
|
|
const res = await agent(app)
|
|
.post(`/api/v1/client-registrations/${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.registration.pj_cnpj).toBe("11222333000181");
|
|
expect(res.body.registration.pj_company_name).toBe(FOUND_RESULT.companyName);
|
|
expect(res.body.registration.pj_trade_name).toBe(FOUND_RESULT.tradeName);
|
|
expect(res.body.registration.pj_state_registration).toBe(FOUND_RESULT.stateRegistration);
|
|
expect(res.body.registration.pj_legal_nature).toBe(FOUND_RESULT.legalNature);
|
|
expect(res.body.registration.pj_cnpj_situation).toBe(FOUND_RESULT.cnpjSituation);
|
|
expect(res.body.registration.pj_main_activity).toBe(FOUND_RESULT.mainActivity);
|
|
expect(res.body.registration.address_zip).toBe(FOUND_RESULT.address.zip);
|
|
expect(res.body.registration.address_street).toBe(FOUND_RESULT.address.street);
|
|
expect(res.body.registration.address_city).toBe(FOUND_RESULT.address.city);
|
|
expect(res.body.registration.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(
|
|
["Beltrano da Silva", "Fulano de Tal"].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/client-registrations/${id}`)
|
|
.set(...bearer(admin.accessToken))
|
|
.expect(200);
|
|
expect(getRes.body.pj_cnpj).toBe("11222333000181");
|
|
|
|
const partnersRes = await agent(app)
|
|
.get(`/api/v1/client-registrations/${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 createDirectRegistration("pj");
|
|
|
|
await agent(app)
|
|
.post(`/api/v1/client-registrations/${id}/cnpj-lookup`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ cnpj: "11222333000181" })
|
|
.expect(201);
|
|
|
|
const second = await agent(app)
|
|
.post(`/api/v1/client-registrations/${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/client-registrations/${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 createDirectRegistration("pj");
|
|
|
|
await agent(app)
|
|
.post(`/api/v1/client-registrations/${id}/cnpj-lookup`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ cnpj: "99999999000199" })
|
|
.expect(404);
|
|
|
|
const getRes = await agent(app)
|
|
.get(`/api/v1/client-registrations/${id}`)
|
|
.set(...bearer(admin.accessToken))
|
|
.expect(200);
|
|
expect(getRes.body.pj_cnpj).toBeNull();
|
|
expect(getRes.body.pj_company_name).toBeNull();
|
|
|
|
const partnersRes = await agent(app)
|
|
.get(`/api/v1/client-registrations/${id}/partners`)
|
|
.set(...bearer(admin.accessToken))
|
|
.expect(200);
|
|
expect(partnersRes.body).toHaveLength(0);
|
|
});
|
|
|
|
it("rejects CNPJ lookup for a PF registration with 400", async () => {
|
|
const id = await createDirectRegistration("pf");
|
|
|
|
const res = await agent(app)
|
|
.post(`/api/v1/client-registrations/${id}/cnpj-lookup`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ cnpj: "11222333000181" })
|
|
.expect(400);
|
|
expect(res.body.message).toMatch(/pessoa jurídica/i);
|
|
expect(lookupCnpjMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns 404 for a client registration id that doesn't exist", async () => {
|
|
await agent(app)
|
|
.post(`/api/v1/client-registrations/${randomUUID()}/cnpj-lookup`)
|
|
.set(...bearer(admin.accessToken))
|
|
.send({ cnpj: "11222333000181" })
|
|
.expect(404);
|
|
expect(lookupCnpjMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|