test: speed up auto reply command tests

This commit is contained in:
Peter Steinberger
2026-04-08 01:42:27 +01:00
parent b52f106533
commit e5d0dbdc7c
3 changed files with 129 additions and 118 deletions

View File

@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { EffectiveToolInventoryResult } from "../../agents/tools-effective-inventory.js";
import type { OpenClawConfig } from "../../config/config.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import {
@@ -6,92 +7,106 @@ import {
createTestRegistry,
} from "../../test-utils/channel-plugins.js";
async function loadToolsHarness(options?: {
resolveToolsMock?: ReturnType<typeof vi.fn>;
resolveTools?: () => {
agentId: string;
profile: string;
groups: Array<{
id: "core" | "plugin" | "channel";
label: string;
source: "core" | "plugin" | "channel";
pluginId?: string;
channelId?: string;
tools: Array<{
id: string;
label: string;
description: string;
source: "core" | "plugin" | "channel";
pluginId?: string;
channelId?: string;
}>;
}>;
};
function makeInventoryEntry(params: {
id: string;
label: string;
description: string;
source: "core" | "plugin" | "channel";
pluginId?: string;
channelId?: string;
}) {
vi.resetModules();
vi.doMock("../../agents/agent-scope.js", async () => {
const actual = await vi.importActual<typeof import("../../agents/agent-scope.js")>(
"../../agents/agent-scope.js",
);
return {
...actual,
resolveSessionAgentId: () => "main",
};
});
const resolveToolsMock =
options?.resolveToolsMock ??
vi.fn(
options?.resolveTools ??
(() => ({
agentId: "main",
profile: "coding",
groups: [
{
id: "core" as const,
label: "Built-in tools",
source: "core" as const,
tools: [
{
id: "exec",
label: "Exec",
description: "Run shell commands",
source: "core" as const,
},
],
},
{
id: "plugin" as const,
label: "Connected tools",
source: "plugin" as const,
tools: [
{
id: "docs_lookup",
label: "Docs Lookup",
description: "Search internal documentation",
source: "plugin" as const,
pluginId: "docs",
},
],
},
],
})),
);
vi.doMock("../../agents/tools-effective-inventory.js", () => ({
resolveEffectiveToolInventory: resolveToolsMock,
}));
vi.doMock("./agent-runner-utils.js", () => ({
buildThreadingToolContext: () => ({
return {
...params,
rawDescription: params.description,
};
}
function makeDefaultInventory(): EffectiveToolInventoryResult {
return {
agentId: "main",
profile: "coding",
groups: [
{
id: "core",
label: "Built-in tools",
source: "core",
tools: [
makeInventoryEntry({
id: "exec",
label: "Exec",
description: "Run shell commands",
source: "core",
}),
],
},
{
id: "plugin",
label: "Connected tools",
source: "plugin",
tools: [
makeInventoryEntry({
id: "docs_lookup",
label: "Docs Lookup",
description: "Search internal documentation",
source: "plugin",
pluginId: "docs",
}),
],
},
],
};
}
const toolsTestState = vi.hoisted(() => {
const defaultResolveTools = (): EffectiveToolInventoryResult => makeDefaultInventory();
return {
resolveToolsImpl: defaultResolveTools,
resolveToolsMock: vi.fn((..._args: unknown[]) => defaultResolveTools()),
threadingContext: {
currentChannelId: "channel-123",
currentMessageId: "message-456",
}),
}));
vi.doMock("./reply-threading.js", () => ({
resolveReplyToMode: () => "all",
}));
},
replyToMode: "all" as const,
};
});
const { buildCommandTestParams } = await import("./commands.test-harness.js");
const { handleToolsCommand } = await import("./commands-info.js");
return { buildCommandTestParams, handleToolsCommand, resolveToolsMock };
vi.mock("../../agents/agent-scope.js", async () => {
const actual = await vi.importActual<typeof import("../../agents/agent-scope.js")>(
"../../agents/agent-scope.js",
);
return {
...actual,
resolveSessionAgentId: () => "main",
};
});
vi.mock("../../agents/tools-effective-inventory.js", () => ({
resolveEffectiveToolInventory: (...args: unknown[]) => toolsTestState.resolveToolsMock(...args),
}));
vi.mock("./agent-runner-utils.js", () => ({
buildThreadingToolContext: () => toolsTestState.threadingContext,
}));
vi.mock("./reply-threading.js", () => ({
resolveReplyToMode: () => toolsTestState.replyToMode,
}));
let buildCommandTestParams: typeof import("./commands.test-harness.js").buildCommandTestParams;
let handleToolsCommand: typeof import("./commands-info.js").handleToolsCommand;
async function loadToolsHarness(options?: { resolveTools?: () => EffectiveToolInventoryResult }) {
toolsTestState.resolveToolsImpl = options?.resolveTools ?? (() => makeDefaultInventory());
toolsTestState.resolveToolsMock.mockImplementation((..._args: unknown[]) =>
toolsTestState.resolveToolsImpl(),
);
return {
buildCommandTestParams,
handleToolsCommand,
resolveToolsMock: toolsTestState.resolveToolsMock,
};
}
function buildConfig() {
@@ -102,6 +117,17 @@ function buildConfig() {
}
describe("handleToolsCommand", () => {
beforeAll(async () => {
({ buildCommandTestParams } = await import("./commands.test-harness.js"));
({ handleToolsCommand } = await import("./commands-info.js"));
});
beforeEach(() => {
toolsTestState.resolveToolsMock.mockReset();
toolsTestState.resolveToolsImpl = () => makeDefaultInventory();
setActivePluginRegistry(createTestRegistry([]));
});
it("renders a product-facing tool list", async () => {
const { buildCommandTestParams, handleToolsCommand, resolveToolsMock } =
await loadToolsHarness();

View File

@@ -1,5 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../../test/helpers/import-fresh.ts";
import type { OpenClawConfig } from "../../config/config.js";
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
import type { HandleCommandsParams } from "./commands-types.js";
@@ -487,8 +486,13 @@ function expectIdleTimeoutSetReply(
}
describe("/session idle and /session max-age", () => {
beforeEach(async () => {
if (!handleSessionCommand) {
({ handleSessionCommand } = await import("./commands-session.js"));
}
});
beforeEach(() => {
vi.resetModules();
hoisted.setThreadBindingIdleTimeoutBySessionKeyMock.mockReset();
hoisted.setThreadBindingMaxAgeBySessionKeyMock.mockReset();
hoisted.setMatrixThreadBindingIdleTimeoutBySessionKeyMock.mockReset();
@@ -499,13 +503,6 @@ describe("/session idle and /session max-age", () => {
vi.useRealTimers();
});
beforeEach(async () => {
({ handleSessionCommand } = await importFreshModule<typeof import("./commands-session.js")>(
import.meta.url,
"./commands-session.js?scope=commands-session-lifecycle",
));
});
it("sets idle timeout for the focused thread-chat session", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-02-20T00:00:00.000Z"));

View File

@@ -1,15 +1,13 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
let listSkillCommandsForAgents: typeof import("./skill-commands.js").listSkillCommandsForAgents;
let listSkillCommandsForWorkspace: typeof import("./skill-commands.js").listSkillCommandsForWorkspace;
let resolveSkillCommandInvocation: typeof import("./skill-commands.js").resolveSkillCommandInvocation;
let skillCommandsTesting: typeof import("./skill-commands.js").__testing;
type SkillCommandMockRegistrar = (path: string, factory: () => unknown) => void;
function resolveUniqueSkillCommandName(base: string, used: Set<string>): string {
let name = base;
let suffix = 2;
@@ -82,39 +80,29 @@ function buildWorkspaceSkillCommandSpecs(
});
}
function installSkillCommandTestMocks(registerMock: SkillCommandMockRegistrar) {
// Avoid importing the full chat command registry for reserved-name calculation.
registerMock("./commands-registry.js", () => ({
listChatCommands: () => [],
}));
vi.mock("./commands-registry.js", () => ({
listChatCommands: () => [],
}));
registerMock("../infra/skills-remote.js", () => ({
getRemoteSkillEligibility: () => ({}),
}));
vi.mock("../infra/skills-remote.js", () => ({
getRemoteSkillEligibility: () => ({}),
}));
// Avoid filesystem-driven skill scanning for these unit tests; we only need command naming semantics.
registerMock("../agents/skills.js", () => ({
buildWorkspaceSkillCommandSpecs,
}));
}
vi.mock("../agents/skills.js", () => ({
buildWorkspaceSkillCommandSpecs,
}));
const registerDynamicSkillCommandMock: SkillCommandMockRegistrar = (modulePath, factory) => {
vi.doMock(modulePath, factory as Parameters<typeof vi.doMock>[1]);
};
async function loadFreshSkillCommandsModuleForTest() {
vi.resetModules();
installSkillCommandTestMocks(registerDynamicSkillCommandMock);
beforeAll(async () => {
({
listSkillCommandsForAgents,
listSkillCommandsForWorkspace,
resolveSkillCommandInvocation,
__testing: skillCommandsTesting,
} = await import("./skill-commands.js"));
}
});
beforeEach(async () => {
await loadFreshSkillCommandsModuleForTest();
beforeEach(() => {
vi.clearAllMocks();
});
describe("resolveSkillCommandInvocation", () => {