Ir al contenido principal

Pruebas

Pruebas

AppKit incluye un kit de pruebas en @databricks/appkit/testing para que puedas probar un plugin, incluidas sus llamadas a herramientas entre plugins y sus respuestas en streaming, sin necesidad de un workspace de Databricks activo, credenciales ni acceso a la red. Las pruebas de plugins son rápidas y se ejecutan en CI, donde no hay ningún workspace disponible.

Objetivo

Ejercitar las rutas de código reales de un plugin contra un PluginContext real, simulando únicamente sus bordes externos. Esto cubre el registro de rutas, el despacho de herramientas entre plugins, la ejecución con alcance de usuario (on-behalf-of) y los tiempos de espera por llamada. No se reimplementa nada del contexto, de modo que una prueba no puede desviarse del comportamiento de producción.

El kit tiene tres puntos de entrada más un conjunto de utilidades de fixtures:

  • createTestApp({ plugins }) — arranca una app real y la invoca por HTTP real. Empieza por aquí.
  • createTestPluginContext() — construye un PluginContext real con los bordes simulados y lo adjunta a un plugin, sin arranque ni socket.
  • expectStream(...).toEmit(...) — verifica el orden de los tipos de evento que emite un stream.
  • FixturescreateMockRequest, createMockResponse, createMockWorkspaceClient, mockServiceContext y constructores de respuestas SQL.

El kit usa el vi de Vitest para sus mocks, por lo que vitest es una dependencia de pares (peer) opcional: ya lo tienes (estás escribiendo pruebas con Vitest) y el kit usa tu copia en lugar de empaquetar una segunda. Al ser opcional, no se instala en apps que nunca importan @databricks/appkit/testing: las instalaciones de producción quedan libres del framework de pruebas. Funciona con cualquier Vitest v3 o v4.

Probar tu plugin

createTestApp({ plugins }) arranca una app de AppKit real, con la configuración real de Express, las rutas y la validación de recursos, y te ofrece métodos para invocarla como lo haría un cliente:

import { createTestApp, expectStream } from "@databricks/appkit/testing";

test("my plugin answers a request", async () => {
  const app = await createTestApp({ plugins: [myPlugin()] });
  try {
    const res = await app.post("/api/my-plugin/thing", { body: { q: 1 }, obo: true });
    expect(res.status).toBe(200);
    await expectStream(res).toEmit("status", "result");
  } finally {
    await app.close();
  }
});

Sin workspace, sin credenciales, sin red. El entorno de pruebas fija un NODE_ENV distinto de desarrollo, enlaza un puerto efímero, instala un cliente de workspace simulado y mantiene la caché en memoria para que nada salga al exterior.

Las rutas son la ruta montada completa. El prefijo de un plugin es /api/ más el nombre de su manifiesto en kebab-case, de modo que un plugin llamado mySearch se sirve en /api/my-search/….

¿Qué entorno de pruebas usar?

createTestAppcreateTestPluginContext
Arranca la aplicaciónNo
Abre un socketSí (puerto efímero)No
Middleware de Express, manejador de erroresRealesNo intervienen
Validación de recursos y del entornoReal y estrictaNo interviene
Cliente del workspaceSimulado e inyectadoSimúlalo tú mismo con mockServiceContext
Requiere close()No
VelocidadRápido, pero con el coste de un socketEl más rápido

Usa createTestApp para probar de extremo a extremo el comportamiento HTTP de un plugin. Usa createTestPluginContext para probar unitariamente la integración interna: registro de rutas, despacho de herramientas, composición de tiempos de espera. Nombra los conjuntos de pruebas con entorno de pruebas como *.integration.test.ts, siguiendo la convención existente.

Simular lo que lee tu plugin

Declara las respuestas mediante rutas con puntos: "<service>.<method>" en la fachada del cliente de workspace de AppKit:

