Skip to main content
Una transcripción dorada (golden transcript) es una conversación grabada (lo que enviaron los usuarios y lo que respondió el bot) guardada como un archivo JSON versionado. La commiteas junto a tus tests y la reproduces contra el bot: si un cambio altera una respuesta, la reproducción muestra el mensaje esperado y el real lado a lado. El flujo de trabajo:
  1. Grabar una conversación (en tests o en producción) o construirla a partir del log de mensajes del store.
  2. Redactarla para que ningún dato personal acabe en el repositorio.
  3. Guardarla como JSON.
  4. Reproducirla en un test (o en un gate de despliegue) y fallar ante cualquier diferencia.
@botmesh/core también reexporta las funciones de grabación, redacción y formato, marcadas como @deprecated: impórtalas de @botmesh/core/testing (la grabación también sirve en producción).

Grabar una conversación

recordTranscript(provider, options?) envuelve cualquier IProvider (un transporte real o un MockProvider) en un RecordingProvider transparente. Pasa recorder.provider al bot; graba los mensajes e interacciones entrantes y cada envío correcto.
El handle expone transcript({ name?, description? }) (un snapshot), clear(), pause() y resume(). En un test creado con createTestBot, chat.transcript() devuelve esa misma forma directamente desde el mock (ver Snapshots de conversación). Los envíos fallidos u omitidos no se graban. La grabación también funciona en producción (RecordingProvider envuelve tanto un transporte real como el mock), pero el buffer guarda en RAM los cuerpos de los mensajes, los nombres visibles y los ids sin redactar: mantén maxTurns acotado y llama a clear() en cuanto hayas sacado una transcripción.

Desde el log de mensajes del store

transcriptFromStore(source, chatIds, options?) lee uno o más chats con getMessagesByChat y los une en una transcripción ordenada por timestamp. Pasa el bot (o bot.getStore()): un Bot escribe en una vista con namespace del store que recibe, así que el store crudo no encuentra nada con los ids de chat sin más.
Opciones: name, limit (por chat, por defecto 1000), includeFailed (conserva los mensajes salientes con estado failed, por defecto false) e isGroup (un booleano o (chatId) => boolean; si se omite, transcriptFromStore lee el tipo de chat del store). transcriptFromMessages(messages, options?) hace lo mismo con un array de StoredMessage. El store no guarda el contenido saliente completo ni los nombres visibles, así que estas transcripciones solo comparan kind y text, y las reproducciones usan el push name por defecto del mock.

El formato del archivo

  • Turnos entrantes (dir: 'in'): chatId, senderId, type, text y, opcionalmente, event ('message' o 'interaction', donde text lleva los datos del botón o la lista), isGroup, displayName, id y t.
  • Turnos salientes (dir: 'out'): chatId, kind, text (texto, caption, pregunta…) y, opcionalmente, content (el contenido completo apto para JSON, solo al grabar en vivo; los binarios pasan a { $binary: { bytes, sha256 } }), id y t.
  • t son milisegundos desde el primer turno; startedAt (epoch ms) es opcional y la redacción lo elimina.
  • Los ids son strings opacos: nada los parsea.
serializeTranscript(t) escribe JSON estable y cómodo para diffs (indentación de 2 espacios y salto de línea final). parseTranscript(input) (un string JSON o un objeto ya parseado) valida la forma y la versión y, si no cuadran, lanza TranscriptFormatError. createTranscript(turns, meta?) construye una a mano.

Redacción

redactTranscript(transcript, options?) devuelve una copia redactada con marcadores estables numerados por orden de aparición, así que el resultado es determinista y redactar dos veces no cambia nada.
  • Los ids pasan a <user:N>, <chat:N> (chats de grupo) y <msg:N>. Los ids de usuario y de chat de 4 o más caracteres también se sustituyen dentro de los textos (los ids de mensaje, solo como campo completo).
  • Los nombres visibles pasan a <name:N>, en el campo y dentro de cualquier texto (así también se redacta una respuesta que saluda al usuario por su nombre). Al reproducir, el marcador se envía como push name, de modo que la respuesta sigue coincidiendo.
  • Los emails y teléfonos (7 o más dígitos) de los textos entrantes pasan a <email:N> / <phone:N>. Los textos salientes solo reciben los valores ya aprendidos del lado entrante (datos que el bot repite): precios, fechas o números de pedido que imprime el bot por su cuenta se quedan como están, porque la reproducción los compara con los valores reales.

Reproducir y comprobar

replayTranscript(transcript, createBot, options?) construye un bot nuevo sobre un MockProvider en memoria, le pasa los turnos entrantes uno a uno y compara lo que envía con los turnos salientes grabados. createBot recibe el provider y debe devolver el bot sin arrancarlo; la reproducción lo arranca y lo detiene.
assertTranscript lanza un TranscriptMismatchError (con .result) cuyo mensaje es el informe. replayTranscript, en cambio, devuelve un ReplayResult: { ok, diffs, expected, actual, report() }, donde actual es la conversación que produjo el bot. Cada TranscriptDiff tiene un kind ('missing', 'unexpected', 'mismatch' o 'timing'), el step (índice del turno entrante, -1 antes del primero), chatId, position, los turnos expected y actual, el primer field distinto ('kind', 'text', 'content.buttons.0.text', 't', 'id') y un message. formatReplayReport(transcript, diffs) genera el mismo informe. Tras cada turno entrante, la reproducción espera a que el bot haya enviado al menos tantos mensajes como los esperados y luego a que pase settleMs sin actividad, así detecta tanto las respuestas que faltan como las que sobran.

Opciones de tolerancia

REPLAY_MASKS trae máscaras listas: isoDate, time, uuid y number.

Gate de publicación (@botmesh/deployments)

Experimental: paquete privado@botmesh/deployments es privado (no está en npm) y su API puede cambiar. Es persistente: MemoryDeploymentStore es la referencia y KvDeploymentStore persiste sobre cualquier DurableKv (SQLite, PostgreSQL, Redis, archivo). Los gate runs se guardan en un log de auditoría.
DeploymentRegistry guarda versiones inmutables por tenant y bot con una única versión activa (publish, active, rollback, list). publish(tenantId, botId, version, artifact, { gate }) ejecuta el gate antes de activar la versión:
  • si pasa, la versión se guarda y se activa con deployment.gate = { passed: true, checkedAt, summary? };
  • si falla o lanza, publish rechaza con un DeploymentGateError (candidate, result) y no se guarda nada, así que la misma versión se puede corregir y volver a publicar.
Con gate, publish devuelve una Promise, y la versión queda reservada mientras el gate corre (un publish duplicado falla con deployment version already exists). Un gate es cualquier función (candidate) => boolean | GateResult, síncrona o async; GateResult es { ok, reason?, summary?, details? }. goldenTranscriptGate(options) reproduce transcripciones doradas contra el candidato y solo pasa si todas coinciden. Cada transcripción corre contra un bot nuevo.
Si falla, error.result.details.results lista { name, ok, diffs, report } por transcripción.

Ver también