Saltar al contenido principal

Configuración

Importa las utilidades de testing desde @jelou/functions/testing:
import {
  createMockContext,
  createMockCronContext,
  createMockEventContext,
  createMockRequest,
  createMockPlatformRequest,
  createMockJelouClient,
  createMockMemoryClient,
} from "@jelou/functions/testing";

createMockContext(overrides?)

Crea un context con valores por defecto sensatos para testing.
const ctx = createMockContext();

const ctx2 = createMockContext({
  company: { id: 42, name: "Tienda ABC" },
  user: { id: 99, names: "María García" },
  bot: { id: "bot-123", name: "Bot de Soporte", channel: "whatsapp" },
  env: {
    get: (key) => key === "CRM_API_KEY" ? "sk-test-123" : undefined,
    has: (key) => key === "CRM_API_KEY",
    toObject: () => ({ CRM_API_KEY: "sk-test-123" }),
  },
});

createMockCronContext(expression, overrides?, cronName?)

Crea un context con isCron: true y trigger.type: "cron".
const ctx = createMockCronContext("0 9 * * *");
// ctx.isCron === true
// ctx.trigger === { type: "cron", cron: "0 9 * * *" }
// ctx.isHttp === false

const ctx2 = createMockCronContext("0 9 * * *", {}, "recordatorio-diario");
// ctx2.trigger === { type: "cron", cron: "0 9 * * *", cronName: "recordatorio-diario" }

createMockEventContext(eventName, overrides?)

Crea un context con isEvent: true y trigger.type: "event".
const ctx = createMockEventContext("pago.completado");
// ctx.isEvent === true
// ctx.trigger === { type: "event", event: "pago.completado" }

createMockRequest(body?, options?)

Crea un objeto Web Request estándar.
const req = createMockRequest();
// GET http://localhost:8000/

const req2 = createMockRequest({ telefono: "593987654321" });
// POST con body JSON, Content-Type: application/json

const req3 = createMockRequest(null, {
  method: "PUT",
  url: "https://consultar-cliente.fn.jelou.ai/clientes/42",
  headers: { "x-api-key": "sk-test-123" },
});

createMockPlatformRequest(token, body?, options?)

Crea un Request con header X-Jelou-Token. Útil para testing de funciones que reciben peticiones autenticadas con runtime tokens.
const req = createMockPlatformRequest("jfn_rt_test123");
// GET con X-Jelou-Token: jfn_rt_test123

const req2 = createMockPlatformRequest("jfn_rt_test123", { query: "test" });
// POST con body JSON + X-Jelou-Token: jfn_rt_test123

const req3 = createMockPlatformRequest("jfn_rt_test123", null, {
  method: "PUT",
  url: "https://mi-funcion.fn.jelou.ai/admin",
  headers: { "x-custom": "value" },
});
// PUT con X-Jelou-Token + headers adicionales
createMockPlatformRequest es un wrapper sobre createMockRequest que agrega el header x-jelou-token automáticamente. Es equivalente a:
createMockRequest(body, { ...options, headers: { ...options?.headers, "x-jelou-token": token } })

createMockJelouClient(options?)

Crea un mock del cliente de mensajería con grabación de llamadas.
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });

await ctx.jelou.send({ type: "text", to: "+593987654321", text: "Hola" });

// Inspeccionar llamadas
mockJelou.calls.length;          // 1
mockJelou.calls[0].method;       // "send"
mockJelou.calls[0].args.type;    // "text"
mockJelou.calls[0].timestamp;    // Date.now()

Resultados personalizados

const mockJelou = createMockJelouClient({
  sendResult: { messageId: "msg-custom-123" },
  templateResult: [{ id: "tmpl-1", destination: "+593987654321" }],
});

Reset entre tests

mockJelou.reset(); // limpia las llamadas grabadas

createMockMemoryClient(options?)

Crea un mock del cliente de memoria con un store in-memory.
const mockMemory = createMockMemoryClient({
  store: { paso: "inicio", intentos: 0 },
});
const ctx = createMockContext({ memory: mockMemory });

const paso = await ctx.memory.get("paso", "desconocido"); // "inicio"
await ctx.memory.set("paso", "confirmacion", 3600);

Inspeccionar llamadas

mockMemory.calls.length;         // 2 (get + set)
mockMemory.calls[1].method;      // "set"
mockMemory.calls[1].args;        // { key: "paso", value: "confirmacion", ttl: 3600 }

Reset entre tests

mockMemory.reset(); // limpia las llamadas y restaura el store inicial