const app = await createTestApp({
  plugins: [myPlugin()],
  responses: {
    "jobs.getRun": { state: "TERMINATED", result_state: "SUCCESS" },
    "statementExecution.executeStatement": { status: { state: "SUCCEEDED" } },
    "apiClient.request": { results: [] },
  },
});

Un valor de función recibe los argumentos de la llamada, por lo que puedes definir el comportamiento según cada argumento o rechazar la promesa para probar una ruta de error. responses configura el mock integrado, así que pasarlo junto con tu propio client se rechaza en lugar de ignorarse en silencio: configura las respuestas en ese cliente. Cualquier ruta que no declares devuelve undefined en lugar de fallar; consulta Simular services de Databricks para conocer la contrapartida que implica.

Para las formas de las respuestas, guíate por los tipos de services del SDK de Databricks. El kit no los valida, así que una forma incorrecta falla en tu plugin, no en el simulador.

Con una sola app abierta, app.client es exactamente el objeto que tu handler resuelve en runtime —accesible dentro de un plugin mediante getExecutionContext().client—, de modo que puedes verificar las llamadas sobre él:

import { getMock } from "@databricks/appkit/testing";

expect(getMock(app.client, "jobs.getRun")).toHaveBeenCalledWith({ run_id: 42 });

getMock existe porque los accesores de la fachada están tipados según el SDK, por lo que expect(app.client.jobs.getRun).toHaveBeenCalled() no pasará la comprobación de tipos.

Solicitudes

app.get/post/put/patch/delete(path, options?) devuelven un Response nativo, por lo que expectStream se integra directamente sin necesidad de adaptadores.

  • body — un valor que no sea una cadena se codifica en JSON con content-type: application/json. Una cadena se envía tal cual.
  • headers — se fusionan en último lugar, así que prevalecen sobre cualquier valor definido por el entorno de pruebas.
  • obotrue para el usuario de prueba predeterminado, o { userId, token, email }. Es la misma forma abreviada que createMockRequest({ obo }), de modo que un handler que use asUser(req) resuelve esa identidad.
  • signal — se reenvía a fetch.

Desmontaje

El entorno de pruebas abre un socket, por lo que cada arranque requiere un close(). Libera el socket, ejecuta los hooks shutdown() de tu plugin, descarta los singletons de AppKit y restaura process.env a su estado previo al arranque. Es idempotente.

Es preferible usar await using, que cierra la aplicación al salir del ámbito incluso si la prueba lanza una excepción:

await using app = await createTestApp({ plugins: [myPlugin()] });
// se libera al salir del ámbito

try/finally también funciona, y es lo que necesitas si la aplicación debe seguir viva más allá de un bloque:

const app = await createTestApp({ plugins: [myPlugin()] });
try {
  // ...
} finally {
  await app.close();
}

Si omites el cierre, la app sigue activa —con el socket enlazado y sin restaurar los singletons ni process.env—, por lo que se rechazará el siguiente createTestApp (solo se permite una app a la vez).

Satisfacer los recursos declarados

El entorno de pruebas ejecuta el validador real en modo estricto, por lo que un plugin cuyo manifiesto requiere un recurso no arrancará si no se define su variable de entorno. Proporciónala con env:

// Lanza un error: el manifiesto requiere MY_WAREHOUSE_ID.
await createTestApp({ plugins: [myPlugin()] });

// Arranca correctamente.
await createTestApp({ plugins: [myPlugin()], env: { MY_WAREHOUSE_ID: "w-1" } });

Eso convierte «mi plugin declara sus recursos correctamente» en una afirmación real. env se restaura en close().

Lo que esto no comprueba

El entorno de pruebas valida que las variables de entorno de los recursos requeridos estén presentes. No valida los valores de configuración frente al config.schema de tu manifiesto: todavía no existe un validador en runtime para eso. Una prueba que arranca correctamente te confirma que tus declaraciones de recursos y el entorno están bien conectados, pero no dice nada sobre si los valores de configuración están bien formados.

