test: micro-optimize slow suites and CLI command setup

This commit is contained in:
Peter Steinberger
2026-03-02 23:00:42 +00:00
parent ba5ae5b4f1
commit 2287d1ec13
7 changed files with 259 additions and 164 deletions

View File

@@ -1,5 +1,5 @@
import { Command } from "commander";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.js";
/**
@@ -61,27 +61,24 @@ function setSnapshotOnce(snapshot: ConfigFileSnapshot) {
}
let registerConfigCli: typeof import("./config-cli.js").registerConfigCli;
let sharedProgram: Command;
async function runConfigCommand(args: string[]) {
const program = new Command();
program.exitOverride();
registerConfigCli(program);
await program.parseAsync(args, { from: "user" });
await sharedProgram.parseAsync(args, { from: "user" });
}
describe("config cli", () => {
beforeAll(async () => {
({ registerConfigCli } = await import("./config-cli.js"));
sharedProgram = new Command();
sharedProgram.exitOverride();
registerConfigCli(sharedProgram);
});
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("config set - issue #6070", () => {
it("preserves existing config keys when setting a new value", async () => {
const resolved: OpenClawConfig = {

View File

@@ -1,6 +1,5 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../../test-utils/command-runner.js";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
const callGatewayCli = vi.fn(async (_method: string, _opts: unknown, _params?: unknown) => ({
@@ -113,9 +112,13 @@ vi.mock("./discover.js", () => ({
describe("gateway register option collisions", () => {
let registerGatewayCli: typeof import("./register.js").registerGatewayCli;
let sharedProgram: Command;
beforeAll(async () => {
({ registerGatewayCli } = await import("./register.js"));
sharedProgram = new Command();
sharedProgram.exitOverride();
registerGatewayCli(sharedProgram);
});
beforeEach(() => {
@@ -125,9 +128,8 @@ describe("gateway register option collisions", () => {
});
it("forwards --token to gateway call when parent and child option names collide", async () => {
await runRegisteredCli({
register: registerGatewayCli as (program: Command) => void,
argv: ["gateway", "call", "health", "--token", "tok_call", "--json"],
await sharedProgram.parseAsync(["gateway", "call", "health", "--token", "tok_call", "--json"], {
from: "user",
});
expect(callGatewayCli).toHaveBeenCalledWith(
@@ -140,9 +142,8 @@ describe("gateway register option collisions", () => {
});
it("forwards --token to gateway probe when parent and child option names collide", async () => {
await runRegisteredCli({
register: registerGatewayCli as (program: Command) => void,
argv: ["gateway", "probe", "--token", "tok_probe", "--json"],
await sharedProgram.parseAsync(["gateway", "probe", "--token", "tok_probe", "--json"], {
from: "user",
});
expect(gatewayStatusCommand).toHaveBeenCalledWith(

View File

@@ -1,6 +1,5 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runRegisteredCli } from "../../test-utils/command-runner.js";
import { createCliRuntimeCapture } from "../test-runtime-capture.js";
const startGatewayServer = vi.fn(async (_port: number, _opts?: unknown) => ({
@@ -93,9 +92,14 @@ vi.mock("./run-loop.js", () => ({
describe("gateway run option collisions", () => {
let addGatewayRunCommand: typeof import("./run.js").addGatewayRunCommand;
let sharedProgram: Command;
beforeAll(async () => {
({ addGatewayRunCommand } = await import("./run.js"));
sharedProgram = new Command();
sharedProgram.exitOverride();
const gateway = addGatewayRunCommand(sharedProgram.command("gateway"));
addGatewayRunCommand(gateway.command("run"));
});
beforeEach(() => {
@@ -109,13 +113,7 @@ describe("gateway run option collisions", () => {
});
async function runGatewayCli(argv: string[]) {
await runRegisteredCli({
register: ((program: Command) => {
const gateway = addGatewayRunCommand(program.command("gateway"));
addGatewayRunCommand(gateway.command("run"));
}) as (program: Command) => void,
argv,
});
await sharedProgram.parseAsync(argv, { from: "user" });
}
function expectAuthOverrideMode(mode: string) {

View File

@@ -12,6 +12,9 @@ type NodeInvokeCall = {
};
};
let lastNodeInvokeCall: NodeInvokeCall | null = null;
let lastApprovalRequestCall: { params?: Record<string, unknown> } | null = null;
const callGateway = vi.fn(async (opts: NodeInvokeCall) => {
if (opts.method === "node.list") {
return {
@@ -28,6 +31,7 @@ const callGateway = vi.fn(async (opts: NodeInvokeCall) => {
};
}
if (opts.method === "node.invoke") {
lastNodeInvokeCall = opts;
const command = opts.params?.command;
if (command === "system.run.prepare") {
const params = (opts.params?.params ?? {}) as {
@@ -83,6 +87,7 @@ const callGateway = vi.fn(async (opts: NodeInvokeCall) => {
};
}
if (opts.method === "exec.approval.request") {
lastApprovalRequestCall = opts as { params?: Record<string, unknown> };
return { decision: "allow-once" };
}
return { ok: true };
@@ -107,44 +112,36 @@ vi.mock("../config/config.js", () => ({
describe("nodes-cli coverage", () => {
let registerNodesCli: (program: Command) => void;
let sharedProgram: Command;
const getNodeInvokeCall = () => {
const nodeInvokeCalls = callGateway.mock.calls
.map((call) => call[0])
.filter((entry): entry is NodeInvokeCall => entry?.method === "node.invoke");
const last = nodeInvokeCalls.at(-1);
const last = lastNodeInvokeCall;
if (!last) {
throw new Error("expected node.invoke call");
}
return last;
};
const getApprovalRequestCall = () =>
callGateway.mock.calls.find((call) => call[0]?.method === "exec.approval.request")?.[0] as {
params?: Record<string, unknown>;
};
const createNodesProgram = () => {
const program = new Command();
program.exitOverride();
registerNodesCli(program);
return program;
};
const getApprovalRequestCall = () => lastApprovalRequestCall;
const runNodesCommand = async (args: string[]) => {
const program = createNodesProgram();
await program.parseAsync(args, { from: "user" });
await sharedProgram.parseAsync(args, { from: "user" });
return getNodeInvokeCall();
};
beforeAll(async () => {
({ registerNodesCli } = await import("./nodes-cli.js"));
sharedProgram = new Command();
sharedProgram.exitOverride();
registerNodesCli(sharedProgram);
});
beforeEach(() => {
resetRuntimeCapture();
callGateway.mockClear();
randomIdempotencyKey.mockClear();
lastNodeInvokeCall = null;
lastApprovalRequestCall = null;
});
it("invokes system.run with parsed params", async () => {

View File

@@ -3,28 +3,17 @@ import { buildConfigSchema } from "./schema.js";
import { applyDerivedTags, CONFIG_TAGS, deriveTagsForPath } from "./schema.tags.js";
describe("config schema", () => {
type SchemaInput = NonNullable<Parameters<typeof buildConfigSchema>[0]>;
let baseSchema: ReturnType<typeof buildConfigSchema>;
let pluginUiHintInput: SchemaInput;
let tokenHintInput: SchemaInput;
let mergedSchemaInput: SchemaInput;
let heartbeatChannelInput: SchemaInput;
let cachedMergeInput: SchemaInput;
beforeAll(() => {
baseSchema = buildConfigSchema();
});
it("exports schema + hints", () => {
const res = baseSchema;
const schema = res.schema as { properties?: Record<string, unknown> };
expect(schema.properties?.gateway).toBeTruthy();
expect(schema.properties?.agents).toBeTruthy();
expect(schema.properties?.acp).toBeTruthy();
expect(schema.properties?.$schema).toBeUndefined();
expect(res.uiHints.gateway?.label).toBe("Gateway");
expect(res.uiHints["gateway.auth.token"]?.sensitive).toBe(true);
expect(res.uiHints["channels.discord.threadBindings.spawnAcpSessions"]?.label).toBeTruthy();
expect(res.version).toBeTruthy();
expect(res.generatedAt).toBeTruthy();
});
it("merges plugin ui hints", () => {
const res = buildConfigSchema({
pluginUiHintInput = {
plugins: [
{
id: "voice-call",
@@ -36,18 +25,8 @@ describe("config schema", () => {
},
},
],
});
expect(res.uiHints["plugins.entries.voice-call"]?.label).toBe("Voice Call");
expect(res.uiHints["plugins.entries.voice-call.config"]?.label).toBe("Voice Call Config");
expect(res.uiHints["plugins.entries.voice-call.config.twilio.authToken"]?.label).toBe(
"Auth Token",
);
expect(res.uiHints["plugins.entries.voice-call.config.twilio.authToken"]?.sensitive).toBe(true);
});
it("does not re-mark existing non-sensitive token-like fields", () => {
const res = buildConfigSchema({
};
tokenHintInput = {
plugins: [
{
id: "voice-call",
@@ -56,13 +35,8 @@ describe("config schema", () => {
},
},
],
});
expect(res.uiHints["plugins.entries.voice-call.config.tokens"]?.sensitive).toBe(false);
});
it("merges plugin + channel schemas", () => {
const res = buildConfigSchema({
};
mergedSchemaInput = {
plugins: [
{
id: "voice-call",
@@ -87,7 +61,67 @@ describe("config schema", () => {
},
},
],
});
};
heartbeatChannelInput = {
channels: [
{
id: "bluebubbles",
label: "BlueBubbles",
configSchema: { type: "object" },
},
],
};
cachedMergeInput = {
plugins: [
{
id: "voice-call",
name: "Voice Call",
configSchema: { type: "object", properties: { provider: { type: "string" } } },
},
],
channels: [
{
id: "matrix",
label: "Matrix",
configSchema: { type: "object", properties: { accessToken: { type: "string" } } },
},
],
};
});
it("exports schema + hints", () => {
const res = baseSchema;
const schema = res.schema as { properties?: Record<string, unknown> };
expect(schema.properties?.gateway).toBeTruthy();
expect(schema.properties?.agents).toBeTruthy();
expect(schema.properties?.acp).toBeTruthy();
expect(schema.properties?.$schema).toBeUndefined();
expect(res.uiHints.gateway?.label).toBe("Gateway");
expect(res.uiHints["gateway.auth.token"]?.sensitive).toBe(true);
expect(res.uiHints["channels.discord.threadBindings.spawnAcpSessions"]?.label).toBeTruthy();
expect(res.version).toBeTruthy();
expect(res.generatedAt).toBeTruthy();
});
it("merges plugin ui hints", () => {
const res = buildConfigSchema(pluginUiHintInput);
expect(res.uiHints["plugins.entries.voice-call"]?.label).toBe("Voice Call");
expect(res.uiHints["plugins.entries.voice-call.config"]?.label).toBe("Voice Call Config");
expect(res.uiHints["plugins.entries.voice-call.config.twilio.authToken"]?.label).toBe(
"Auth Token",
);
expect(res.uiHints["plugins.entries.voice-call.config.twilio.authToken"]?.sensitive).toBe(true);
});
it("does not re-mark existing non-sensitive token-like fields", () => {
const res = buildConfigSchema(tokenHintInput);
expect(res.uiHints["plugins.entries.voice-call.config.tokens"]?.sensitive).toBe(false);
});
it("merges plugin + channel schemas", () => {
const res = buildConfigSchema(mergedSchemaInput);
const schema = res.schema as {
properties?: Record<string, unknown>;
@@ -110,15 +144,7 @@ describe("config schema", () => {
});
it("adds heartbeat target hints with dynamic channels", () => {
const res = buildConfigSchema({
channels: [
{
id: "bluebubbles",
label: "BlueBubbles",
configSchema: { type: "object" },
},
],
});
const res = buildConfigSchema(heartbeatChannelInput);
const defaultsHint = res.uiHints["agents.defaults.heartbeat.target"];
const listHint = res.uiHints["agents.list.*.heartbeat.target"];
@@ -128,26 +154,10 @@ describe("config schema", () => {
});
it("caches merged schemas for identical plugin/channel metadata", () => {
const params = {
plugins: [
{
id: "voice-call",
name: "Voice Call",
configSchema: { type: "object", properties: { provider: { type: "string" } } },
},
],
channels: [
{
id: "matrix",
label: "Matrix",
configSchema: { type: "object", properties: { accessToken: { type: "string" } } },
},
],
};
const first = buildConfigSchema(params);
const first = buildConfigSchema(cachedMergeInput);
const second = buildConfigSchema({
plugins: [{ ...params.plugins[0] }],
channels: [{ ...params.channels[0] }],
plugins: [{ ...cachedMergeInput.plugins![0] }],
channels: [{ ...cachedMergeInput.channels![0] }],
});
expect(second).toBe(first);
});

View File

@@ -1,8 +1,9 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import { makeTempWorkspace, writeWorkspaceFile } from "../../../test-helpers/workspace.js";
import { writeWorkspaceFile } from "../../../test-helpers/workspace.js";
import type { HookHandler } from "../../hooks.js";
import { createHookEvent } from "../../hooks.js";
@@ -12,9 +13,28 @@ vi.mock("../../llm-slug-generator.js", () => ({
}));
let handler: HookHandler;
let suiteWorkspaceRoot = "";
let workspaceCaseCounter = 0;
async function createCaseWorkspace(prefix = "case"): Promise<string> {
const dir = path.join(suiteWorkspaceRoot, `${prefix}-${workspaceCaseCounter}`);
workspaceCaseCounter += 1;
await fs.mkdir(dir, { recursive: true });
return dir;
}
beforeAll(async () => {
({ default: handler } = await import("./handler.js"));
suiteWorkspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-session-memory-"));
});
afterAll(async () => {
if (!suiteWorkspaceRoot) {
return;
}
await fs.rm(suiteWorkspaceRoot, { recursive: true, force: true });
suiteWorkspaceRoot = "";
workspaceCaseCounter = 0;
});
/**
@@ -69,7 +89,7 @@ async function runNewWithPreviousSession(params: {
cfg?: (tempDir: string) => OpenClawConfig;
action?: "new" | "reset";
}): Promise<{ tempDir: string; files: string[]; memoryContent: string }> {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const tempDir = await createCaseWorkspace("workspace");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
@@ -117,7 +137,7 @@ function makeSessionMemoryConfig(tempDir: string, messages?: number): OpenClawCo
async function createSessionMemoryWorkspace(params?: {
activeSession?: { name: string; content: string };
}): Promise<{ tempDir: string; sessionsDir: string; activeSessionFile?: string }> {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const tempDir = await createCaseWorkspace("workspace");
const sessionsDir = path.join(tempDir, "sessions");
await fs.mkdir(sessionsDir, { recursive: true });
@@ -162,7 +182,7 @@ function expectMemoryConversation(params: {
describe("session-memory hook", () => {
it("skips non-command events", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const tempDir = await createCaseWorkspace("workspace");
const event = createHookEvent("agent", "bootstrap", "agent:main:main", {
workspaceDir: tempDir,
@@ -176,7 +196,7 @@ describe("session-memory hook", () => {
});
it("skips commands other than new", async () => {
const tempDir = await makeTempWorkspace("openclaw-session-memory-");
const tempDir = await createCaseWorkspace("workspace");
const event = createHookEvent("command", "help", "agent:main:main", {
workspaceDir: tempDir,

View File

@@ -22,8 +22,13 @@ let installPluginFromPath: typeof import("./install.js").installPluginFromPath;
let PLUGIN_INSTALL_ERROR_CODE: typeof import("./install.js").PLUGIN_INSTALL_ERROR_CODE;
let runCommandWithTimeout: typeof import("../process/exec.js").runCommandWithTimeout;
let suiteTempRoot = "";
let suiteFixtureRoot = "";
let tempDirCounter = 0;
const pluginFixturesDir = path.resolve(process.cwd(), "test", "fixtures", "plugins-install");
const archiveFixturePathCache = new Map<string, string>();
const dynamicArchiveTemplatePathCache = new Map<string, string>();
let installPluginFromDirTemplateDir = "";
let manifestInstallTemplateDir = "";
function ensureSuiteTempRoot() {
if (suiteTempRoot) {
@@ -40,6 +45,15 @@ function makeTempDir() {
return dir;
}
function ensureSuiteFixtureRoot() {
if (suiteFixtureRoot) {
return suiteFixtureRoot;
}
suiteFixtureRoot = path.join(ensureSuiteTempRoot(), "_fixtures");
fs.mkdirSync(suiteFixtureRoot, { recursive: true });
return suiteFixtureRoot;
}
async function packToArchive({
pkgDir,
outDir,
@@ -66,10 +80,18 @@ async function createVoiceCallArchiveBuffer(version: string): Promise<Buffer> {
return fs.readFileSync(path.join(pluginFixturesDir, `voice-call-${version}.tgz`));
}
function writeArchiveBuffer(params: { outName: string; buffer: Buffer }): string {
const workDir = makeTempDir();
const archivePath = path.join(workDir, params.outName);
function getArchiveFixturePath(params: {
cacheKey: string;
outName: string;
buffer: Buffer;
}): string {
const hit = archiveFixturePathCache.get(params.cacheKey);
if (hit) {
return hit;
}
const archivePath = path.join(ensureSuiteFixtureRoot(), params.outName);
fs.writeFileSync(archivePath, params.buffer);
archiveFixturePathCache.set(params.cacheKey, archivePath);
return archivePath;
}
@@ -94,7 +116,11 @@ async function getVoiceCallArchiveBuffer(version: string): Promise<Buffer> {
async function setupVoiceCallArchiveInstall(params: { outName: string; version: string }) {
const stateDir = makeTempDir();
const archiveBuffer = await getVoiceCallArchiveBuffer(params.version);
const archivePath = writeArchiveBuffer({ outName: params.outName, buffer: archiveBuffer });
const archivePath = getArchiveFixturePath({
cacheKey: `voice-call:${params.version}`,
outName: params.outName,
buffer: archiveBuffer,
});
return {
stateDir,
archivePath,
@@ -131,22 +157,17 @@ function setupPluginInstallDirs() {
}
function setupInstallPluginFromDirFixture(params?: { devDependencies?: Record<string, string> }) {
const workDir = makeTempDir();
const stateDir = makeTempDir();
const pluginDir = path.join(workDir, "plugin");
fs.mkdirSync(path.join(pluginDir, "dist"), { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "@openclaw/test-plugin",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
dependencies: { "left-pad": "1.3.0" },
...(params?.devDependencies ? { devDependencies: params.devDependencies } : {}),
}),
"utf-8",
);
fs.writeFileSync(path.join(pluginDir, "dist", "index.js"), "export {};", "utf-8");
const pluginDir = path.join(makeTempDir(), "plugin");
fs.cpSync(installPluginFromDirTemplateDir, pluginDir, { recursive: true });
if (params?.devDependencies) {
const packageJsonPath = path.join(pluginDir, "package.json");
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as {
devDependencies?: Record<string, string>;
};
manifest.devDependencies = params.devDependencies;
fs.writeFileSync(packageJsonPath, JSON.stringify(manifest), "utf-8");
}
return { pluginDir, extensionsDir: path.join(stateDir, "extensions") };
}
@@ -164,18 +185,9 @@ async function installFromDirWithWarnings(params: { pluginDir: string; extension
}
function setupManifestInstallFixture(params: { manifestId: string }) {
const { pluginDir, extensionsDir } = setupPluginInstallDirs();
fs.mkdirSync(path.join(pluginDir, "dist"), { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "package.json"),
JSON.stringify({
name: "@openclaw/cognee-openclaw",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
fs.writeFileSync(path.join(pluginDir, "dist", "index.js"), "export {};", "utf-8");
const stateDir = makeTempDir();
const pluginDir = path.join(makeTempDir(), "plugin-src");
fs.cpSync(manifestInstallTemplateDir, pluginDir, { recursive: true });
fs.writeFileSync(
path.join(pluginDir, "openclaw.plugin.json"),
JSON.stringify({
@@ -184,7 +196,7 @@ function setupManifestInstallFixture(params: { manifestId: string }) {
}),
"utf-8",
);
return { pluginDir, extensionsDir };
return { pluginDir, extensionsDir: path.join(stateDir, "extensions") };
}
async function expectArchiveInstallReservedSegmentRejection(params: {
@@ -214,20 +226,31 @@ async function installArchivePackageAndReturnResult(params: {
withDistIndex?: boolean;
}) {
const stateDir = makeTempDir();
const workDir = makeTempDir();
const pkgDir = path.join(workDir, "package");
fs.mkdirSync(pkgDir, { recursive: true });
if (params.withDistIndex) {
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
}
fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify(params.packageJson), "utf-8");
const archivePath = await packToArchive({
pkgDir,
outDir: workDir,
outName: params.outName,
const templateKey = JSON.stringify({
packageJson: params.packageJson,
withDistIndex: params.withDistIndex === true,
});
let archivePath = dynamicArchiveTemplatePathCache.get(templateKey);
if (!archivePath) {
const templateDir = makeTempDir();
const pkgDir = path.join(templateDir, "package");
fs.mkdirSync(pkgDir, { recursive: true });
if (params.withDistIndex) {
fs.mkdirSync(path.join(pkgDir, "dist"), { recursive: true });
fs.writeFileSync(path.join(pkgDir, "dist", "index.js"), "export {};", "utf-8");
}
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(params.packageJson),
"utf-8",
);
archivePath = await packToArchive({
pkgDir,
outDir: ensureSuiteFixtureRoot(),
outName: params.outName,
});
dynamicArchiveTemplatePathCache.set(templateKey, archivePath);
}
const extensionsDir = path.join(stateDir, "extensions");
const result = await installPluginFromArchive({
@@ -258,6 +281,52 @@ beforeAll(async () => {
PLUGIN_INSTALL_ERROR_CODE,
} = await import("./install.js"));
({ runCommandWithTimeout } = await import("../process/exec.js"));
installPluginFromDirTemplateDir = path.join(
ensureSuiteFixtureRoot(),
"install-from-dir-template",
);
fs.mkdirSync(path.join(installPluginFromDirTemplateDir, "dist"), { recursive: true });
fs.writeFileSync(
path.join(installPluginFromDirTemplateDir, "package.json"),
JSON.stringify({
name: "@openclaw/test-plugin",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
dependencies: { "left-pad": "1.3.0" },
}),
"utf-8",
);
fs.writeFileSync(
path.join(installPluginFromDirTemplateDir, "dist", "index.js"),
"export {};",
"utf-8",
);
manifestInstallTemplateDir = path.join(ensureSuiteFixtureRoot(), "manifest-install-template");
fs.mkdirSync(path.join(manifestInstallTemplateDir, "dist"), { recursive: true });
fs.writeFileSync(
path.join(manifestInstallTemplateDir, "package.json"),
JSON.stringify({
name: "@openclaw/cognee-openclaw",
version: "0.0.1",
openclaw: { extensions: ["./dist/index.js"] },
}),
"utf-8",
);
fs.writeFileSync(
path.join(manifestInstallTemplateDir, "dist", "index.js"),
"export {};",
"utf-8",
);
fs.writeFileSync(
path.join(manifestInstallTemplateDir, "openclaw.plugin.json"),
JSON.stringify({
id: "manifest-template",
configSchema: { type: "object", properties: {} },
}),
"utf-8",
);
});
beforeEach(() => {
@@ -303,8 +372,9 @@ describe("installPluginFromArchive", () => {
it("installs from a zip archive", async () => {
const stateDir = makeTempDir();
const archivePath = writeArchiveBuffer({
outName: "plugin.zip",
const archivePath = getArchiveFixturePath({
cacheKey: "zipper:0.0.1",
outName: "zipper-0.0.1.zip",
buffer: await ZIPPER_ARCHIVE_BUFFER_PROMISE,
});
@@ -318,12 +388,14 @@ describe("installPluginFromArchive", () => {
it("allows updates when mode is update", async () => {
const stateDir = makeTempDir();
const archiveV1 = writeArchiveBuffer({
outName: "plugin-v1.tgz",
const archiveV1 = getArchiveFixturePath({
cacheKey: "voice-call:0.0.1",
outName: "voice-call-0.0.1.tgz",
buffer: await VOICE_CALL_ARCHIVE_V1_BUFFER_PROMISE,
});
const archiveV2 = writeArchiveBuffer({
outName: "plugin-v2.tgz",
const archiveV2 = getArchiveFixturePath({
cacheKey: "voice-call:0.0.2",
outName: "voice-call-0.0.2.tgz",
buffer: await VOICE_CALL_ARCHIVE_V2_BUFFER_PROMISE,
});