Patrones de testing para desarrollo asistido por IA · Next.js 14 + Supabase + Vitest
crm_tags añadido a la respuesta pero no al SELECT.select("id, email, name, score"). La IA revisó su propio código y lo encontró "correcto".import { defineConfig } from "vitest/config"; import path from "path"; export default defineConfig({ test: { environment: "node", globals: true, include: ["__tests__/**/*.test.ts"], setupFiles: ["__tests__/setup.ts"], }, resolve: { alias: { "@": path.resolve(__dirname, "."), }, }, });
// Fuerza sandbox mode — sin base de datos // LeadFlow: todos los tests corren sin Supabase process.env.SANDBOX_MODE = "true"; process.env.NEXT_PUBLIC_SUPABASE_URL = ""; process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = ""; // LeadFlow-specific sandbox seeds process.env.SANDBOX_LEADS_COUNT = "10"; process.env.SANDBOX_ORG_ID = "org-demo-leadflow";
import { describe, it, expect } from "vitest"; import { createTestRequest, parseResponse } from "../../helpers"; import { GET } from "@/app/api/leads/profile/route"; // Contrato del endpoint: todos estos campos DEBEN estar siempre const REQUIRED_FIELDS = [ "id", "email", "full_name", "company", "score", // ← Añadido semana 1 "pipeline_stage", // ← BUG-R3: olvidado en sandbox path "crm_tags", // ← BUG-R1: no estaba en SELECT "assigned_to", "last_contacted", "created_at", ]; describe("GET /api/leads/profile", () => { it("devuelve todos los campos requeridos del contrato", async () => { const req = createTestRequest("/api/leads/profile"); const res = await GET(req); const { status, json } = await parseResponse(res); expect(status).toBe(200); for (const field of REQUIRED_FIELDS) { expect(json.data, `Campo "${field}" no encontrado`) .toHaveProperty(field); } }); // BUG-R1 regression — introducido por IA 4 veces it("crm_tags no es undefined en sandbox mode (BUG-R1)", async () => { const req = createTestRequest("/api/leads/profile"); const res = await GET(req); const { json } = await parseResponse(res); expect("crm_tags" in json.data).toBe(true); expect(json.data.crm_tags === null || Array.isArray(json.data.crm_tags)) .toBe(true); }); // BUG-R3 regression — sandbox/production path mismatch it("pipeline_stage existe en sandbox (BUG-R3)", async () => { const req = createTestRequest("/api/leads/profile"); const res = await GET(req); const { json } = await parseResponse(res); expect("pipeline_stage" in json.data).toBe(true); expect(json.data.pipeline_stage).not.toBeUndefined(); }); });
describe("GET /api/leads/list", () => { it("incluye assigned_to en sandbox mode", async () => { const req = createTestRequest( "/api/leads/list", { sandboxUserId: "user-001" } ); const res = await GET(req); const { json } = await parseResponse(res); if (json.data.length > 0) { for (const lead of json.data) { expect("assigned_to" in lead) .toBe(true); expect("last_contacted" in lead) .toBe(true); } } }); });
// Patrón PASS: rollback correcto const handleStageChange = async ( id: string, newStage: string ) => { const prevLeads = [...leads]; // captura prev setLeads(prev => prev.map(l => l.id === id ? { ...l, pipeline_stage: newStage } : l )); try { const res = await fetch( `/api/leads/${id}`, { method: "PATCH", body: JSON.stringify({newStage}) }); if (!res.ok) throw new Error(); } catch { setLeads(prevLeads); // rollback } };
| Patrón IA | Endpoint afectado | Estrategia de test | Prioridad | Ocurrencias |
|---|---|---|---|---|
| Sandbox/prod path mismatch Campo añadido en prod, olvidado en sandbox |
/api/leads/profile /api/leads/list |
Assert mismo response shape en sandbox mode | ▲ Alta | 3× |
| SELECT clause omission Columna en return pero no en .select() |
/api/leads/profile | Assert todos los campos requeridos en respuesta | ▲ Alta | 2× |
| Missing rollback Optimistic update sin capturar estado previo |
/api/leads/[id] PATCH | Assert estado restaurado tras API failure | ◆ Media | 1× |
| Type cast masking undefined TS types desactualizados ocultan errores en runtime |
/api/leads/* | Assert campo !== undefined explícitamente | ◆ Media | 1× |
| Error state leakage Error set pero datos anteriores no limpiados |
Frontend components | Assert state cleanup en error handler | ◆ Media | 0× (preventivo) |
# Bug Check — LeadFlow ## Step 1: Tests automáticos (OBLIGATORIO, no se puede saltear) Ejecuta estos comandos ANTES de cualquier revisión de código: npm run test # Suite Vitest en sandbox mode npm run build # TypeScript type check + Next.js build - Si tests fallan → reportar como bug de máxima prioridad - Si build falla → reportar type errors como máxima prioridad - Solo continuar al Step 2 si ambos pasan ## Step 2: Revisión de código (AI review con blind spots conocidos) 1. Consistencia sandbox/prod — ¿mismos campos en AMBAS rutas? 2. Shape de respuesta API — ¿coincide con lo que espera el frontend? 3. Completitud del SELECT — ¿todos los campos devueltos están en la query? 4. Error handling — ¿limpia estado previo antes de setError? 5. Optimistic updates — ¿captura prevState y hace rollback en catch? ## Step 3: Por cada bug encontrado, proponer regression test Nombrar el test: "descripción del bug (BUG-Rn)" Añadir al REQUIRED_FIELDS si es campo de contrato de API.