fix: los cuatro hallazgos reales de la revision con IA
Revisión con IA / ai-review (pull_request) Successful in 29s
Revisión con IA / ai-review (pull_request) Successful in 29s
- crearTurno verificaba disponibilidad y creaba el turno en dos operaciones sueltas: dos clientes podian pasar la verificacion a la vez y sobrevender el mismo horario. Ahora va todo en una transaccion, serializada por jefe con pg_advisory_xact_lock. slotEstaDisponible acepta el cliente de la transaccion. Probado contra un Postgres real: 5 reservas simultaneas del mismo slot con capacidad 1 dan 1 ok + 4 conflictos y una sola fila; sacando el lock, entran las 5. - La deduplicacion del webhook estaba en memoria y solo servia con una instancia. Pasa a la base con el modelo MensajeWhatsappProcesado: el insert del id es atomico por el indice unico, asi que aguanta reintentos concurrentes de Meta (verificado). Se limpian los ids de mas de 7 dias. - postMensaje no reintentaba: ahora reintenta 3 veces ante 429, 5xx y fallas de red, con backoff exponencial y jitter. Un 4xx nuestro no se reintenta. - /api/cliente/servicios devolvia los trabajos de un jefe dado de baja; ahora responde 404 si el jefe no existe o no esta activo. test:envio suma los casos de reintento (35 asserts). El Prisma en memoria del test del bot ahora entiende $transaction y $executeRaw. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,8 @@ function ok(cond: boolean, msg: string) {
|
||||
// ── Stub de fetch: guarda lo que se le manda a Meta en vez de salir a internet ──
|
||||
type Llamada = { url: string; auth: string; body: any };
|
||||
let llamadas: Llamada[] = [];
|
||||
// Códigos a devolver, en orden. Si se vacía, responde 200.
|
||||
let respuestas: number[] = [];
|
||||
globalThis.fetch = (async (url: string | URL, init?: RequestInit) => {
|
||||
const headers = (init?.headers ?? {}) as Record<string, string>;
|
||||
llamadas.push({
|
||||
@@ -39,7 +41,8 @@ globalThis.fetch = (async (url: string | URL, init?: RequestInit) => {
|
||||
auth: headers["Authorization"] ?? "",
|
||||
body: JSON.parse(String(init?.body ?? "{}")),
|
||||
});
|
||||
return { ok: true, status: 200, text: async () => "" } as Response;
|
||||
const status = respuestas.shift() ?? 200;
|
||||
return { ok: status < 400, status, text: async () => "detalle" } as Response;
|
||||
}) as typeof fetch;
|
||||
|
||||
async function main() {
|
||||
@@ -97,6 +100,33 @@ async function main() {
|
||||
ok(llamadas.length === 2, "manda las respuestas múltiples en orden");
|
||||
ok(llamadas[0].body.text.body === "uno" && llamadas[1].body.text.body === "dos", "respeta el orden");
|
||||
|
||||
console.log("\n── Salida: reintentos ──");
|
||||
llamadas = [];
|
||||
respuestas = [429];
|
||||
await enviarRespuestas("549351", [{ tipo: "texto", texto: "con rate limit" }]);
|
||||
ok(llamadas.length === 2, "reintenta ante un 429 y sale bien en el segundo intento");
|
||||
|
||||
llamadas = [];
|
||||
respuestas = [500, 503];
|
||||
await enviarRespuestas("549351", [{ tipo: "texto", texto: "con 5xx" }]);
|
||||
ok(llamadas.length === 3, "reintenta ante 5xx hasta que uno sale bien");
|
||||
|
||||
llamadas = [];
|
||||
respuestas = [500, 500, 500];
|
||||
await enviarRespuestas("549351", [{ tipo: "texto", texto: "siempre 5xx" }]);
|
||||
ok(llamadas.length === 3, "no reintenta más de 3 veces");
|
||||
|
||||
llamadas = [];
|
||||
respuestas = [400];
|
||||
await enviarRespuestas("549351", [{ tipo: "texto", texto: "payload malo" }]);
|
||||
ok(llamadas.length === 1, "un 400 no se reintenta (es error nuestro)");
|
||||
|
||||
llamadas = [];
|
||||
respuestas = [401];
|
||||
await enviarRespuestas("549351", [{ tipo: "texto", texto: "token vencido" }]);
|
||||
ok(llamadas.length === 1, "un 401 no se reintenta");
|
||||
respuestas = [];
|
||||
|
||||
console.log("\n── Firma del webhook ──");
|
||||
const cuerpo = JSON.stringify({ hola: "chau", acento: "ñandú" });
|
||||
const firma = "sha256=" + crypto.createHmac("sha256", "secreto-de-prueba").update(cuerpo, "utf8").digest("hex");
|
||||
|
||||
Reference in New Issue
Block a user