test: trim remaining hotspot tests

This commit is contained in:
Peter Steinberger
2026-04-17 02:07:26 +01:00
parent dbc8179f31
commit 6ba8626c25
5 changed files with 230 additions and 360 deletions

View File

@@ -920,7 +920,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
});
for (const runtime of ["bun", "deno", "tsx", "jiti"] as const) {
it(`denies approval-based execution when a ${runtime} script operand changes after approval`, async () => {
it(`validates approved ${runtime} script operand stability`, async () => {
await withFakeRuntimeOnPath({
runtime,
run: async () => {
@@ -959,23 +959,15 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
},
});
});
it(`keeps approved ${runtime} script execution working when the script is unchanged`, async () => {
await withFakeRuntimeOnPath({
runtime,
run: async () => {
const tmp = fs.mkdtempSync(
const stableTmp = fs.mkdtempSync(
path.join(os.tmpdir(), `openclaw-approval-${runtime}-script-stable-`),
);
const fixture = createRuntimeScriptOperandFixture({ tmp, runtime });
fs.writeFileSync(fixture.scriptPath, fixture.initialBody);
const stableFixture = createRuntimeScriptOperandFixture({ tmp: stableTmp, runtime });
fs.writeFileSync(stableFixture.scriptPath, stableFixture.initialBody);
try {
const prepared = buildSystemRunApprovalPlan({
command: fixture.command,
cwd: tmp,
command: stableFixture.command,
cwd: stableTmp,
});
expect(prepared.ok).toBe(true);
if (!prepared.ok) {
@@ -987,7 +979,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
command: prepared.plan.argv,
rawCommand: prepared.plan.commandText,
systemRunPlan: prepared.plan,
cwd: prepared.plan.cwd ?? tmp,
cwd: prepared.plan.cwd ?? stableTmp,
approved: true,
security: "full",
ask: "off",
@@ -996,7 +988,7 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
expect(runCommand).toHaveBeenCalledTimes(1);
expectInvokeOk(sendInvokeResult);
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
fs.rmSync(stableTmp, { recursive: true, force: true });
}
},
});
@@ -1310,32 +1302,33 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
});
});
it.each([
{
command: ["python3", "-c", "print('hi')"],
expected: "python3 -c requires explicit approval in strictInlineEval mode",
},
{
command: ["awk", 'BEGIN{system("id")}', "/dev/null"],
expected: "awk inline program requires explicit approval in strictInlineEval mode",
},
{
command: ["find", ".", "-exec", "id", "{}", ";"],
expected: "find -exec requires explicit approval in strictInlineEval mode",
},
{
command: ["xargs", "id"],
expected: "xargs inline command requires explicit approval in strictInlineEval mode",
},
{
command: ["make", "-f", "evil.mk"],
expected: "make -f requires explicit approval in strictInlineEval mode",
},
{
command: ["sed", "s/.*/id/e", "/dev/null"],
expected: "sed inline program requires explicit approval in strictInlineEval mode",
},
] as const)("requires explicit approval for strict inline-eval carrier %j", async (testCase) => {
it("requires explicit approval for strict inline-eval carriers", async () => {
const cases = [
{
command: ["python3", "-c", "print('hi')"],
expected: "python3 -c requires explicit approval in strictInlineEval mode",
},
{
command: ["awk", 'BEGIN{system("id")}', "/dev/null"],
expected: "awk inline program requires explicit approval in strictInlineEval mode",
},
{
command: ["find", ".", "-exec", "id", "{}", ";"],
expected: "find -exec requires explicit approval in strictInlineEval mode",
},
{
command: ["xargs", "id"],
expected: "xargs inline command requires explicit approval in strictInlineEval mode",
},
{
command: ["make", "-f", "evil.mk"],
expected: "make -f requires explicit approval in strictInlineEval mode",
},
{
command: ["sed", "s/.*/id/e", "/dev/null"],
expected: "sed inline program requires explicit approval in strictInlineEval mode",
},
] as const;
setRuntimeConfigSnapshot({
tools: {
exec: {
@@ -1344,22 +1337,24 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
},
});
try {
const { runCommand, sendInvokeResult, sendNodeEvent } = await runSystemInvoke({
preferMacAppExecHost: false,
command: [...testCase.command],
security: "full",
ask: "off",
});
for (const testCase of cases) {
const { runCommand, sendInvokeResult, sendNodeEvent } = await runSystemInvoke({
preferMacAppExecHost: false,
command: [...testCase.command],
security: "full",
ask: "off",
});
expect(runCommand).not.toHaveBeenCalled();
expect(sendNodeEvent).toHaveBeenCalledWith(
expect.anything(),
"exec.denied",
expect.objectContaining({ reason: "approval-required" }),
);
expectInvokeErrorMessage(sendInvokeResult, {
message: testCase.expected,
});
expect(runCommand, testCase.command.join(" ")).not.toHaveBeenCalled();
expect(sendNodeEvent, testCase.command.join(" ")).toHaveBeenCalledWith(
expect.anything(),
"exec.denied",
expect.objectContaining({ reason: "approval-required" }),
);
expectInvokeErrorMessage(sendInvokeResult, {
message: testCase.expected,
});
}
} finally {
clearRuntimeConfigSnapshot();
}
@@ -1395,26 +1390,26 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
}
});
it.each([
{ executable: "python3", args: ["-c", "print('hi')"] },
{ executable: "awk", args: ['BEGIN{system("id")}', "/dev/null"] },
{ executable: "find", args: [".", "-exec", "id", "{}", ";"] },
{ executable: "xargs", args: ["id"] },
{ executable: "sed", args: ["s/.*/id/e", "/dev/null"] },
] as const)(
"does not persist allow-always approvals for strict inline-eval carrier %j",
async (testCase) => {
setRuntimeConfigSnapshot({
tools: {
exec: {
strictInlineEval: true,
},
it("does not persist allow-always approvals for strict inline-eval carriers", async () => {
const cases = [
{ executable: "python3", args: ["-c", "print('hi')"] },
{ executable: "awk", args: ['BEGIN{system("id")}', "/dev/null"] },
{ executable: "find", args: [".", "-exec", "id", "{}", ";"] },
{ executable: "xargs", args: ["id"] },
{ executable: "sed", args: ["s/.*/id/e", "/dev/null"] },
] as const;
setRuntimeConfigSnapshot({
tools: {
exec: {
strictInlineEval: true,
},
});
try {
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals(),
run: async () => {
},
});
try {
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals(),
run: async () => {
for (const testCase of cases) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-inline-eval-bin-"));
try {
const executablePath = createTempExecutable({
@@ -1437,13 +1432,13 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
},
});
} finally {
clearRuntimeConfigSnapshot();
}
},
);
}
},
});
} finally {
clearRuntimeConfigSnapshot();
}
});
it("persists benign awk allow-always approvals in strict inline-eval mode without reopening inline carriers", async () => {
setRuntimeConfigSnapshot({

View File

@@ -1,8 +1,3 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { generateSecureToken } from "../infra/secure-random.js";
import { runExec } from "../process/exec.js";
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js";
type CloseTrackedBrowserTabsParams = {
@@ -44,6 +39,19 @@ export async function closeTrackedBrowserTabsForSessions(
}
export async function movePathToTrash(targetPath: string): Promise<string> {
const [
{ default: fs },
{ default: os },
{ default: path },
{ generateSecureToken },
{ runExec },
] = await Promise.all([
import("node:fs"),
import("node:os"),
import("node:path"),
import("../infra/secure-random.js"),
import("../process/exec.js"),
]);
try {
await runExec("trash", [targetPath], { timeoutMs: 10_000 });
return targetPath;

View File

@@ -34,6 +34,7 @@ import {
isCronSessionKey,
parseSessionKey,
resolveAssistantAttachmentAuthToken,
resolveSessionOptionGroups,
resolveSessionDisplayName,
switchChatSession,
} from "./app-render.helpers.ts";
@@ -46,6 +47,28 @@ function row(overrides: Partial<SessionRow> & { key: string }): SessionRow {
return { kind: "direct", updatedAt: 0, ...overrides };
}
function labelsForSessionOptions(params: {
sessionKey: string;
sessions?: SessionRow[];
agentsList?: AppViewState["agentsList"];
}) {
const groups = resolveSessionOptionGroups(
{
sessionsHideCron: true,
agentsList: params.agentsList ?? null,
} as AppViewState,
params.sessionKey,
{
ts: 0,
path: "",
count: params.sessions?.length ?? 0,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: params.sessions ?? [],
},
);
return groups.flatMap((group) => group.options.map((option) => option.label));
}
/* ================================================================
* parseSessionKey low-level key → type / fallback mapping
* ================================================================ */
@@ -348,6 +371,94 @@ describe("isCronSessionKey", () => {
});
});
describe("resolveSessionOptionGroups", () => {
it("prefers grouped session labels over display names", () => {
const sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
const labels = labelsForSessionOptions({
sessionKey,
sessions: [
row({
key: sessionKey,
label: "cron-config-check",
displayName: "webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
}),
],
});
expect(labels).toContain("Subagent: cron-config-check");
expect(labels).not.toContain(sessionKey);
expect(labels).not.toContain(
"subagent:4f2146de-887b-4176-9abe-91140082959b · webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
);
});
it("keeps scoped fallbacks for active grouped sessions without useful row metadata", () => {
const sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
expect(labelsForSessionOptions({ sessionKey })).toContain(
"subagent:4f2146de-887b-4176-9abe-91140082959b",
);
expect(
labelsForSessionOptions({
sessionKey,
sessions: [row({ key: sessionKey })],
}),
).toContain("subagent:4f2146de-887b-4176-9abe-91140082959b");
});
it("disambiguates duplicate grouped labels with scoped suffixes", () => {
const labels = labelsForSessionOptions({
sessionKey: "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b",
sessions: [
row({
key: "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b",
label: "cron-config-check",
}),
row({
key: "agent:main:subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
label: "cron-config-check",
}),
],
});
expect(labels).toContain(
"Subagent: cron-config-check · subagent:4f2146de-887b-4176-9abe-91140082959b",
);
expect(labels).toContain(
"Subagent: cron-config-check · subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
);
expect(labels).not.toContain("Subagent: cron-config-check");
});
it("uses agent group labels to keep duplicate main sessions unique", () => {
const labels = labelsForSessionOptions({
sessionKey: "agent:alpha:main",
agentsList: {
defaultId: "alpha",
mainKey: "agent:alpha:main",
scope: "all",
agents: [
{ id: "alpha", name: "Deep Chat" },
{ id: "beta", name: "Coding" },
],
},
sessions: [
row({ key: "agent:alpha:main" }),
row({ key: "agent:beta:main" }),
row({
key: "agent:alpha:named-main",
label: "Deep Chat (alpha) / main",
}),
],
});
expect(labels.filter((label) => label === "Deep Chat (alpha) / main")).toHaveLength(1);
expect(labels).toContain("Deep Chat (alpha) / main · named-main");
expect(labels).toContain("Coding (beta) / main");
expect(labels).not.toContain("main");
});
});
describe("switchChatSession", () => {
it("refreshes the chat avatar after clearing session-scoped state", async () => {
const settings: AppViewState["settings"] = {

View File

@@ -39,6 +39,34 @@ describe("chat-model-select-state", () => {
expect(resolveChatModelOverrideValue(state)).toBe("openai/gpt-5-mini");
});
it("normalizes cached bare overrides to the matching catalog option", () => {
const state = {
sessionKey: "main",
chatModelOverrides: { main: { kind: "raw", value: "gpt-5-mini" } },
chatModelCatalog: createModelCatalog(...DEFAULT_CHAT_MODEL_CATALOG),
sessionsResult: createSessionsListResult({ model: null, modelProvider: null }),
} as const;
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).toContain("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).not.toContain("gpt-5-mini");
});
it("prefers catalog provider matches over stale session providers", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
chatModelCatalog: createModelCatalog(DEEPSEEK_CHAT_MODEL),
sessionsResult: createSessionsListResult({
model: "deepseek-chat",
modelProvider: "zai",
}),
};
expect(resolveChatModelSelectState(state).currentOverride).toBe("deepseek/deepseek-chat");
});
it("preserves already-qualified active-session models when the provider is stale and the catalog is empty", () => {
const state = {
sessionKey: "main",

View File

@@ -8,7 +8,6 @@ import type { AppViewState } from "../app-view-state.ts";
import {
createModelCatalog,
createSessionsListResult,
DEEPSEEK_CHAT_MODEL,
DEFAULT_CHAT_MODEL_CATALOG,
} from "../chat-model.test-helpers.ts";
import { resetAssistantAttachmentAvailabilityCacheForTest } from "../chat/grouped-render.ts";
@@ -947,277 +946,6 @@ describe("chat view", () => {
vi.unstubAllGlobals();
});
it("normalizes cached bare /model overrides to the matching catalog option", () => {
const { state } = createChatHeaderState();
state.chatModelOverrides = { main: { kind: "raw", value: "gpt-5-mini" } };
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const modelSelect = container.querySelector<HTMLSelectElement>(
'select[data-chat-model-select="true"]',
);
expect(modelSelect).not.toBeNull();
expect(modelSelect?.value).toBe("openai/gpt-5-mini");
const optionValues = Array.from(modelSelect?.querySelectorAll("option") ?? []).map(
(option) => option.value,
);
expect(optionValues).toContain("openai/gpt-5-mini");
expect(optionValues).not.toContain("gpt-5-mini");
});
it("prefers the catalog provider when the active session reports a stale provider", () => {
const { state } = createChatHeaderState({
model: "deepseek-chat",
modelProvider: "zai",
models: createModelCatalog(DEEPSEEK_CHAT_MODEL),
});
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const modelSelect = container.querySelector<HTMLSelectElement>(
'select[data-chat-model-select="true"]',
);
expect(modelSelect?.value).toBe("deepseek/deepseek-chat");
});
it("falls back to the server-qualified session model when catalog lookup fails", () => {
const { state } = createChatHeaderState({
model: "gpt-5-mini",
models: [],
});
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const modelSelect = container.querySelector<HTMLSelectElement>(
'select[data-chat-model-select="true"]',
);
expect(modelSelect?.value).toBe("openai/gpt-5-mini");
const optionValues = Array.from(modelSelect?.querySelectorAll("option") ?? []).map(
(option) => option.value,
);
expect(optionValues).toContain("openai/gpt-5-mini");
expect(optionValues).not.toContain("gpt-5-mini");
});
it("prefers the session label over displayName in the grouped chat session selector", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 1,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [
{
key: state.sessionKey,
kind: "direct",
updatedAt: null,
label: "cron-config-check",
displayName: "webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("Subagent: cron-config-check");
expect(labels).not.toContain(state.sessionKey);
expect(labels).not.toContain(
"subagent:4f2146de-887b-4176-9abe-91140082959b · webchat:g-agent-main-subagent-4f2146de-887b-4176-9abe-91140082959b",
);
});
it("keeps a unique scoped fallback when the current grouped session is missing from sessions.list", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("subagent:4f2146de-887b-4176-9abe-91140082959b");
expect(labels).not.toContain("Subagent:");
});
it("keeps a unique scoped fallback when a grouped session row has no label or displayName", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 1,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [
{
key: state.sessionKey,
kind: "direct",
updatedAt: null,
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("subagent:4f2146de-887b-4176-9abe-91140082959b");
expect(labels).not.toContain("Subagent:");
});
it("disambiguates duplicate grouped labels with the scoped key suffix", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b";
state.settings.sessionKey = state.sessionKey;
state.sessionsResult = {
ts: 0,
path: "",
count: 2,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [
{
key: "agent:main:subagent:4f2146de-887b-4176-9abe-91140082959b",
kind: "direct",
updatedAt: null,
label: "cron-config-check",
},
{
key: "agent:main:subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
kind: "direct",
updatedAt: null,
label: "cron-config-check",
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain(
"Subagent: cron-config-check · subagent:4f2146de-887b-4176-9abe-91140082959b",
);
expect(labels).toContain(
"Subagent: cron-config-check · subagent:6fb8b84b-c31f-410f-b7df-1553c82e43c9",
);
expect(labels).not.toContain("Subagent: cron-config-check");
});
it("prefixes duplicate agent session labels with the agent name", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:alpha:main";
state.settings.sessionKey = state.sessionKey;
state.agentsList = {
defaultId: "alpha",
mainKey: "agent:alpha:main",
scope: "all",
agents: [
{ id: "alpha", name: "Deep Chat" },
{ id: "beta", name: "Coding" },
],
};
state.sessionsResult = {
ts: 0,
path: "",
count: 2,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [
{
key: "agent:alpha:main",
kind: "direct",
updatedAt: null,
},
{
key: "agent:beta:main",
kind: "direct",
updatedAt: null,
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels).toContain("Deep Chat (alpha) / main");
expect(labels).toContain("Coding (beta) / main");
expect(labels).not.toContain("main");
});
it("keeps agent-prefixed labels unique when a custom label already matches the prefix", () => {
const { state } = createChatHeaderState({ omitSessionFromList: true });
state.sessionKey = "agent:alpha:main";
state.settings.sessionKey = state.sessionKey;
state.agentsList = {
defaultId: "alpha",
mainKey: "agent:alpha:main",
scope: "all",
agents: [
{ id: "alpha", name: "Deep Chat" },
{ id: "beta", name: "Coding" },
],
};
state.sessionsResult = {
ts: 0,
path: "",
count: 3,
defaults: { modelProvider: "openai", model: "gpt-5", contextTokens: null },
sessions: [
{
key: "agent:alpha:main",
kind: "direct",
updatedAt: null,
},
{
key: "agent:beta:main",
kind: "direct",
updatedAt: null,
},
{
key: "agent:alpha:named-main",
kind: "direct",
updatedAt: null,
label: "Deep Chat (alpha) / main",
},
],
};
const container = document.createElement("div");
render(renderChatSessionSelect(state), container);
const [sessionSelect] = Array.from(container.querySelectorAll<HTMLSelectElement>("select"));
const labels = Array.from(sessionSelect?.querySelectorAll("option") ?? []).map((option) =>
option.textContent?.trim(),
);
expect(labels.filter((label) => label === "Deep Chat (alpha) / main")).toHaveLength(1);
expect(labels).toContain("Deep Chat (alpha) / main · named-main");
expect(labels).toContain("Coding (beta) / main");
});
it("keeps tool cards collapsed by default and expands them inline on demand", async () => {
const container = document.createElement("div");
const props = createProps({