import { INestApplication } from "@nestjs/common"; import { Readable } from "node:stream"; import { PDFDocument } from "pdf-lib"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, qaEmail, qaName, type AuthedTokens } from "./setup.js"; /** * `POST /v1/signature-envelopes` with `signers` omitted — contratante * signers derived automatically from the envelope's origin * (`clientRegistrationId`/`resellerId`): the legal representative (if * `*_rep_authorization_ack`) plus every sócio marked `will_sign`, deduped * by CPF. Mocks match `signature-envelope-flow.spec.ts` exactly (no real * Chromium/MinIO/SMTP needed). */ const s3Store = new Map(); vi.mock("@eden/integrations", async () => { const actual = await vi.importActual("@eden/integrations"); return { ...actual, renderPdf: vi.fn(async () => { const doc = await PDFDocument.create(); doc.addPage(); return Buffer.from(await doc.save()); }), uploadObject: vi.fn(async (key: string, buffer: Buffer) => { s3Store.set(key, buffer); }), getObjectStream: vi.fn(async (key: string) => { const buffer = s3Store.get(key); if (!buffer) throw new Error(`qa mock S3: no object at ${key}`); return Readable.from([buffer]); }), sendMail: vi.fn(async () => undefined), }; }); describe("signature envelopes — automatic contratante signer derivation", () => { let app: INestApplication; let admin: AuthedTokens; let companyId: string; let generationId: string; async function createGeneration(resellerId?: string, clientRegistrationId?: string) { const template = await agent(app) .post("/api/v1/documents/templates") .set(...bearer(admin.accessToken)) .send({ key: `qa_test_autosig_${Date.now()}_${Math.random().toString(36).slice(2)}`, name: qaName("Template AutoSigners"), docGroup: "revenda" }) .expect(201); const templateId = template.body.id; await agent(app).post(`/api/v1/documents/templates/${templateId}/draft`).set(...bearer(admin.accessToken)).expect(201); await agent(app) .patch(`/api/v1/documents/templates/${templateId}/draft`) .set(...bearer(admin.accessToken)) .send({ editorContent: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "Contrato de teste." }] }] }, }) .expect(200); await agent(app).post(`/api/v1/documents/templates/${templateId}/publish`).set(...bearer(admin.accessToken)).expect(201); const generation = await agent(app) .post("/api/v1/documents/generations") .set(...bearer(admin.accessToken)) .send({ templateId, resellerId, clientRegistrationId, context: {} }) .expect(201); return generation.body.id as string; } beforeAll(async () => { app = await createTestApp(); admin = await loginSuperAdmin(app); const company = await agent(app) .post("/api/v1/companies") .set(...bearer(admin.accessToken)) .send({ companyName: qaName("Empresa AutoSigners"), legalRepName: "Representante Legal QA", legalRepCpf: "11144477735", legalRepEmail: qaEmail("autosig-contratada"), legalRepRole: "Diretor", }) .expect(201); companyId = company.body.id; }); afterAll(async () => { await closeTestApp(app); }); describe("origem client_registrations (PJ)", () => { let registrationId: string; beforeAll(async () => { const reg = await agent(app) .post("/api/v1/client-registrations/direct") .set(...bearer(admin.accessToken)) .send({ personType: "pj", isPortability: false }) .expect(201); registrationId = reg.body.id; generationId = await createGeneration(undefined, registrationId); }); it("400 — sem representante autorizado e sem sócio marcado pra assinar, nada pra derivar", async () => { const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, clientRegistrationId: registrationId, documentGenerationIds: [generationId] }) .expect(400); expect(res.body.message).toMatch(/Nenhum sócio marcado|representante legal autorizado/); }); it("400 — sócio marcado pra assinar mas sem e-mail de assinatura", async () => { await agent(app) .post(`/api/v1/client-registrations/${registrationId}/partners`) .set(...bearer(admin.accessToken)) .send({ name: qaName("Sócio Sem Email"), willSign: true }) .expect(201); const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, clientRegistrationId: registrationId, documentGenerationIds: [generationId] }) .expect(400); expect(res.body.message).toContain("e-mail de assinatura"); }); it("deriva o representante legal autorizado + o sócio marcado pra assinar, ignora o não-marcado", async () => { // Cadastro próprio, dedicado — o `registrationId` do describe já // acumulou, nos testes anteriores, um sócio `will_sign=true` sem // e-mail (criado de propósito pra testar o 400) que continuaria // presente e derrubaria este teste por um motivo alheio ao que ele // verifica. const reg = await agent(app) .post("/api/v1/client-registrations/direct") .set(...bearer(admin.accessToken)) .send({ personType: "pj", isPortability: false }) .expect(201); const freshRegistrationId = reg.body.id; const freshGenerationId = await createGeneration(undefined, freshRegistrationId); const repEmail = qaEmail("autosig-pj-rep"); await agent(app) .patch(`/api/v1/client-registrations/${freshRegistrationId}`) .set(...bearer(admin.accessToken)) .send({ pjRepFullName: "Representante PJ QA", pjRepRole: "Sócio-Administrador", pjRepEmail: repEmail, pjRepMobilePhone: "11988887777", pjRepAuthorizationAck: true, }) .expect(200); const willSignEmail = qaEmail("autosig-pj-partner"); await agent(app) .post(`/api/v1/client-registrations/${freshRegistrationId}/partners`) .set(...bearer(admin.accessToken)) .send({ name: qaName("Sócio Assina"), willSign: true, signatureEmail: willSignEmail, qualification: "Sócio" }) .expect(201); await agent(app) .post(`/api/v1/client-registrations/${freshRegistrationId}/partners`) .set(...bearer(admin.accessToken)) .send({ name: qaName("Sócio Não Assina") }) .expect(201); const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, clientRegistrationId: freshRegistrationId, documentGenerationIds: [freshGenerationId] }) .expect(201); const contratantes = res.body.signers.filter((s: { party: string }) => s.party === "contratante"); expect(contratantes).toHaveLength(2); // rep + o único sócio marcado pra assinar const emails = contratantes.map((s: { email: string }) => s.email); expect(emails).toContain(repEmail); expect(emails).toContain(willSignEmail); }); }); describe("origem client_registrations (PF)", () => { it("usa o próprio titular como signatário único", async () => { const reg = await agent(app) .post("/api/v1/client-registrations/direct") .set(...bearer(admin.accessToken)) .send({ personType: "pf", isPortability: false }) .expect(201); const registrationId = reg.body.id; const titularEmail = qaEmail("autosig-pf-titular"); await agent(app) .patch(`/api/v1/client-registrations/${registrationId}`) .set(...bearer(admin.accessToken)) .send({ pfFullName: "Titular PF QA", pfEmail: titularEmail, pfMobilePhone: "11977776666" }) .expect(200); const genId = await createGeneration(undefined, registrationId); const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, clientRegistrationId: registrationId, documentGenerationIds: [genId] }) .expect(201); const contratantes = res.body.signers.filter((s: { party: string }) => s.party === "contratante"); expect(contratantes).toHaveLength(1); expect(contratantes[0].email).toBe(titularEmail); expect(contratantes[0].full_name).toBe("Titular PF QA"); }); }); describe("origem resellers", () => { let resellerId: string; beforeAll(async () => { const reseller = await agent(app) .post("/api/v1/resellers") .set(...bearer(admin.accessToken)) .send({ name: qaName("Revenda AutoSigners") }) .expect(201); resellerId = reseller.body.id; generationId = await createGeneration(resellerId); }); afterAll(async () => { await agent(app) .patch(`/api/v1/resellers/${resellerId}`) .set(...bearer(admin.accessToken)) .send({ active: false }) .expect(200); }); it("dedup por CPF — sócio com o mesmo CPF do representante aparece só uma vez", async () => { const repCpf = "52998224725"; const repEmail = qaEmail("autosig-reseller-rep"); await agent(app) .patch(`/api/v1/resellers/${resellerId}`) .set(...bearer(admin.accessToken)) .send({ repFullName: "Representante Revenda QA", repCpf, repRole: "Diretor", repEmail, repAuthorizationAck: true, }) .expect(200); // Mesmo CPF do representante — não deve virar um segundo signatário. await agent(app) .post(`/api/v1/resellers/${resellerId}/partners`) .set(...bearer(admin.accessToken)) .send({ name: "Representante Revenda QA", cpf: repCpf, willSign: true, signatureEmail: qaEmail("autosig-reseller-dup") }) .expect(201); // (`toHaveLength(2)` abaixo já prova o dedup — rep + o único outro sócio, nunca 3.) const otherEmail = qaEmail("autosig-reseller-other"); await agent(app) .post(`/api/v1/resellers/${resellerId}/partners`) .set(...bearer(admin.accessToken)) .send({ name: qaName("Outro Sócio Revenda"), cpf: "12345678909", willSign: true, signatureEmail: otherEmail }) .expect(201); const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, resellerId, documentGenerationIds: [generationId] }) .expect(201); const contratantes = res.body.signers.filter((s: { party: string }) => s.party === "contratante"); expect(contratantes).toHaveLength(2); // rep (dedup'd against the same-CPF partner) + the other partner const emails = contratantes.map((s: { email: string }) => s.email); expect(emails).toContain(repEmail); expect(emails).toContain(otherEmail); }); }); it("signers explícito continua funcionando como override manual completo, mesmo com rep/sócios disponíveis", async () => { const reg = await agent(app) .post("/api/v1/client-registrations/direct") .set(...bearer(admin.accessToken)) .send({ personType: "pj", isPortability: false }) .expect(201); const registrationId = reg.body.id; await agent(app) .patch(`/api/v1/client-registrations/${registrationId}`) .set(...bearer(admin.accessToken)) .send({ pjRepFullName: "Rep Ignorado", pjRepEmail: qaEmail("autosig-override-ignored"), pjRepAuthorizationAck: true }) .expect(200); const genId = await createGeneration(undefined, registrationId); const manualEmail = qaEmail("autosig-override-manual"); const res = await agent(app) .post("/api/v1/signature-envelopes") .set(...bearer(admin.accessToken)) .send({ companyId, clientRegistrationId: registrationId, documentGenerationIds: [genId], signers: [{ fullName: "Signatário Manual", email: manualEmail }], }) .expect(201); const contratantes = res.body.signers.filter((s: { party: string }) => s.party === "contratante"); expect(contratantes).toHaveLength(1); expect(contratantes[0].email).toBe(manualEmail); }); });