Otras opciones

  • server: false — sin socket. La configuración, la validación y el desmontaje del plugin se siguen ejecutando; los métodos de solicitud lanzan una excepción si se invocan. Útil cuando solo quieres comprobar que un plugin arranca.
  • client — proporciona tu propio cliente de workspace en lugar del cliente falso integrado. En ese caso, te haces cargo de su currentUser.me(): AppKit lee currentUser.id durante el arranque y no puede iniciarse sin él.
  • nodeEnv — su valor predeterminado es "test". "development" se rechaza: el modo de desarrollo enruta el puerto efímero del entorno de pruebas a través de get-port, que lanza una excepción con el puerto 0, y además levanta un servidor Vite real y relaja la validación.
  • cache — por defecto es en memoria. Sobrescribirlo es justo lo que permitiría que la caché salga a la red, así que déjalo tal cual salvo que ese sea el objetivo de la prueba.

createTestPluginContext()

PluginContext es el mediador que AppKit pasa a cada plugin: almacena rutas en búfer, hace seguimiento de los proveedores de herramientas y ejecuta llamadas a herramientas entre plugins con alcance de usuario y un tiempo de espera. createTestPluginContext() devuelve el contexto real con tres puntos de contacto simulados:

Punto de contactoCómo se simula
TelemetríaUn proveedor mock sin operaciones: no se necesita ningún pipeline de OpenTelemetry.
Proveedores de herramientasSimulaciones registradas mediante el registerToolProvider real, indexadas por plugin y luego por nombre de herramienta.
RutasLos addRoute/addMiddleware reales se envuelven para registrar lo que declara un plugin.

Como el contexto es real, executeTool sigue resolviendo el alcance del usuario mediante asUser(req) y sigue componiendo la señal de cancelación a partir de tu tiempo de espera, de modo que esas rutas de código quedan realmente cubiertas por las pruebas.

Registrar respuestas falsas de herramientas

Pasa respuestas predefinidas indexadas por nombre de plugin y, luego, por nombre de herramienta. Una respuesta puede ser un valor estático o una función de los argumentos de la llamada y la señal de cancelación compuesta:

import { createTestPluginContext } from "@databricks/appkit/testing";

const mock = createTestPluginContext({
  analytics: {
    // respuesta estática
    top_users: [{ user: "alice", events: 42 }],
    // respuesta como función: valida los argumentos o simula trabajo lento o abortado
    query: (args, signal) => runFakeQuery(args, signal),
  },
});

Adjuntar a un plugin

attach() conecta el contexto a un plugin tal como se hace en producción: inicializa una caché en memoria (si AppKit no ha creado una ya) y luego llama a attachContext del plugin, que reconstruye la telemetría y cambia isReady a true. Espera su resolución antes de ejecutar cualquier handler que lea this.context, this.cache o que dependa de isReady:

const plugin = new MyAgentPlugin({});
await mock.attach(plugin);

Instancia directamente la clase del plugin (new MyAgentPlugin(...)). Las fábricas analytics() / agents() que pasas a createApp devuelven un descriptor para que la aplicación lo construya; en una prueba unitaria lo que necesitas es la instancia.

El cliente del workspace y el stub de on-behalf-of también son globales al proceso, no por aplicación: ServiceContext mantiene un único cliente y el doble de createUserContext es un único espía. Por eso, createTestApp solo permite una aplicación abierta a la vez y lanza un error si arrancas una segunda antes de cerrar la primera: con dos abiertas, el client y las responses de la segunda no llegarían a los handlers, y cerrar cualquiera de ellas eliminaría el doble de OBO compartido de la otra. Vitest aísla los archivos de prueba en workers separados, de modo que esto solo afecta a las aplicaciones dentro de un mismo archivo. Una consecuencia que conviene tener presente: un describe que mantiene una aplicación abierta en beforeAll no puede contener una prueba que arranque la suya.

