test: micro-optimize slow suites and CLI command setup
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user