mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:50:45 +00:00
test(perf): speed up slow cron infra and secrets specs
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" } }],
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user