POST /api/leads/:id/ingesta (Bearer FUNNEL_API_KEY): acepta items foto/texto etiquetados por zona y momento, más flags perfilCompleto y finalizar. - ingesta-schema.ts: zod del cuerpo (union discriminada foto|texto), exportado para test; rechaza llamadas vacías. - route.ts: auth 401, valida lead (404), inserta fotos (orden continúa el máx) y notas, traza fotos_subidas; perfilCompleto→señalarPerfilCompleto, finalizar→finalizarYEntregar. - 10 tests del schema. Verificado por HTTP: 401/200/422/404 y finalizar genera el pdf_url y avanza el lead a whatsapp_entregado. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ingestaBodySchema } from '@/lib/funnel/ingesta-schema';
|
|
|
|
describe('ingestaBodySchema', () => {
|
|
it('acepta una foto con zona y momento por defecto "antes"', () => {
|
|
const r = ingestaBodySchema.safeParse({
|
|
items: [{ tipo: 'foto', zona: 'bano', imagen: 'data:image/png;base64,AAA' }],
|
|
});
|
|
expect(r.success).toBe(true);
|
|
if (r.success) {
|
|
const foto = r.data.items[0];
|
|
expect(foto.tipo).toBe('foto');
|
|
if (foto.tipo === 'foto') expect(foto.momento).toBe('antes');
|
|
}
|
|
});
|
|
|
|
it('acepta momento "despues" explícito', () => {
|
|
const r = ingestaBodySchema.safeParse({
|
|
items: [{ tipo: 'foto', zona: 'cocina', momento: 'despues', imagen: 'https://x/y.jpg' }],
|
|
});
|
|
expect(r.success).toBe(true);
|
|
});
|
|
|
|
it('acepta una nota de texto y zona opcional', () => {
|
|
const r = ingestaBodySchema.safeParse({ items: [{ tipo: 'texto', texto: 'suelo premium' }] });
|
|
expect(r.success).toBe(true);
|
|
});
|
|
|
|
it('acepta items mixtos foto + texto', () => {
|
|
const r = ingestaBodySchema.safeParse({
|
|
items: [
|
|
{ tipo: 'foto', zona: 'bano', imagen: 'data:image/png;base64,AAA' },
|
|
{ tipo: 'texto', zona: 'bano', texto: 'suelo premium' },
|
|
],
|
|
});
|
|
expect(r.success).toBe(true);
|
|
});
|
|
|
|
it('acepta una llamada solo con flag perfilCompleto (sin items)', () => {
|
|
const r = ingestaBodySchema.safeParse({ perfilCompleto: true });
|
|
expect(r.success).toBe(true);
|
|
if (r.success) expect(r.data.items).toEqual([]);
|
|
});
|
|
|
|
it('acepta una llamada solo con flag finalizar', () => {
|
|
expect(ingestaBodySchema.safeParse({ finalizar: true }).success).toBe(true);
|
|
});
|
|
|
|
it('rechaza una llamada totalmente vacía', () => {
|
|
expect(ingestaBodySchema.safeParse({}).success).toBe(false);
|
|
expect(ingestaBodySchema.safeParse({ items: [] }).success).toBe(false);
|
|
});
|
|
|
|
it('rechaza un tipo de item inválido', () => {
|
|
const r = ingestaBodySchema.safeParse({ items: [{ tipo: 'video', imagen: 'x' }] });
|
|
expect(r.success).toBe(false);
|
|
});
|
|
|
|
it('rechaza una zona fuera del enum', () => {
|
|
const r = ingestaBodySchema.safeParse({
|
|
items: [{ tipo: 'foto', zona: 'jardin', imagen: 'data:...' }],
|
|
});
|
|
expect(r.success).toBe(false);
|
|
});
|
|
|
|
it('rechaza foto sin imagen y texto vacío', () => {
|
|
expect(ingestaBodySchema.safeParse({ items: [{ tipo: 'foto', imagen: '' }] }).success).toBe(
|
|
false,
|
|
);
|
|
expect(ingestaBodySchema.safeParse({ items: [{ tipo: 'texto', texto: ' ' }] }).success).toBe(
|
|
false,
|
|
);
|
|
});
|
|
});
|