La caché que inicializa attach() es un singleton global al proceso: CacheManager se inicializa una sola vez por proceso de prueba y se reutiliza. Vitest aísla los archivos de prueba en workers separados, así que las cachés nunca se filtran entre archivos, pero las pruebas dentro de un mismo archivo la comparten. Si una prueba llena la caché y otra posterior del mismo archivo no debe verla, límpiala entre pruebas con resetTestCache():

import { resetTestCache } from "@databricks/appkit/testing";

beforeEach(async () => {
  await resetTestCache(); // no hace nada si la caché aún no se ha inicializado
});

También resulta útil dentro de una misma prueba: borra la caché para forzar un fallo y luego comprueba que la siguiente llamada sea un acierto.

Inspeccionar lo que ocurrió

El objeto devuelto expone views en vivo que puedes consultar una vez ejecutada la acción bajo prueba:

await someHandler(req, res);

// Cada envío de herramienta entre plugins, en orden.
expect(mock.toolCalls[0]).toMatchObject({
  plugin: "analytics",
  tool: "query",
  asUser: true, // demuestra que se ejecutó la ruta en nombre del usuario
});

// Cada ruta que registró el plugin (handlers en crudo, antes de envolverlos).
expect(mock.routes).toContainEqual(
  expect.objectContaining({ method: "post", path: "/invocations" }),
);

// El proveedor de telemetría inyectado registra los spans propios del contexto, es decir,
// el span que PluginContext.executeTool abre en torno a cada llamada de herramienta entre plugins.
expect(mock.telemetry.getTracer().startActiveSpan).toHaveBeenCalled();

mock.telemetry se inyecta en el PluginContext, de modo que captura los spans que abre el contexto (en particular, executeTool). No es la telemetría propia del plugin: attachContext reconstruye this.telemetry a partir del TelemetryManager real, por lo que los spans que un plugin abre internamente no llegan a mock.telemetry.

RecordedToolCall.asUser es el campo que conviene verificar en las llamadas entre plugins: como el asUser simulado aplica la misma precondición de token que el Plugin.asUser real, un despacho que registra asUser: true (con userId definido) sí resolvió el ámbito de usuario del llamador, mientras que una solicitud sin x-forwarded-access-token se rechaza — la distinción de OBO que los stubs silenciosos de { executeTool } no pueden verificar. Comprueba ambos casos: que una solicitud bien formada registre el userId esperado y que una sin token lance un error.

La versión simulada replica la precondición de token de asUser, pero no su marcador interno de telemetría en modo de desarrollo: con NODE_ENV=development, el Plugin.asUser real omite la suplantación y activa una marca OTel isDevOboFallback() que la simulación no reproduce. Verifica el OBO mediante los campos registrados asUser/userId en lugar de isDevOboFallback().

expectStream(...)

Los plugins de AppKit emiten Server-Sent Events. expectStream consume un flujo y comprueba la secuencia de tipos de eventos que emite. Acepta un iterable asíncrono (el run() de un adaptador de agente), un array simple de eventos, una Response SSE (o una promesa que la resuelva) cuyo cuerpo analiza, o un createMockResponse() cuyas escrituras capturadas reproduce.

import { expectStream } from "@databricks/appkit/testing";

// Coincidencia de subsecuencia en orden: se ignoran los eventos intercalados (heartbeats, deltas).
await expectStream(agent.adapter.run(input)).toEmit("tool_call", "message_delta");

// Coincidencia exacta: la forma completa del stream, en orden y sin nada más.
await expectStream(events).toEmitExactly("warehouse_status", "result");

// O recopilar sin hacer aserciones.
const types = await expectStream(res).collectTypes();

Verificar la ruta de streaming de un plugin

La mayoría de los plugins emiten SSE desde un handler de ruta (res.write(...)), no desde un generador simple. createMockResponse() captura esas escrituras y expectStream las vuelve a leer directamente: ejecuta el handler real y luego verifica:

