Ir al contenido

Pruebas de complementos

Referencia de utilidades de prueba, patrones y aplicación de reglas de lint para complementos de OpenClaw.

Importar: openclaw/plugin-sdk/testing

La subruta de pruebas exporta un conjunto limitado de auxiliares para los autores de complementos:

import { installCommonResolveTargetErrorCases, shouldAckReaction, removeAckReactionAfterReply } from "openclaw/plugin-sdk/testing";
ExportaciónPropósito
installCommonResolveTargetErrorCasesCasos de prueba compartidos para el manejo de errores de resolución de objetivos
shouldAckReactionVerificar si un canal debe añadir una reacción de acknowledgment
removeAckReactionAfterReplyEliminar la reacción de acknowledgment después de la entrega de la respuesta

La subruta de pruebas también reexporta tipos útiles en los archivos de prueba:

import type { ChannelAccountSnapshot, ChannelGatewayContext, OpenClawConfig, PluginRuntime, RuntimeEnv, MockFn } from "openclaw/plugin-sdk/testing";

Usa installCommonResolveTargetErrorCases para añadir casos de error estándar para la resolución de objetivos del canal:

import { describe } from "vitest";
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/testing";
describe("my-channel target resolution", () => {
installCommonResolveTargetErrorCases({
resolveTarget: ({ to, mode, allowFrom }) => {
// Your channel's target resolution logic
return myChannelResolveTarget({ to, mode, allowFrom });
},
implicitAllowFrom: ["user1", "user2"],
});
// Add channel-specific test cases
it("should resolve @username targets", () => {
// ...
});
});
import { describe, it, expect, vi } from "vitest";
describe("my-channel plugin", () => {
it("should resolve account from config", () => {
const cfg = {
channels: {
"my-channel": {
token: "test-token",
allowFrom: ["user1"],
},
},
};
const account = myPlugin.setup.resolveAccount(cfg, undefined);
expect(account.token).toBe("test-token");
});
it("should inspect account without materializing secrets", () => {
const cfg = {
channels: {
"my-channel": { token: "test-token" },
},
};
const inspection = myPlugin.setup.inspectAccount(cfg, undefined);
expect(inspection.configured).toBe(true);
expect(inspection.tokenStatus).toBe("available");
// No token value exposed
expect(inspection).not.toHaveProperty("token");
});
});

Prueba unitaria de un complemento de proveedor

Sección titulada «Prueba unitaria de un complemento de proveedor»
import { describe, it, expect } from "vitest";
describe("my-provider plugin", () => {
it("should resolve dynamic models", () => {
const model = myProvider.resolveDynamicModel({
modelId: "custom-model-v2",
// ... context
});
expect(model.id).toBe("custom-model-v2");
expect(model.provider).toBe("my-provider");
expect(model.api).toBe("openai-completions");
});
it("should return catalog when API key is available", async () => {
const result = await myProvider.catalog.run({
resolveProviderApiKey: () => ({ apiKey: "test-key" }),
// ... context
});
expect(result?.provider?.models).toHaveLength(2);
});
});

Simulación del tiempo de ejecución del complemento

Sección titulada «Simulación del tiempo de ejecución del complemento»

Para el código que usa createPluginRuntimeStore, simula el tiempo de ejecución en las pruebas:

import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
const store = createPluginRuntimeStore<PluginRuntime>("test runtime not set");
// In test setup
const mockRuntime = {
agent: {
resolveAgentDir: vi.fn().mockReturnValue("/tmp/agent"),
// ... other mocks
},
config: {
loadConfig: vi.fn(),
writeConfigFile: vi.fn(),
},
// ... other namespaces
} as unknown as PluginRuntime;
store.setRuntime(mockRuntime);
// After tests
store.clearRuntime();

Prefiera las simulaciones por instancia sobre la mutación del prototipo:

// Preferred: per-instance stub
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });
// Avoid: prototype mutation
// MyChannelClient.prototype.sendMessage = vi.fn();

Pruebas de contrato (complementos en el repositorio)

Sección titulada «Pruebas de contrato (complementos en el repositorio)»

Los complementos integrados tienen pruebas de contrato que verifican la propiedad del registro:

Ventana de terminal
pnpm test -- src/plugins/contracts/

Estas pruebas afirman:

  • Qué complementos registran qué proveedores
  • Qué complementos registran qué proveedores de voz
  • Corrección de la forma del registro
  • Cumplimiento del contrato de tiempo de ejecución

Para un complemento específico:

Ventana de terminal
pnpm test -- <bundled-plugin-root>/my-channel/

Solo para pruebas de contrato:

Ventana de terminal
pnpm test -- src/plugins/contracts/shape.contract.test.ts
pnpm test -- src/plugins/contracts/auth.contract.test.ts
pnpm test -- src/plugins/contracts/runtime.contract.test.ts

Aplicación de reglas de lint (complementos en el repositorio)

Sección titulada «Aplicación de reglas de lint (complementos en el repositorio)»

Tres reglas son aplicadas por pnpm check para los complementos en el repositorio:

  1. Sin importaciones raíz monolíticas — se rechaza el barril raíz openclaw/plugin-sdk
  2. Sin importaciones directas de src/ — los complementos no pueden importar ../../src/ directamente
  3. No hay autoimportaciones — los complementos no pueden importar su propia sub-ruta plugin-sdk/<name>

Los complementos externos no están sujetos a estas reglas de linting, pero se recomienda seguir los mismos patrones.

OpenClaw usa Vitest con umbrales de cobertura de V8. Para las pruebas de complementos:

Ventana de terminal
# Run all tests
pnpm test
# Run specific plugin tests
pnpm test -- <bundled-plugin-root>/my-channel/src/channel.test.ts
# Run with a specific test name filter
pnpm test -- <bundled-plugin-root>/my-channel/ -t "resolves account"
# Run with coverage
pnpm test:coverage

Si las ejecuciones locales causan presión de memoria:

Ventana de terminal
OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test