test: micro-optimize heavy gateway/browser/telegram suites

This commit is contained in:
Peter Steinberger
2026-03-02 22:29:04 +00:00
parent 1fa2488db1
commit 3cb851be90
10 changed files with 242 additions and 143 deletions

View File

@@ -11,13 +11,28 @@ import {
import { AcpxRuntime, decodeAcpxRuntimeHandleState } from "./runtime.js";
let sharedFixture: Awaited<ReturnType<typeof createMockRuntimeFixture>> | null = null;
let missingCommandRuntime: AcpxRuntime | null = null;
beforeAll(async () => {
sharedFixture = await createMockRuntimeFixture();
missingCommandRuntime = new AcpxRuntime(
{
command: "/definitely/missing/acpx",
allowPluginLocalInstall: false,
installCommand: "n/a",
cwd: process.cwd(),
permissionMode: "approve-reads",
nonInteractivePermissions: "fail",
strictWindowsCmdWrapper: true,
queueOwnerTtlSeconds: 0.1,
},
{ logger: NOOP_LOGGER },
);
});
afterAll(async () => {
sharedFixture = null;
missingCommandRuntime = null;
await cleanupMockRuntimeFixtures();
});
@@ -319,22 +334,12 @@ describe("AcpxRuntime", () => {
});
it("marks runtime unhealthy when command is missing", async () => {
const runtime = new AcpxRuntime(
{
command: "/definitely/missing/acpx",
allowPluginLocalInstall: false,
installCommand: "n/a",
cwd: process.cwd(),
permissionMode: "approve-reads",
nonInteractivePermissions: "fail",
strictWindowsCmdWrapper: true,
queueOwnerTtlSeconds: 0.1,
},
{ logger: NOOP_LOGGER },
);
await runtime.probeAvailability();
expect(runtime.isHealthy()).toBe(false);
expect(missingCommandRuntime).toBeDefined();
if (!missingCommandRuntime) {
throw new Error("missing-command runtime fixture missing");
}
await missingCommandRuntime.probeAvailability();
expect(missingCommandRuntime.isHealthy()).toBe(false);
});
it("logs ACPX spawn resolution once per command policy", async () => {
@@ -363,21 +368,11 @@ describe("AcpxRuntime", () => {
});
it("returns doctor report for missing command", async () => {
const runtime = new AcpxRuntime(
{
command: "/definitely/missing/acpx",
allowPluginLocalInstall: false,
installCommand: "n/a",
cwd: process.cwd(),
permissionMode: "approve-reads",
nonInteractivePermissions: "fail",
strictWindowsCmdWrapper: true,
queueOwnerTtlSeconds: 0.1,
},
{ logger: NOOP_LOGGER },
);
const report = await runtime.doctor();
expect(missingCommandRuntime).toBeDefined();
if (!missingCommandRuntime) {
throw new Error("missing-command runtime fixture missing");
}
const report = await missingCommandRuntime.doctor();
expect(report.ok).toBe(false);
expect(report.code).toBe("ACP_BACKEND_UNAVAILABLE");
expect(report.installCommand).toContain("acpx");

View File

@@ -17,6 +17,7 @@ async function pathExists(filePath: string): Promise<boolean> {
let fixtureRoot = "";
let fixtureCount = 0;
let syncSourceTemplateDir = "";
async function createCaseDir(prefix: string): Promise<string> {
const dir = path.join(fixtureRoot, `${prefix}-${fixtureCount++}`);
@@ -26,6 +27,27 @@ async function createCaseDir(prefix: string): Promise<string> {
beforeAll(async () => {
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-skills-sync-suite-"));
syncSourceTemplateDir = await createCaseDir("source-template");
await writeSkill({
dir: path.join(syncSourceTemplateDir, ".extra", "demo-skill"),
name: "demo-skill",
description: "Extra version",
});
await writeSkill({
dir: path.join(syncSourceTemplateDir, ".bundled", "demo-skill"),
name: "demo-skill",
description: "Bundled version",
});
await writeSkill({
dir: path.join(syncSourceTemplateDir, ".managed", "demo-skill"),
name: "demo-skill",
description: "Managed version",
});
await writeSkill({
dir: path.join(syncSourceTemplateDir, "skills", "demo-skill"),
name: "demo-skill",
description: "Workspace version",
});
});
afterAll(async () => {
@@ -39,34 +61,19 @@ describe("buildWorkspaceSkillsPrompt", () => {
) =>
withEnv({ HOME: workspaceDir, PATH: "" }, () => buildWorkspaceSkillsPrompt(workspaceDir, opts));
it("syncs merged skills into a target workspace", async () => {
const cloneSourceTemplate = async () => {
const sourceWorkspace = await createCaseDir("source");
await fs.cp(syncSourceTemplateDir, sourceWorkspace, { recursive: true });
return sourceWorkspace;
};
it("syncs merged skills into a target workspace", async () => {
const sourceWorkspace = await cloneSourceTemplate();
const targetWorkspace = await createCaseDir("target");
const extraDir = path.join(sourceWorkspace, ".extra");
const bundledDir = path.join(sourceWorkspace, ".bundled");
const managedDir = path.join(sourceWorkspace, ".managed");
await writeSkill({
dir: path.join(extraDir, "demo-skill"),
name: "demo-skill",
description: "Extra version",
});
await writeSkill({
dir: path.join(bundledDir, "demo-skill"),
name: "demo-skill",
description: "Bundled version",
});
await writeSkill({
dir: path.join(managedDir, "demo-skill"),
name: "demo-skill",
description: "Managed version",
});
await writeSkill({
dir: path.join(sourceWorkspace, "skills", "demo-skill"),
name: "demo-skill",
description: "Workspace version",
});
await withEnv({ HOME: sourceWorkspace, PATH: "" }, () =>
syncSkillsToWorkspace({
sourceWorkspaceDir: sourceWorkspace,

View File

@@ -7,9 +7,32 @@ import { writeSkill } from "./skills.e2e-test-helpers.js";
import { buildWorkspaceSkillSnapshot, buildWorkspaceSkillsPrompt } from "./skills.js";
const fixtureSuite = createFixtureSuite("openclaw-skills-snapshot-suite-");
let truncationWorkspaceTemplateDir = "";
let nestedRepoTemplateDir = "";
beforeAll(async () => {
await fixtureSuite.setup();
truncationWorkspaceTemplateDir = await fixtureSuite.createCaseDir(
"template-truncation-workspace",
);
for (let i = 0; i < 8; i += 1) {
const name = `skill-${String(i).padStart(2, "0")}`;
await writeSkill({
dir: path.join(truncationWorkspaceTemplateDir, "skills", name),
name,
description: "x".repeat(800),
});
}
nestedRepoTemplateDir = await fixtureSuite.createCaseDir("template-skills-repo");
for (let i = 0; i < 8; i += 1) {
const name = `repo-skill-${String(i).padStart(2, "0")}`;
await writeSkill({
dir: path.join(nestedRepoTemplateDir, "skills", name),
name,
description: `Desc ${i}`,
});
}
});
afterAll(async () => {
@@ -20,6 +43,12 @@ function withWorkspaceHome<T>(workspaceDir: string, cb: () => T): T {
return withEnv({ HOME: workspaceDir, PATH: "" }, cb);
}
async function cloneTemplateDir(templateDir: string, prefix: string): Promise<string> {
const cloned = await fixtureSuite.createCaseDir(prefix);
await fs.cp(templateDir, cloned, { recursive: true });
return cloned;
}
describe("buildWorkspaceSkillSnapshot", () => {
it("returns an empty snapshot when skills dirs are missing", async () => {
const workspaceDir = await fixtureSuite.createCaseDir("workspace");
@@ -110,17 +139,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
});
it("truncates the skills prompt when it exceeds the configured char budget", async () => {
const workspaceDir = await fixtureSuite.createCaseDir("workspace");
// Keep fixture size modest while still forcing truncation logic.
for (let i = 0; i < 8; i += 1) {
const name = `skill-${String(i).padStart(2, "0")}`;
await writeSkill({
dir: path.join(workspaceDir, "skills", name),
name,
description: "x".repeat(800),
});
}
const workspaceDir = await cloneTemplateDir(truncationWorkspaceTemplateDir, "workspace");
const snapshot = withWorkspaceHome(workspaceDir, () =>
buildWorkspaceSkillSnapshot(workspaceDir, {
@@ -143,16 +162,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
it("limits discovery for nested repo-style skills roots (dir/skills/*)", async () => {
const workspaceDir = await fixtureSuite.createCaseDir("workspace");
const repoDir = await fixtureSuite.createCaseDir("skills-repo");
for (let i = 0; i < 8; i += 1) {
const name = `repo-skill-${String(i).padStart(2, "0")}`;
await writeSkill({
dir: path.join(repoDir, "skills", name),
name,
description: `Desc ${i}`,
});
}
const repoDir = await cloneTemplateDir(nestedRepoTemplateDir, "skills-repo");
const snapshot = withWorkspaceHome(workspaceDir, () =>
buildWorkspaceSkillSnapshot(workspaceDir, {

View File

@@ -1,5 +1,5 @@
import { createServer } from "node:http";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
import WebSocket from "ws";
import { captureEnv } from "../test-utils/env.js";
import {
@@ -141,6 +141,7 @@ async function waitForListMatch<T>(
describe("chrome extension relay server", () => {
const TEST_GATEWAY_TOKEN = "test-gateway-token";
let cdpUrl = "";
let sharedCdpUrl = "";
let envSnapshot: ReturnType<typeof captureEnv>;
beforeEach(() => {
@@ -162,6 +163,24 @@ describe("chrome extension relay server", () => {
envSnapshot.restore();
});
afterAll(async () => {
if (!sharedCdpUrl) {
return;
}
await stopChromeExtensionRelayServer({ cdpUrl: sharedCdpUrl }).catch(() => {});
sharedCdpUrl = "";
});
async function ensureSharedRelayServer() {
if (sharedCdpUrl) {
return sharedCdpUrl;
}
const port = await getFreePort();
sharedCdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl: sharedCdpUrl });
return sharedCdpUrl;
}
async function startRelayWithExtension() {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
@@ -205,57 +224,51 @@ describe("chrome extension relay server", () => {
const unknown = getChromeExtensionRelayAuthHeaders(`http://127.0.0.1:${port}`);
expect(unknown).toEqual({});
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const headers = getChromeExtensionRelayAuthHeaders(cdpUrl);
const headers = getChromeExtensionRelayAuthHeaders(sharedUrl);
expect(Object.keys(headers)).toContain("x-openclaw-relay-token");
expect(headers["x-openclaw-relay-token"]).not.toBe(TEST_GATEWAY_TOKEN);
});
it("rejects CDP access without relay auth token", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const sharedPort = new URL(sharedUrl).port;
const res = await fetch(`${cdpUrl}/json/version`);
const res = await fetch(`${sharedUrl}/json/version`);
expect(res.status).toBe(401);
const cdp = new WebSocket(`ws://127.0.0.1:${port}/cdp`);
const cdp = new WebSocket(`ws://127.0.0.1:${sharedPort}/cdp`);
const err = await waitForError(cdp);
expect(err.message).toContain("401");
});
it("returns 400 for malformed percent-encoding in target action routes", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const res = await fetch(`${cdpUrl}/json/activate/%E0%A4%A`, {
headers: relayAuthHeaders(cdpUrl),
const res = await fetch(`${sharedUrl}/json/activate/%E0%A4%A`, {
headers: relayAuthHeaders(sharedUrl),
});
expect(res.status).toBe(400);
expect(await res.text()).toContain("invalid targetId encoding");
});
it("deduplicates concurrent relay starts for the same requested port", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
const sharedUrl = await ensureSharedRelayServer();
const port = Number(new URL(sharedUrl).port);
const [first, second] = await Promise.all([
ensureChromeExtensionRelayServer({ cdpUrl }),
ensureChromeExtensionRelayServer({ cdpUrl }),
ensureChromeExtensionRelayServer({ cdpUrl: sharedUrl }),
ensureChromeExtensionRelayServer({ cdpUrl: sharedUrl }),
]);
expect(first).toBe(second);
expect(first.port).toBe(port);
});
it("allows CORS preflight from chrome-extension origins", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const origin = "chrome-extension://abcdefghijklmnop";
const res = await fetch(`${cdpUrl}/json/version`, {
const res = await fetch(`${sharedUrl}/json/version`, {
method: "OPTIONS",
headers: {
Origin: origin,
@@ -272,11 +285,9 @@ describe("chrome extension relay server", () => {
});
it("rejects CORS preflight from non-extension origins", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const res = await fetch(`${cdpUrl}/json/version`, {
const res = await fetch(`${sharedUrl}/json/version`, {
method: "OPTIONS",
headers: {
Origin: "https://example.com",
@@ -288,15 +299,13 @@ describe("chrome extension relay server", () => {
});
it("returns CORS headers on JSON responses for extension origins", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const origin = "chrome-extension://abcdefghijklmnop";
const res = await fetch(`${cdpUrl}/json/version`, {
const res = await fetch(`${sharedUrl}/json/version`, {
headers: {
Origin: origin,
...relayAuthHeaders(cdpUrl),
...relayAuthHeaders(sharedUrl),
},
});
@@ -305,11 +314,10 @@ describe("chrome extension relay server", () => {
});
it("rejects extension websocket access without relay auth token", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const sharedPort = new URL(sharedUrl).port;
const ext = new WebSocket(`ws://127.0.0.1:${port}/extension`);
const ext = new WebSocket(`ws://127.0.0.1:${sharedPort}/extension`);
const err = await waitForError(ext);
expect(err.message).toContain("401");
});
@@ -566,44 +574,42 @@ describe("chrome extension relay server", () => {
});
it("accepts extension websocket access with relay token query param", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const sharedPort = new URL(sharedUrl).port;
const token = relayAuthHeaders(`ws://127.0.0.1:${port}/extension`)["x-openclaw-relay-token"];
const token = relayAuthHeaders(`ws://127.0.0.1:${sharedPort}/extension`)[
"x-openclaw-relay-token"
];
expect(token).toBeTruthy();
const ext = new WebSocket(
`ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(String(token))}`,
`ws://127.0.0.1:${sharedPort}/extension?token=${encodeURIComponent(String(token))}`,
);
await waitForOpen(ext);
ext.close();
});
it("accepts /json endpoints with relay token query param", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const token = relayAuthHeaders(cdpUrl)["x-openclaw-relay-token"];
const token = relayAuthHeaders(sharedUrl)["x-openclaw-relay-token"];
expect(token).toBeTruthy();
const versionRes = await fetch(
`${cdpUrl}/json/version?token=${encodeURIComponent(String(token))}`,
`${sharedUrl}/json/version?token=${encodeURIComponent(String(token))}`,
);
expect(versionRes.status).toBe(200);
});
it("accepts raw gateway token for relay auth compatibility", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const sharedUrl = await ensureSharedRelayServer();
const sharedPort = new URL(sharedUrl).port;
const versionRes = await fetch(`${cdpUrl}/json/version`, {
const versionRes = await fetch(`${sharedUrl}/json/version`, {
headers: { "x-openclaw-relay-token": TEST_GATEWAY_TOKEN },
});
expect(versionRes.status).toBe(200);
const ext = new WebSocket(
`ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(TEST_GATEWAY_TOKEN)}`,
`ws://127.0.0.1:${sharedPort}/extension?token=${encodeURIComponent(TEST_GATEWAY_TOKEN)}`,
);
await waitForOpen(ext);
ext.close();

View File

@@ -7,6 +7,7 @@ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-cha
import { buildDeviceAuthPayload } from "./device-auth.js";
import { PROTOCOL_VERSION } from "./protocol/index.js";
import {
createGatewaySuiteHarness,
connectReq,
getTrackedConnectChallengeNonce,
getFreePort,
@@ -360,6 +361,7 @@ export {
connectReq,
CONTROL_UI_CLIENT,
createSignedDevice,
createGatewaySuiteHarness,
ensurePairedDeviceTokenForCurrentIdentity,
expectHelloOkServerVersion,
getFreePort,

View File

@@ -115,12 +115,11 @@ installGatewayTestHooks({ scope: "suite" });
let harness: GatewayServerHarness;
let sharedSessionStoreDir: string;
let sharedSessionStorePath: string;
let sessionStoreCaseSeq = 0;
beforeAll(async () => {
harness = await startGatewayServerHarness();
sharedSessionStoreDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
sharedSessionStorePath = path.join(sharedSessionStoreDir, "sessions.json");
});
afterAll(async () => {
@@ -131,10 +130,11 @@ afterAll(async () => {
const openClient = async (opts?: Parameters<typeof connectOk>[1]) => await harness.openClient(opts);
async function createSessionStoreDir() {
await fs.rm(sharedSessionStoreDir, { recursive: true, force: true });
await fs.mkdir(sharedSessionStoreDir, { recursive: true });
testState.sessionStorePath = sharedSessionStorePath;
return { dir: sharedSessionStoreDir, storePath: sharedSessionStorePath };
const dir = path.join(sharedSessionStoreDir, `case-${sessionStoreCaseSeq++}`);
await fs.mkdir(dir, { recursive: true });
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
return { dir, storePath };
}
async function writeSingleLineSession(dir: string, sessionId: string, content: string) {

View File

@@ -354,6 +354,57 @@ export async function withGatewayServer<T>(
}
}
export async function createGatewaySuiteHarness(opts?: {
port?: number;
serverOptions?: GatewayServerOptions;
}): Promise<{
port: number;
server: Awaited<ReturnType<typeof startGatewayServer>>;
openWs: (headers?: Record<string, string>) => Promise<WebSocket>;
close: () => Promise<void>;
}> {
const started = await startGatewayServerWithRetries({
port: opts?.port ?? (await getFreePort()),
opts: opts?.serverOptions,
});
return {
port: started.port,
server: started.server,
openWs: async (headers?: Record<string, string>) => {
const ws = new WebSocket(`ws://127.0.0.1:${started.port}`, headers ? { headers } : undefined);
trackConnectChallengeNonce(ws);
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("timeout waiting for ws open")), 10_000);
const cleanup = () => {
clearTimeout(timer);
ws.off("open", onOpen);
ws.off("error", onError);
ws.off("close", onClose);
};
const onOpen = () => {
cleanup();
resolve();
};
const onError = (err: unknown) => {
cleanup();
reject(err instanceof Error ? err : new Error(String(err)));
};
const onClose = (code: number, reason: Buffer) => {
cleanup();
reject(new Error(`closed ${code}: ${reason.toString()}`));
};
ws.once("open", onOpen);
ws.once("error", onError);
ws.once("close", onClose);
});
return ws;
},
close: async () => {
await started.server.close();
},
};
}
export async function startServerWithClient(
token?: string,
opts?: GatewayServerOptions & { wsHeaders?: Record<string, string> },

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { Chat, Message } from "@grammyjs/types";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../test/helpers/envelope-timestamp.js";
import { withEnvAsync } from "../test-utils/env.js";
import {
@@ -52,10 +52,10 @@ const TELEGRAM_TEST_TIMINGS = {
} as const;
describe("createTelegramBot", () => {
beforeEach(() => {
beforeAll(() => {
process.env.TZ = "UTC";
});
afterEach(() => {
afterAll(() => {
process.env.TZ = ORIGINAL_TZ;
});

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../test/helpers/envelope-timestamp.js";
import { expectInboundContextContract } from "../../test/helpers/inbound-contract.js";
import {
@@ -36,8 +36,14 @@ function resolveSkillCommands(config: Parameters<typeof listNativeCommandSpecsFo
const ORIGINAL_TZ = process.env.TZ;
describe("createTelegramBot", () => {
beforeEach(() => {
beforeAll(() => {
process.env.TZ = "UTC";
});
afterAll(() => {
process.env.TZ = ORIGINAL_TZ;
});
beforeEach(() => {
loadConfig.mockReturnValue({
agents: {
defaults: {
@@ -49,11 +55,8 @@ describe("createTelegramBot", () => {
},
});
});
afterEach(() => {
process.env.TZ = ORIGINAL_TZ;
});
it("merges custom commands with native commands", () => {
it("merges custom commands with native commands", async () => {
const config = {
channels: {
telegram: {
@@ -68,6 +71,10 @@ describe("createTelegramBot", () => {
createTelegramBot({ token: "tok" });
await vi.waitFor(() => {
expect(setMyCommandsSpy).toHaveBeenCalled();
});
const registered = setMyCommandsSpy.mock.calls[0]?.[0] as Array<{
command: string;
description: string;
@@ -84,7 +91,7 @@ describe("createTelegramBot", () => {
]);
});
it("ignores custom commands that collide with native commands", () => {
it("ignores custom commands that collide with native commands", async () => {
const errorSpy = vi.fn();
const config = {
channels: {
@@ -109,6 +116,10 @@ describe("createTelegramBot", () => {
},
});
await vi.waitFor(() => {
expect(setMyCommandsSpy).toHaveBeenCalled();
});
const registered = setMyCommandsSpy.mock.calls[0]?.[0] as Array<{
command: string;
description: string;
@@ -126,7 +137,7 @@ describe("createTelegramBot", () => {
expect(errorSpy).toHaveBeenCalled();
});
it("registers custom commands when native commands are disabled", () => {
it("registers custom commands when native commands are disabled", async () => {
const config = {
commands: { native: false },
channels: {
@@ -142,6 +153,10 @@ describe("createTelegramBot", () => {
createTelegramBot({ token: "tok" });
await vi.waitFor(() => {
expect(setMyCommandsSpy).toHaveBeenCalled();
});
const registered = setMyCommandsSpy.mock.calls[0]?.[0] as Array<{
command: string;
description: string;

View File

@@ -15,6 +15,7 @@ let sharedBinDir = "";
let sharedHomeDir = "";
let sharedHomeBinDir = "";
let sharedFakePythonPath = "";
const runScriptCache = new Map<string, { ok: boolean; stdout: string; stderr: string }>();
async function writeExecutable(filePath: string, body: string): Promise<void> {
await writeFile(filePath, body, "utf8");
@@ -29,6 +30,14 @@ function runScript(
stdout: string;
stderr: string;
} {
const cacheKey = JSON.stringify({
homeDir,
extraEnv: Object.entries(extraEnv).toSorted(([a], [b]) => a.localeCompare(b)),
});
const cached = runScriptCache.get(cacheKey);
if (cached) {
return cached;
}
const binDir = path.join(homeDir, "bin");
const env = {
HOME: homeDir,
@@ -42,7 +51,9 @@ function runScript(
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
return { ok: true, stdout: stdout.trim(), stderr: "" };
const result = { ok: true, stdout: stdout.trim(), stderr: "" };
runScriptCache.set(cacheKey, result);
return result;
} catch (error) {
const e = error as {
stdout?: string | Buffer;
@@ -50,7 +61,9 @@ function runScript(
};
const stdout = typeof e.stdout === "string" ? e.stdout : (e.stdout?.toString("utf8") ?? "");
const stderr = typeof e.stderr === "string" ? e.stderr : (e.stderr?.toString("utf8") ?? "");
return { ok: false, stdout: stdout.trim(), stderr: stderr.trim() };
const result = { ok: false, stdout: stdout.trim(), stderr: stderr.trim() };
runScriptCache.set(cacheKey, result);
return result;
}
}