import { createMockRequest, createMockResponse, expectStream } from "@databricks/appkit/testing";

const res = createMockResponse();
await plugin._handleStream(createMockRequest({ obo: true }), res);

// El mock capturó el SSE que escribió el handler; expectStream lo parsea.
await expectStream(res).toEmit("status", "result");

expectStream(res) y expectStream(res.sseResponse()) son equivalentes; este último te devuelve el Response en crudo por si lo necesitas. No pases el cuerpo del SSE como cadena: una cadena es un iterable de caracteres, así que expectStream la rechaza y te remite a sseResponse() en lugar de emitir un «evento» por carácter.

toEmit comprueba que los tipos esperados aparezcan en orden, pero tolera otros eventos antes, entre o después de ellos, que es justo lo que interesa en flujos que intercalan eventos de control como latidos o metadatos. Usa toEmitExactly cuando la forma del flujo esté totalmente determinada.

expectStream almacena en búfer toda la fuente antes de hacer las aserciones, de modo que un flujo que nunca termina se quedaría colgado hasta agotar el tiempo de espera del propio ejecutor de pruebas. Pasa { timeout } para fallar cuanto antes con un error claro:

await expectStream(handler.stream(req), { timeout: 1000 }).toEmit("result");

Fixtures

AppKit tiene dos contextos, y cada uno se simula con herramientas distintas. PluginContext es el mediador entre plugins: gestiona rutas, el despacho de herramientas y el alcance por usuario; createTestPluginContext() te entrega el objeto real con los bordes simulados. ServiceContext es el plano de datos: resuelve el cliente del workspace, el service principal y el ID del warehouse a los que los plugins acceden mediante getWorkspaceClient().

El kit ahora cubre ambos. createTestApp simula el plano de datos por ti inyectando un cliente de workspace simulado en el punto de unión real; por debajo, mockServiceContext espía el singleton directamente, y createMockWorkspaceClient construye el cliente que instala cualquiera de los dos.

El kit reexporta los fixtures de petición/respuesta/contexto que AppKit usa internamente:

  • createMockRequest(overrides?) / createMockResponse() — dobles de petición/respuesta de Express, incluidas las banderas de streaming (headersSent, writableEnded). Pasa obo: true (o obo: { userId, token, email }) para establecer las cabeceras de identidad reenviada que requiere asUser, en lugar de añadirlas a mano. createMockResponse() también captura todo lo que escribe un handler; pásalo a expectStream (o llama a sseResponse()) para hacer aserciones sobre el SSE de una ruta de streaming. (Los plugins resuelven el cliente del workspace mediante getWorkspaceClient(), no a través de la petición: usa mockServiceContext para controlarlo).

  • mockServiceContext(options?) — espía el singleton ServiceContext para que el código que resuelve el service principal o un contexto de usuario obtenga dobles de prueba. Llámalo en beforeEach y llama al restore() devuelto en afterEach.

  • useServiceContextMock(options?) — lo mismo, en una línea: registra por ti la instalación en beforeEach y la restauración en afterEach. Llámalo al inicio de un bloque describe (no dentro de un test) y lee el handle vivo .current desde dentro de un test:

    describe("my plugin", () => {
      const ctx = useServiceContextMock();
      test("...", async () => {
        await handler(createMockRequest({ obo: true }), res);
        expect(ctx.current.createUserContextSpy).toHaveBeenCalled();
      });
    });
  • createSuccessfulSQLResponse(rows, columns) / createFailedSQLResponse(message) — construyen respuestas de sentencias de SQL Warehouse.

  • setupDatabricksEnv(overrides?) — establece DATABRICKS_HOST / DATABRICKS_WAREHOUSE_ID con valores de prueba.

  • resetTestCache() — limpia el singleton de caché compartida entre tests (o dentro de ellos); no hace nada si la caché aún no se ha inicializado. El kit usa ambas palabras de forma deliberada: un mock registra las llamadas para que puedas hacer aserciones sobre ellas (createMockWorkspaceClient, mockServiceContext), mientras que un fake ocupa el lugar y simplemente funciona (FakeProvider, FakeToolResponse).

  • createTestPlugin(factory, config?) — instancia un plugin a partir de su factory con la misma fusión de configuración que aplica AppKit. Consulta Ejemplo completo.

  • getListeningPort(server) — espera a que un servidor termine de enlazarse y devuelve el puerto que le tocó. createTestApp lo hace por ti; recurre a él cuando inicies un servidor tú mismo con port: 0.

