test(perf): speed up slow cron infra and secrets specs

This commit is contained in:
Vincent Koc
2026-04-15 10:22:33 +01:00
parent 7611d41136
commit 7320dfc1ff
5 changed files with 168 additions and 87 deletions

View File

@@ -1,84 +1,86 @@
import "./isolated-agent.mocks.js";
import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as sessionOverride from "../agents/auth-profiles/session-override.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import { createCliDeps } from "./isolated-agent.delivery.test-helpers.js";
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
makeCfg,
makeJob,
withTempCronHome,
writeSessionStore,
} from "./isolated-agent.test-harness.js";
import { setupIsolatedAgentTurnMocks } from "./isolated-agent.test-setup.js";
clearFastTestEnv,
loadRunCronIsolatedAgentTurn,
makeCronSession,
resolveConfiguredModelRefMock,
resolveCronSessionMock,
resolveSessionAuthProfileOverrideMock,
resetRunCronIsolatedAgentTurnHarness,
restoreFastTestEnv,
} from "./isolated-agent/run.test-harness.js";
const runCronIsolatedAgentTurn = await loadRunCronIsolatedAgentTurn();
function makeParams(overrides?: Record<string, unknown>) {
return {
cfg: {
auth: {
profiles: {
"openrouter:default": {
type: "api_key",
provider: "openrouter",
key: "sk-or-test-key",
},
},
order: { openrouter: ["openrouter:default"] },
},
},
deps: {} as never,
job: {
id: "cron-auth-flag",
name: "Auth Flag",
enabled: true,
createdAtMs: 0,
updatedAtMs: 0,
schedule: { kind: "cron" as const, expr: "0 * * * *", tz: "UTC" },
sessionTarget: "isolated" as const,
state: {},
wakeMode: "next-heartbeat" as const,
payload: { kind: "agentTurn" as const, message: "hi" },
delivery: { mode: "none" as const },
},
message: "hi",
sessionKey: "cron:auth-flag-1",
lane: "cron" as const,
...overrides,
};
}
describe("isolated cron resolveSessionAuthProfileOverride isNewSession (#62783)", () => {
let previousFastTestEnv: string | undefined;
beforeEach(() => {
setupIsolatedAgentTurnMocks({ fast: true });
previousFastTestEnv = clearFastTestEnv();
resetRunCronIsolatedAgentTurnHarness();
resolveConfiguredModelRefMock.mockReturnValue({
provider: "openrouter",
model: "moonshotai/kimi-k2.5",
});
resolveCronSessionMock.mockReturnValue(
makeCronSession({
isNewSession: true,
sessionEntry: {
sessionId: "main-session",
updatedAt: 0,
systemSent: false,
skillsSnapshot: undefined,
},
}),
);
resolveSessionAuthProfileOverrideMock.mockResolvedValue("openrouter:default");
});
afterEach(() => {
vi.restoreAllMocks();
restoreFastTestEnv(previousFastTestEnv);
});
it("passes isNewSession=false when sessionTarget is isolated", async () => {
const spy = vi.spyOn(sessionOverride, "resolveSessionAuthProfileOverride");
spy.mockResolvedValue("openrouter:default");
await runCronIsolatedAgentTurn(makeParams());
await withTempCronHome(async (home) => {
const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" });
const agentDir = path.join(home, ".openclaw", "agents", "main", "agent");
await fs.mkdir(agentDir, { recursive: true });
await fs.writeFile(
path.join(agentDir, "auth-profiles.json"),
JSON.stringify({
version: 1,
profiles: {
"openrouter:default": {
type: "api_key",
provider: "openrouter",
key: "sk-or-test-key",
},
},
order: { openrouter: ["openrouter:default"] },
}),
"utf-8",
);
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
payloads: [{ text: "ok" }],
meta: {
durationMs: 1,
agentMeta: { sessionId: "s", provider: "openrouter", model: "kimi-k2.5" },
},
});
const cfg = makeCfg(home, storePath, {
agents: {
defaults: {
model: { primary: "openrouter/moonshotai/kimi-k2.5" },
workspace: path.join(home, "openclaw"),
},
},
});
await runCronIsolatedAgentTurn({
cfg,
deps: createCliDeps(),
job: {
...makeJob({ kind: "agentTurn", message: "hi" }),
sessionTarget: "isolated",
delivery: { mode: "none" },
},
message: "hi",
sessionKey: "cron:auth-flag-1",
lane: "cron",
});
});
const openRouterCall = spy.mock.calls.find((c) => c[0]?.provider === "openrouter");
const openRouterCall = resolveSessionAuthProfileOverrideMock.mock.calls.find(
(call) => call[0]?.provider === "openrouter",
);
expect(
openRouterCall,
"resolveSessionAuthProfileOverride was not called with provider openrouter",

View File

@@ -2,12 +2,8 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
isWhatsAppGroupJid,
normalizeWhatsAppTarget,
} from "../../test/helpers/channels/command-contract.js";
import { whatsappOutbound } from "../../test/helpers/infra/deliver-test-outbounds.js";
import { HEARTBEAT_PROMPT } from "../auto-reply/heartbeat.js";
import type { ChannelOutboundAdapter } from "../channels/plugins/types.public.js";
import type { OpenClawConfig } from "../config/config.js";
import {
resolveAgentIdFromSessionKey,
@@ -39,6 +35,85 @@ let testRegistry: ReturnType<typeof getActivePluginRegistry> | null = null;
let fixtureRoot = "";
let fixtureCount = 0;
function normalizeWhatsAppTargetForTest(raw: string): string | null {
const trimmed = raw
.trim()
.replace(/^whatsapp:/i, "")
.trim();
if (!trimmed) {
return null;
}
const lowered = trimmed.toLowerCase().replace(/\s+/gu, "");
if (/^\d+@g\.us$/u.test(lowered)) {
return lowered;
}
const digits = trimmed.replace(/\D/gu, "");
const normalized = digits ? `+${digits}` : "";
return /^\+\d{7,15}$/u.test(normalized) ? normalized : null;
}
function isWhatsAppGroupJidForTest(raw: string): boolean {
return /^\d+@g\.us$/u.test(raw.trim().toLowerCase());
}
const whatsappOutboundForTest: ChannelOutboundAdapter = {
deliveryMode: "gateway",
sendText: async ({ cfg, to, text, accountId, deps }) => {
const sender = deps?.whatsapp as
| ((
to: string,
text: string,
options?: Record<string, unknown>,
) => Promise<{ messageId: string } & Record<string, unknown>>)
| undefined;
if (!sender) {
throw new Error("missing whatsapp sender");
}
const result = await sender(to, text, {
verbose: false,
cfg,
accountId: accountId ?? undefined,
});
return {
channel: "whatsapp",
...result,
};
},
sendMedia: async ({
cfg,
to,
text,
mediaUrl,
mediaLocalRoots,
mediaReadFile,
accountId,
deps,
}) => {
const sender = deps?.whatsapp as
| ((
to: string,
text: string,
options?: Record<string, unknown>,
) => Promise<{ messageId: string } & Record<string, unknown>>)
| undefined;
if (!sender) {
throw new Error("missing whatsapp sender");
}
const result = await sender(to, text, {
verbose: false,
cfg,
mediaUrl,
mediaLocalRoots,
mediaReadFile,
accountId: accountId ?? undefined,
});
return {
channel: "whatsapp",
...result,
};
},
};
function resolveWhatsAppTargetForTest(params: {
to: string | null | undefined;
allowFrom: Array<string | number> | null | undefined;
@@ -50,9 +125,9 @@ function resolveWhatsAppTargetForTest(params: {
const hasWildcard = allowListRaw.includes("*");
const allowList = allowListRaw
.filter((entry) => entry !== "*")
.map((entry) => normalizeWhatsAppTarget(entry))
.map((entry) => normalizeWhatsAppTargetForTest(entry))
.filter((entry): entry is string => Boolean(entry));
const normalizedTarget = normalizeWhatsAppTarget(trimmed);
const normalizedTarget = normalizeWhatsAppTargetForTest(trimmed);
if (!normalizedTarget) {
return {
@@ -60,7 +135,7 @@ function resolveWhatsAppTargetForTest(params: {
error: new Error('Missing target for WhatsApp; expected "<E.164|group JID>".'),
};
}
if (isWhatsAppGroupJid(normalizedTarget)) {
if (isWhatsAppGroupJidForTest(normalizedTarget)) {
return { ok: true as const, to: normalizedTarget };
}
if (hasWildcard || allowList.length === 0 || allowList.includes(normalizedTarget)) {
@@ -86,7 +161,7 @@ beforeAll(async () => {
const whatsappPlugin = createOutboundTestPlugin({
id: "whatsapp",
outbound: {
...whatsappOutbound,
...whatsappOutboundForTest,
resolveTarget: ({ to, allowFrom }) =>
resolveWhatsAppTargetForTest({
to,

View File

@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const resolveDefaultAgentIdMock = vi.hoisted(() => vi.fn());
const resolveAgentWorkspaceDirMock = vi.hoisted(() => vi.fn());
const getLoadedChannelPluginMock = vi.hoisted(() => vi.fn());
const getChannelPluginMock = vi.hoisted(() => vi.fn());
const applyPluginAutoEnableMock = vi.hoisted(() => vi.fn());
const resolveRuntimePluginRegistryMock = vi.hoisted(() => vi.fn());
@@ -17,6 +18,7 @@ vi.mock("../../agents/agent-scope.js", () => ({
}));
vi.mock("../../channels/plugins/index.js", () => ({
getLoadedChannelPlugin: (...args: unknown[]) => getLoadedChannelPluginMock(...args),
getChannelPlugin: (...args: unknown[]) => getChannelPluginMock(...args),
}));
@@ -67,6 +69,7 @@ describe("outbound channel resolution", () => {
beforeEach(async () => {
resolveDefaultAgentIdMock.mockReset();
resolveAgentWorkspaceDirMock.mockReset();
getLoadedChannelPluginMock.mockReset();
getChannelPluginMock.mockReset();
applyPluginAutoEnableMock.mockReset();
resolveRuntimePluginRegistryMock.mockReset();
@@ -107,7 +110,7 @@ describe("outbound channel resolution", () => {
it("returns the already-registered plugin without bootstrapping", async () => {
const plugin = { id: "telegram" };
getChannelPluginMock.mockReturnValueOnce(plugin);
getLoadedChannelPluginMock.mockReturnValueOnce(plugin);
const channelResolution = await importChannelResolution("existing-plugin");
expect(
@@ -140,7 +143,7 @@ describe("outbound channel resolution", () => {
it("bootstraps plugins once per registry key and returns the newly loaded plugin", async () => {
const plugin = { id: "telegram" };
getChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
getLoadedChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
const channelResolution = await importChannelResolution("bootstrap-success");
expect(
@@ -162,7 +165,7 @@ describe("outbound channel resolution", () => {
it("bootstraps when the active registry has other channels but not the requested one", async () => {
const plugin = { id: "telegram" };
getChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
getLoadedChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: { id: "discord" } }],
});

View File

@@ -1,4 +1,4 @@
import { getChannelPlugin } from "../../channels/plugins/index.js";
import { getChannelPlugin, getLoadedChannelPlugin } from "../../channels/plugins/index.js";
import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { getActivePluginRegistry } from "../../plugins/runtime.js";
@@ -58,8 +58,9 @@ export function resolveOutboundChannelPlugin(params: {
return undefined;
}
const resolveLoaded = () => getLoadedChannelPlugin(normalized);
const resolve = () => getChannelPlugin(normalized);
const current = resolve();
const current = resolveLoaded();
if (current) {
return current;
}
@@ -69,5 +70,5 @@ export function resolveOutboundChannelPlugin(params: {
}
maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });
return resolve() ?? resolveDirectFromActiveRegistry(normalized);
return resolveLoaded() ?? resolveDirectFromActiveRegistry(normalized) ?? resolve();
}

View File

@@ -416,7 +416,7 @@ async function prepareConfigCoverageSnapshot(params: {
skipConfigCollectors?: boolean;
}) {
await ensureConfigCoverageRuntimeLoaded();
const sourceConfig = structuredClone(params.config);
const sourceConfig = params.config;
const resolvedConfig = structuredClone(params.config);
const context = createResolverContext({
sourceConfig,
@@ -468,7 +468,7 @@ async function prepareAuthCoverageSnapshot(params: {
loadAuthStore: (agentDir?: string) => AuthProfileStore;
}) {
await ensureAuthCoverageRuntimeLoaded();
const sourceConfig = structuredClone(params.config);
const sourceConfig = params.config;
const context = createResolverContext({
sourceConfig,
env: params.env,