Ejemplo completo

index.test.ts
import { assertEquals } from "jsr:@std/assert";
import { define, z } from "@jelou/functions";
import {
  createMockContext,
  createMockCronContext,
  createMockRequest,
  createMockPlatformRequest,
  createMockJelouClient,
  createMockMemoryClient,
} from "@jelou/functions/testing";

const fn = define({
  name: "consultar-cliente",
  description: "Busca información de un cliente por teléfono",
  input: z.object({
    telefono: z.string().min(10),
  }),
  output: z.object({
    nombre: z.string(),
    plan: z.string(),
    companyId: z.number(),
  }),
  handler: async (input, ctx) => {
    return {
      nombre: "María García",
      plan: "Premium",
      companyId: ctx.company.id,
    };
  },
});

Deno.test("retorna info del cliente con el company ID correcto", async () => {
  const ctx = createMockContext({
    company: { id: 42, name: "Tienda ABC" },
    bot: { id: "bot-123", name: "Bot Soporte", channel: "whatsapp" },
  });
  const req = createMockRequest({ telefono: "593987654321" });

  const result = await fn.handler(
    { telefono: "593987654321" },
    ctx,
    req,
  );

  assertEquals(result.nombre, "María García");
  assertEquals(result.plan, "Premium");
  assertEquals(result.companyId, 42);
});

Deno.test("cron skip cuando no es trigger cron", async () => {
  const cronFn = define({
    name: "limpieza-diaria",
    input: z.object({}),
    config: {
      cron: [{ expression: "0 3 * * *" }],
    },
    handler: async (_input, ctx) => {
      if (!ctx.isCron) return { skipped: true };
      return { cleaned: true };
    },
  });

  const httpCtx = createMockContext();
  const req = createMockRequest();
  const result = await cronFn.handler({}, httpCtx, req);
  assertEquals(result, { skipped: true });

  const cronCtx = createMockCronContext("0 3 * * *");
  const result2 = await cronFn.handler({}, cronCtx, req);
  assertEquals(result2, { cleaned: true });
});

Deno.test("mock de mensajería graba llamadas", async () => {
  const mockJelou = createMockJelouClient();
  const ctx = createMockContext({ jelou: mockJelou });

  await ctx.jelou.send({
    type: "text",
    to: "+593987654321",
    text: "Tu pedido está listo",
  });

  assertEquals(mockJelou.calls.length, 1);
  assertEquals(mockJelou.calls[0].method, "send");
  assertEquals(mockJelou.calls[0].args.type, "text");
});

Deno.test("mock de memoria persiste valores", async () => {
  const mockMemory = createMockMemoryClient({
    store: { paso: "inicio" },
  });
  const ctx = createMockContext({ memory: mockMemory });

  const paso = await ctx.memory.get("paso", "desconocido");
  assertEquals(paso, "inicio");

  await ctx.memory.set("paso", "confirmacion", 3600);
  const paso2 = await ctx.memory.get("paso", "desconocido");
  assertEquals(paso2, "confirmacion");
});

Deno.test("platform request incluye header de token", () => {
  const req = createMockPlatformRequest("jfn_rt_test123", { action: "test" });
  assertEquals(req.headers.get("x-jelou-token"), "jfn_rt_test123");
  assertEquals(req.method, "POST");
});

createMockApp(tools, config?)

Crea un EdgeApp mock para testing de funciones multi-tool.
import { assertEquals } from "jsr:@std/assert";
import { createMockApp, createMockContext, createMockRequest } from "@jelou/functions/testing";
import { define, z } from "@jelou/functions";

const myApp = createMockApp({
  enviarEmail: define({
    description: "Envía un correo electrónico",
    input: z.object({ to: z.string() }),
    handler: async (input) => ({ sent: true }),
  }),
  leerBandeja: define({
    description: "Lee los mensajes de la bandeja de entrada",
    input: z.object({}),
    handler: async () => ({ messages: [] }),
  }),
});

Deno.test("app tiene los tools correctos", () => {
  assertEquals(Object.keys(myApp.tools), ["enviarEmail", "leerBandeja"]);
  assertEquals(myApp.__jelou_edge_app, true);
});

Deno.test("cada tool funciona independientemente", async () => {
  const ctx = createMockContext();
  const req = createMockRequest({ to: "[email protected]" });
  const result = await myApp.tools.enviarEmail.handler(
    { to: "[email protected]" },
    ctx,
    req,
  );
  assertEquals(result, { sent: true });
});
Ejecuta los tests con Deno:
deno test