Simular servicios de Databricks

Todo el trabajo real de cada plugin principal pasa por getWorkspaceClient(). createMockWorkspaceClient() simula toda esa superficie, de modo que un plugin que use jobs, genie, servingEndpoints o files se puede probar sin tener que construir a mano un cliente anidado:

import { createMockWorkspaceClient, getMock } from "@databricks/appkit/testing";

const client = createMockWorkspaceClient({
  responses: { "jobs.getRun": { state: "TERMINATED" } },
  config: { host: "https://my-test-host.example.com" },
});

await client.jobs.getRun({ run_id: 1 });        // → { state: "TERMINATED" }
await client.genie.getMessage({ id: "m-1" });   // → undefined, no lanza error

createTestApp instala uno de estos por ti, así que recurre a él directamente solo cuando estés ejecutando un plugin mediante createTestPluginContext o mockServiceContext.

Cómo funciona y qué esperar:

  • La fachada está tipada, por lo que client.jbos es un error de compilación. La interfaz pertenece a AppKit, así que se trata de un conjunto cerrado y no de una persecución interminable del SDK.
  • Cada service es un proxy que genera un mock memoizado por método. client.jobs.getRun === client.jobs.getRun, de modo que las aserciones sobre llamadas son estables, y toLegacyWorkspaceClient() comparte las mismas funciones: una sola entrada de responses cubre ambas vistas.
  • config.host es una cadena real (no un mock), porque AppKit construye las URL a partir de ella. apiClient.userAgent() es síncrono por la misma razón, y apiClient.request resuelve {} para que desestructurar su resultado no lance un error.
  • Incluye valores predeterminados razonables: las sentencias SQL se ejecutan correctamente, los warehouses informan RUNNING y currentUser.me() devuelve un usuario de servicio. Pasa defaults: false para definirlo todo tú mismo.
Los métodos no declarados devuelven undefined

Un método no declarado resuelve undefined en lugar de lanzar un error. Esa es la idea —tu plugin sobrevive al tocar services que a la prueba no le importan—, pero significa que una llamada cuya respuesta olvidaste declarar devuelve undefined en silencio en vez de fallar de forma evidente, con lo que una prueba puede pasar por el motivo equivocado.

Pasa strict: true para convertir ese silencio en un fallo: una llamada a una ruta sin respuesta declarada lanza un error, indicando la ruta, en lugar de resolver undefined. Los valores predeterminados incorporados siguen contando como declarados, así que el arranque del entorno de pruebas funciona igual.

const app = await createTestApp({ plugins: [myPlugin()], strict: true });
// un handler que llama a una ruta no declarada ahora hace fallar la solicitud

TypeScript cubre más de esto de lo que cabría esperar: como cada descriptor de acceso está tipado contra la propia clase de servicio del SDK, tanto un servicio mal escrito (client.jbos) como un método mal escrito (client.jobs.getRunz) provocan errores de compilación. El punto ciego es un método real sin respuesta declarada, y cualquier llamada que esquive los tipos mediante un cast.

