Usuário: o cadastro de revenda precisa ficar completo porque essa mesma base vai alimentar a criação de contrato com a revenda dentro do ERP. resellers ganha rep_full_name/rep_cpf/rep_role/rep_email/ rep_mobile_phone/rep_authorization_ack, mesmo shape de client_registrations.pj_rep_* — um contrato precisa de um signatário nomeado e autorizado, não só a lista geral de sócios. Divergência deliberada: ao contrário de pj_rep_cpf (bloqueado do PATCH genérico em client_registrations, esperando um fluxo de submissão pública que o EDEN ainda não construiu — hoje uma lacuna conhecida, nunca preenchido de fato), rep_cpf aqui fica livremente editável — revenda não tem esse fluxo de submissão pública pra travar atrás. Nova tela: card "Representante legal" entre "Dados da revenda" e "Sócios". Suíte: 107/107 (106→107). pnpm turbo run typecheck build: 28/28. Testado ao vivo em navegador real. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
243 lines
8.9 KiB
TypeScript
243 lines
8.9 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/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<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- 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
|
|
});
|
|
});
|