Hay otra divergencia: los métodos de un servicio se crean al acceder a ellos, por lo que son invocables pero no enumerables. typeof client.jobs.getRun es "function", pero 'getRun' in client.jobs es false y Object.keys(client.jobs) es []. Por tanto, el código de un plugin que detecte capacidades con in o que use reflexión sobre un servicio tomará una rama distinta a la que tomaría en producción. Esto es deliberado: exponer esas claves haría que util.inspect sondeara cada una y generara un mock por sondeo, que es precisamente la recursión descontrolada que evitan las trampas por defecto.

Aparte, createLakebasePool({ workspaceClient }) construirá un pool cuya devolución de llamada de contraseña se resuelve en un mock: el pool existe, pero no puede conectarse. Una prueba de Lakebase necesita una base de datos real o un pool falso creado a propósito, no esto.

Ejemplo completo

Si se trata de un plugin que escribiste tú, instancia la clase directamente con new. Las funciones de fábrica analytics() / agents() que le pasas a createApp devuelven un descriptor para que la app lo construya, no una instancia.

Cuando necesites una instancia de alguna de esas fábricas, usa createTestPlugin en lugar de acceder a ella a través del descriptor:

import { createTestPlugin } from "@databricks/appkit/testing";

const plugin = createTestPlugin(genie, { spaceId: "s-1" });

// Así no — se salta DEFAULT_CONFIG y omite `name`, por lo que la instancia queda
// configurada de forma distinta a la que se construye en producción:
//   const plugin = new (genie({}).plugin)({ spaceId: "s-1" });

createTestPlugin aplica la misma fusión que hace AppKit al registrar: DEFAULT_CONFIG, luego tu configuración y, por último, el name del manifiesto. Solo sirve para esta ruta de pruebas unitarias: createTestApp recibe descriptores y construye las instancias por su cuenta.

import { Plugin, type PluginManifest } from "@databricks/appkit";
import { expectStream, createMockRequest, createTestPluginContext } from "@databricks/appkit/testing";
import { describe, expect, test } from "vitest";

// Un plugin sencillo que registra una ruta y transmite dos eventos.
class GreeterPlugin extends Plugin {
  static manifest = {
    name: "greeter",
    displayName: "Greeter",
    description: "Example plugin",
    resources: { required: [], optional: [] },
  } as PluginManifest<"greeter">;

  async setup() {
    this.context?.addRoute("get", "/hello", (_req, res) => res.end());
  }

  async *greet(name: string) {
    yield { type: "greeting_start", name };
    yield { type: "greeting_end", message: `Hello, ${name}!` };
  }
}

describe("greeter plugin", () => {
  test("registers its route through the context", async () => {
    const mock = createTestPluginContext();
    const plugin = new GreeterPlugin({});

    await mock.attach(plugin);
    await plugin.setup();

    expect(mock.routes).toContainEqual(
      expect.objectContaining({ method: "get", path: "/hello" }),
    );
  });

  test("streams events in order", async () => {
    const plugin = new GreeterPlugin({});
    await expectStream(plugin.greet("world")).toEmit(
      "greeting_start",
      "greeting_end",
    );
  });
});

Para probar un plugin que envía llamadas a herramientas entre plugins, registra proveedores falsos y haz aserciones sobre mock.toolCalls —incluido asUser, que confirma que se ejecutó la ruta en nombre del usuario:

const mock = createTestPluginContext({ analytics: { query: [{ n: 1 }] } });
const plugin = new MyAgentPlugin({});
await mock.attach(plugin);

// `obo` establece los encabezados de identidad reenviados que requiere `asUser`; sin ellos,
// el despacho rechazaría (correctamente) con "Missing user token".
const req = createMockRequest({ obo: true });
await plugin.runSomethingThatCallsAnalytics(req);

expect(mock.toolCalls[0]).toMatchObject({
  plugin: "analytics",
  tool: "query",
  asUser: true,
});

Consulta también

Databricks Developer Hub

¿Todo listo para lanzar tu próxima aplicación basada en agentes en minutos?

Leer la documentación