mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(test): repair split agent shard runs
This commit is contained in:
@@ -12,6 +12,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Tests: suppress the current Rolldown plugin timing warning format in the Vitest wrapper so tiny focused runs do not drown useful stderr in repeated build-timing noise.
|
||||
- Models/OpenRouter: use endpoint-specific OpenRouter context limits from `top_provider` metadata so provider-routed models no longer overstate available context. (#85949) Thanks @TurboTheTurtle.
|
||||
- Crabbox: sync clean sparse-checkout remote changed gates from a temporary full checkout with local-only commits overlaid as worktree changes so git-backed script checks can seed the runner repository.
|
||||
- Agents: avoid loading bundled channel plugins while resolving completion delivery policy and queue defaults on subagent handoff paths.
|
||||
- Tests: allow split Vitest config shards through the explicit-target preflight so CI shard jobs run their intended projects.
|
||||
- Tests: make startup memory and startup bench smoke scripts build CLI startup artifacts when run from a fresh source checkout.
|
||||
- iMessage: mark authorized slash-command turns as text-sourced commands so `/status`, `/new`, and `/restart` acknowledgements return to the source conversation. (#82642) thanks @homer-byte.
|
||||
- Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`.
|
||||
|
||||
@@ -47,11 +47,11 @@ import { isCiLikeEnv, resolveLocalFullSuiteProfile } from "./lib/vitest-local-sc
|
||||
import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./run-vitest.mjs";
|
||||
|
||||
const DEFAULT_VITEST_CONFIG = "test/vitest/vitest.unit.config.ts";
|
||||
const AGENTS_VITEST_CONFIG = "test/vitest/vitest.agents.config.ts";
|
||||
const AGENTS_CORE_VITEST_CONFIG = "test/vitest/vitest.agents-core.config.ts";
|
||||
const AGENTS_PI_EMBEDDED_VITEST_CONFIG = "test/vitest/vitest.agents-pi-embedded.config.ts";
|
||||
const AGENTS_SUPPORT_VITEST_CONFIG = "test/vitest/vitest.agents-support.config.ts";
|
||||
const AGENTS_TOOLS_VITEST_CONFIG = "test/vitest/vitest.agents-tools.config.ts";
|
||||
const AGENTS_VITEST_CONFIG = "test/vitest/vitest.agents.config.ts";
|
||||
const ACP_VITEST_CONFIG = "test/vitest/vitest.acp.config.ts";
|
||||
const AUTO_REPLY_CORE_VITEST_CONFIG = "test/vitest/vitest.auto-reply-core.config.ts";
|
||||
const AUTO_REPLY_VITEST_CONFIG = "test/vitest/vitest.auto-reply.config.ts";
|
||||
@@ -234,11 +234,11 @@ const FS_MODULE_CACHE_PATH_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_PATH";
|
||||
const CHANGED_ARGS_PATTERN = /^--changed(?:=(.+))?$/u;
|
||||
const VITEST_CONFIG_BY_KIND = {
|
||||
acp: ACP_VITEST_CONFIG,
|
||||
agent: AGENTS_VITEST_CONFIG,
|
||||
agentCore: AGENTS_CORE_VITEST_CONFIG,
|
||||
agentPiEmbedded: AGENTS_PI_EMBEDDED_VITEST_CONFIG,
|
||||
agentSupport: AGENTS_SUPPORT_VITEST_CONFIG,
|
||||
agentTools: AGENTS_TOOLS_VITEST_CONFIG,
|
||||
agent: AGENTS_VITEST_CONFIG,
|
||||
autoReplyCore: AUTO_REPLY_CORE_VITEST_CONFIG,
|
||||
autoReplyReply: AUTO_REPLY_REPLY_VITEST_CONFIG,
|
||||
autoReplyTopLevel: AUTO_REPLY_TOP_LEVEL_VITEST_CONFIG,
|
||||
@@ -1724,6 +1724,10 @@ export function buildVitestRunPlans(
|
||||
"autoReplyCore",
|
||||
"autoReplyReply",
|
||||
"autoReplyTopLevel",
|
||||
"agentCore",
|
||||
"agentPiEmbedded",
|
||||
"agentSupport",
|
||||
"agentTools",
|
||||
"agent",
|
||||
"plugin",
|
||||
"ui",
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export function resolvePathEnvKey(env: NodeJS.ProcessEnv): string;
|
||||
|
||||
export function buildCmdExeCommandLine(command: string, args: string[]): string;
|
||||
|
||||
@@ -305,9 +305,7 @@ describe("exec approval followup", () => {
|
||||
expect(callGatewayTool).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses safe denied copy for nested-parentheses denial metadata when session resume fails", async () => {
|
||||
vi.mocked(callGatewayTool).mockRejectedValueOnce(new Error("session missing"));
|
||||
|
||||
it("uses safe direct denied copy for nested-parentheses denial metadata without resuming the session", async () => {
|
||||
await sendExecApprovalFollowup({
|
||||
approvalId: "req-denied-resume-failed-nested",
|
||||
sessionKey: "agent:main:telegram:-100123",
|
||||
@@ -319,13 +317,11 @@ describe("exec approval followup", () => {
|
||||
"Exec denied (gateway id=req-denied-resume-failed-nested, approval-timeout (allowlist-miss)): uname -a",
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
content:
|
||||
"Automatic session resume failed, so sending the status directly.\n\nCommand did not run: approval timed out.",
|
||||
idempotencyKey: "exec-approval-followup:req-denied-resume-failed-nested",
|
||||
}),
|
||||
);
|
||||
expectDirectSend({
|
||||
content: "Command did not run: approval timed out.",
|
||||
idempotencyKey: "exec-approval-followup:req-denied-resume-failed-nested",
|
||||
});
|
||||
expect(callGatewayTool).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("suppresses denied followups for subagent sessions", async () => {
|
||||
|
||||
@@ -178,7 +178,6 @@ export async function ensureOpenClawModelsJson(
|
||||
config: cfg,
|
||||
env: createConfigRuntimeEnv(cfg),
|
||||
...(workspaceDir ? { workspaceDir } : {}),
|
||||
allowWorkspaceScopedCurrent: workspaceDir === undefined,
|
||||
});
|
||||
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg);
|
||||
const targetPath = path.join(agentDir, "models.json");
|
||||
|
||||
@@ -44,6 +44,7 @@ vi.mock("../pi-model-discovery.js", () => ({
|
||||
}));
|
||||
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resetModelDiscoveryCacheForTest } from "./model-discovery-cache.js";
|
||||
import {
|
||||
expectResolvedForwardCompatFallbackResult,
|
||||
expectUnknownModelErrorResult,
|
||||
@@ -57,6 +58,7 @@ import {
|
||||
} from "./model.test-harness.js";
|
||||
|
||||
beforeEach(() => {
|
||||
resetModelDiscoveryCacheForTest();
|
||||
resetMockDiscoverModels(discoverModels);
|
||||
});
|
||||
|
||||
|
||||
@@ -212,9 +212,13 @@ describe("ensureSandboxContainer config-hash recreation", () => {
|
||||
});
|
||||
|
||||
it("recreates shared container when array-order change alters hash", async () => {
|
||||
const workspaceDir = "/tmp/workspace";
|
||||
const oldCfg = createSandboxConfig(["1.1.1.1", "8.8.8.8"]);
|
||||
const newCfg = createSandboxConfig(["8.8.8.8", "1.1.1.1"]);
|
||||
const workspaceDir = makeTempDir();
|
||||
const oldCfg = createSandboxConfig(["1.1.1.1", "8.8.8.8"], [
|
||||
`${workspaceDir}:/workspace:rw`,
|
||||
]);
|
||||
const newCfg = createSandboxConfig(["8.8.8.8", "1.1.1.1"], [
|
||||
`${workspaceDir}:/workspace:rw`,
|
||||
]);
|
||||
|
||||
const oldHash = computeSandboxConfigHash({
|
||||
docker: oldCfg.docker,
|
||||
@@ -270,11 +274,12 @@ describe("ensureSandboxContainer config-hash recreation", () => {
|
||||
});
|
||||
|
||||
it("recreates shared container when previously filtered explicit env becomes allowed", async () => {
|
||||
const workspaceDir = "/tmp/workspace";
|
||||
const workspaceDir = makeTempDir();
|
||||
const cfg = createSandboxConfig(["1.1.1.1"], undefined, "rw", {
|
||||
LANG: "C.UTF-8",
|
||||
GEMINI_API_KEY: "dummy-gemini",
|
||||
});
|
||||
cfg.docker.binds = [`${workspaceDir}:/workspace:rw`];
|
||||
|
||||
const oldHash = computeSandboxConfigHash({
|
||||
docker: cfg.docker,
|
||||
@@ -316,10 +321,12 @@ describe("ensureSandboxContainer config-hash recreation", () => {
|
||||
});
|
||||
|
||||
it("applies custom binds after workspace mounts so overlapping binds can override", async () => {
|
||||
const workspaceDir = "/tmp/workspace";
|
||||
const workspaceDir = makeTempDir();
|
||||
const customRoot = makeTempDir();
|
||||
const customUserFile = path.join(customRoot, "USER.md");
|
||||
const cfg = createSandboxConfig(
|
||||
["1.1.1.1"],
|
||||
["/tmp/workspace-shared/USER.md:/workspace/USER.md:ro"],
|
||||
[`${customUserFile}:/workspace/USER.md:ro`],
|
||||
);
|
||||
cfg.docker.dangerouslyAllowExternalBindSources = true;
|
||||
const expectedHash = computeSandboxConfigHash({
|
||||
@@ -346,8 +353,8 @@ describe("ensureSandboxContainer config-hash recreation", () => {
|
||||
expect(createCall.args).toContain(`openclaw.configHash=${expectedHash}`);
|
||||
|
||||
const bindArgs = collectDockerFlagValues(createCall.args, "-v");
|
||||
const workspaceMountIdx = bindArgs.indexOf("/tmp/workspace:/workspace:z");
|
||||
const customMountIdx = bindArgs.indexOf("/tmp/workspace-shared/USER.md:/workspace/USER.md:ro");
|
||||
const workspaceMountIdx = bindArgs.indexOf(`${workspaceDir}:/workspace:z`);
|
||||
const customMountIdx = bindArgs.indexOf(`${customUserFile}:/workspace/USER.md:ro`);
|
||||
expect(workspaceMountIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(customMountIdx).toBeGreaterThan(workspaceMountIdx);
|
||||
});
|
||||
|
||||
@@ -51,17 +51,18 @@ describe("appendWorkspaceMountArgs", () => {
|
||||
});
|
||||
|
||||
it("omits agent workspace mount when paths are identical", () => {
|
||||
const workspaceDir = makeTempWorkspace();
|
||||
const args: string[] = [];
|
||||
appendWorkspaceMountArgs({
|
||||
args,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentWorkspaceDir: "/tmp/workspace",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir: workspaceDir,
|
||||
workdir: "/workspace",
|
||||
workspaceAccess: "rw",
|
||||
});
|
||||
|
||||
const mounts = args.filter((arg) => arg.startsWith("/tmp/"));
|
||||
expect(mounts).toEqual(["/tmp/workspace:/workspace:z"]);
|
||||
const mounts = args.filter((arg) => arg.startsWith(workspaceDir));
|
||||
expect(mounts).toEqual([`${workspaceDir}:/workspace:z`]);
|
||||
});
|
||||
|
||||
it("marks split agent workspace mounts shared for SELinux", () => {
|
||||
|
||||
@@ -247,9 +247,11 @@ async function deliverTelegramDirectMessageCompletion(params: {
|
||||
sendMessage?: typeof runtimeSendMessage;
|
||||
internalEvents?: AgentInternalEvent[];
|
||||
isActive?: boolean;
|
||||
requesterSessionId?: string | null;
|
||||
queueEmbeddedPiMessageWithOutcome?: QueueEmbeddedPiMessageWithOutcome;
|
||||
requesterSessionKey?: string;
|
||||
sourceTool?: string;
|
||||
runtimeConfig?: Record<string, unknown>;
|
||||
origin?: {
|
||||
channel: "telegram";
|
||||
to: string;
|
||||
@@ -266,10 +268,13 @@ async function deliverTelegramDirectMessageCompletion(params: {
|
||||
testing.setDepsForTest({
|
||||
callGateway: params.callGateway,
|
||||
getRequesterSessionActivity: () => ({
|
||||
sessionId: "requester-session-telegram",
|
||||
sessionId:
|
||||
params.requesterSessionId === null
|
||||
? undefined
|
||||
: (params.requesterSessionId ?? "requester-session-telegram"),
|
||||
isActive: params.isActive === true,
|
||||
}),
|
||||
getRuntimeConfig: () => ({}) as never,
|
||||
getRuntimeConfig: () => (params.runtimeConfig ?? {}) as never,
|
||||
sendMessage: params.sendMessage ?? runtimeSendMessage,
|
||||
...(params.queueEmbeddedPiMessageWithOutcome
|
||||
? { queueEmbeddedPiMessageWithOutcome: params.queueEmbeddedPiMessageWithOutcome }
|
||||
@@ -1416,13 +1421,35 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
});
|
||||
|
||||
it("reports failure for Telegram DMs when announce-agent delivery fails", async () => {
|
||||
const callGateway = vi.fn(async () => {
|
||||
throw new Error("UNAVAILABLE: requester wake failed");
|
||||
}) as unknown as typeof runtimeCallGateway;
|
||||
const callGateway = createGatewayMock({
|
||||
result: {
|
||||
deliveryStatus: {
|
||||
status: "failed",
|
||||
errorMessage: "requester wake failed",
|
||||
},
|
||||
},
|
||||
});
|
||||
const sendMessage = createSendMessageMock();
|
||||
const result = await deliverTelegramDirectMessageCompletion({
|
||||
callGateway,
|
||||
sendMessage,
|
||||
queueEmbeddedPiMessageWithOutcome: createQueueOutcomeMock(false),
|
||||
requesterSessionId: null,
|
||||
requesterSessionKey: "agent:main:telegram:direct:123456789",
|
||||
origin: {
|
||||
channel: "telegram",
|
||||
to: "direct:123456789",
|
||||
accountId: "bot-1",
|
||||
},
|
||||
runtimeConfig: {
|
||||
agents: {
|
||||
defaults: {
|
||||
subagents: {
|
||||
announceTimeoutMs: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
internalEvents: [
|
||||
{
|
||||
type: "task_completion",
|
||||
@@ -1442,7 +1469,7 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
expectRecordFields(result, {
|
||||
delivered: false,
|
||||
path: "direct",
|
||||
error: "UNAVAILABLE: requester wake failed",
|
||||
error: "requester wake failed",
|
||||
});
|
||||
expect(sendMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -1460,6 +1487,15 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
sendMessage,
|
||||
isActive: true,
|
||||
queueEmbeddedPiMessageWithOutcome,
|
||||
runtimeConfig: {
|
||||
agents: {
|
||||
defaults: {
|
||||
subagents: {
|
||||
announceTimeoutMs: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
internalEvents: [
|
||||
{
|
||||
type: "task_completion",
|
||||
@@ -1496,7 +1532,7 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
|
||||
steeringMode: "all",
|
||||
debounceMs: 500,
|
||||
waitForTranscriptCommit: true,
|
||||
deliveryTimeoutMs: 120_000,
|
||||
deliveryTimeoutMs: 10,
|
||||
},
|
||||
);
|
||||
expect(callGateway).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -60,6 +60,7 @@ vi.mock("./subagent-announce.runtime.js", () => ({
|
||||
isEmbeddedPiRunActive: (sessionId: string) => isEmbeddedPiRunActiveMock(sessionId),
|
||||
getRuntimeConfig: () => mockConfig,
|
||||
loadSessionStore: (storePath: string) => loadSessionStoreMock(storePath),
|
||||
readSessionMessagesAsync: vi.fn(async () => []),
|
||||
readSessionEntry: (storePath: string, sessionKey: string) =>
|
||||
(loadSessionStoreMock(storePath) as Record<string, unknown>)[sessionKey],
|
||||
resolveAgentIdFromSessionKey: (sessionKey: string) =>
|
||||
|
||||
@@ -191,6 +191,7 @@ vi.mock("./subagent-announce.runtime.js", () => ({
|
||||
},
|
||||
getRuntimeConfig: () => configOverride,
|
||||
loadSessionStore: vi.fn(() => sessionStore),
|
||||
readSessionMessagesAsync: vi.fn(async () => []),
|
||||
readSessionEntry: (_storePath: string, sessionKey: string) => sessionStore[sessionKey],
|
||||
resolveAgentIdFromSessionKey: () => "main",
|
||||
resolveStorePath: () => "/tmp/sessions-main.json",
|
||||
|
||||
@@ -82,7 +82,6 @@ export function loadCapabilityMetadataSnapshot(params: {
|
||||
config: params.config ?? {},
|
||||
env: params.env ?? process.env,
|
||||
...(workspaceDir ? { workspaceDir } : {}),
|
||||
allowWorkspaceScopedCurrent: workspaceDir === undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
completionRequiresMessageToolDelivery,
|
||||
resolveCompletionChatType,
|
||||
@@ -6,13 +6,6 @@ import {
|
||||
} from "./completion-delivery-policy.js";
|
||||
|
||||
describe("completion delivery policy", () => {
|
||||
beforeAll(() => {
|
||||
resolveCompletionChatType({ requesterSessionKey: "agent:main:whatsapp:warmup@g.us" });
|
||||
resolveCompletionChatType({
|
||||
requesterSessionKey: "agent:main:discord:guild-warmup:channel-warmup",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "canonical group key",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { normalizeChatType, type ChatType } from "../../channels/chat-type.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { deriveSessionChatType } from "../../sessions/session-chat-type.js";
|
||||
import { deriveSessionChatTypeFromKey } from "../../sessions/session-chat-type-shared.js";
|
||||
import type { DeliveryContext } from "../../utils/delivery-context.types.js";
|
||||
import { resolveSourceReplyDeliveryMode } from "./source-reply-delivery-mode.js";
|
||||
|
||||
@@ -26,7 +26,7 @@ export function resolveCompletionChatType(params: {
|
||||
}
|
||||
|
||||
for (const key of [params.targetRequesterSessionKey, params.requesterSessionKey]) {
|
||||
const derived = deriveSessionChatType(key);
|
||||
const derived = deriveSessionChatTypeFromKey(key);
|
||||
if (derived !== "unknown") {
|
||||
return derived;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export function completionRequiresMessageToolDelivery(params: {
|
||||
export function shouldRouteCompletionThroughRequesterSession(
|
||||
sessionKey: string | undefined | null,
|
||||
): boolean {
|
||||
const chatType = deriveSessionChatType(sessionKey);
|
||||
const chatType = deriveSessionChatTypeFromKey(sessionKey);
|
||||
return chatType === "group" || chatType === "channel";
|
||||
}
|
||||
|
||||
|
||||
42
src/auto-reply/reply/queue/settings-runtime.test.ts
Normal file
42
src/auto-reply/reply/queue/settings-runtime.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
|
||||
const getLoadedChannelPluginMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("../../../channels/plugins/index.js", () => ({
|
||||
getLoadedChannelPlugin: getLoadedChannelPluginMock,
|
||||
}));
|
||||
|
||||
describe("resolveQueueSettings runtime defaults", () => {
|
||||
it("uses defaults from already-loaded channel plugins", async () => {
|
||||
getLoadedChannelPluginMock.mockReturnValueOnce({
|
||||
defaults: {
|
||||
queue: {
|
||||
debounceMs: 125,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { resolveQueueSettings } = await import("./settings-runtime.js");
|
||||
|
||||
expect(resolveQueueSettings({ cfg: {} as OpenClawConfig, channel: "demo" })).toEqual({
|
||||
mode: "steer",
|
||||
debounceMs: 125,
|
||||
cap: 20,
|
||||
dropPolicy: "summarize",
|
||||
});
|
||||
expect(getLoadedChannelPluginMock).toHaveBeenCalledWith("demo");
|
||||
});
|
||||
|
||||
it("falls back without loading bundled channel plugins", async () => {
|
||||
getLoadedChannelPluginMock.mockReturnValueOnce(undefined);
|
||||
const { resolveQueueSettings } = await import("./settings-runtime.js");
|
||||
|
||||
expect(resolveQueueSettings({ cfg: {} as OpenClawConfig, channel: "telegram" })).toEqual({
|
||||
mode: "steer",
|
||||
debounceMs: 500,
|
||||
cap: 20,
|
||||
dropPolicy: "summarize",
|
||||
});
|
||||
expect(getLoadedChannelPluginMock).toHaveBeenCalledWith("telegram");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getChannelPlugin } from "../../../channels/plugins/index.js";
|
||||
import { getLoadedChannelPlugin } from "../../../channels/plugins/index.js";
|
||||
import { normalizeOptionalLowercaseString } from "../../../shared/string-coerce.js";
|
||||
import { resolveQueueSettings as resolveQueueSettingsCore } from "./settings.js";
|
||||
import type { QueueSettings, ResolveQueueSettingsParams } from "./types.js";
|
||||
@@ -7,7 +7,7 @@ function resolvePluginDebounce(channelKey: string | undefined): number | undefin
|
||||
if (!channelKey) {
|
||||
return undefined;
|
||||
}
|
||||
const plugin = getChannelPlugin(channelKey);
|
||||
const plugin = getLoadedChannelPlugin(channelKey);
|
||||
const value = plugin?.defaults?.queue?.debounceMs;
|
||||
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
@@ -106,8 +107,23 @@ const {
|
||||
) => number;
|
||||
};
|
||||
|
||||
const VITEST_CLI_ENTRY = path.join(process.cwd(), "node_modules", "vitest", "vitest.mjs");
|
||||
const VITEST_NODE_PREFIX = ["exec", "node", "--no-maglev", VITEST_CLI_ENTRY];
|
||||
const require = createRequire(import.meta.url);
|
||||
const resolveExpectedVitestCliEntry = () => {
|
||||
const vitestPackageJson = require.resolve("vitest/package.json");
|
||||
return path.join(path.dirname(vitestPackageJson), "vitest.mjs");
|
||||
};
|
||||
const resolveExpectedVitestNodeArgs = (env: NodeJS.ProcessEnv) =>
|
||||
["1", "true", "yes", "on"].includes(
|
||||
env.OPENCLAW_VITEST_ENABLE_MAGLEV?.trim().toLowerCase() ?? "",
|
||||
)
|
||||
? []
|
||||
: ["--no-maglev"];
|
||||
const VITEST_NODE_PREFIX = [
|
||||
"exec",
|
||||
"node",
|
||||
...resolveExpectedVitestNodeArgs(process.env),
|
||||
resolveExpectedVitestCliEntry(),
|
||||
];
|
||||
|
||||
describe("test-projects args", () => {
|
||||
it("drops a pnpm passthrough separator while preserving targeted filters", () => {
|
||||
@@ -1058,6 +1074,39 @@ describe("test-projects args", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("accepts split CI Vitest config targets routed as whole config runs", () => {
|
||||
expect(
|
||||
findUnmatchedExplicitTestTargets([
|
||||
"test/vitest/vitest.agents-core.config.ts",
|
||||
"test/vitest/vitest.agents-pi-embedded.config.ts",
|
||||
"test/vitest/vitest.agents-support.config.ts",
|
||||
"test/vitest/vitest.agents-tools.config.ts",
|
||||
]),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps split CI Vitest config targets on their own configs", () => {
|
||||
expect(
|
||||
buildVitestRunPlans([
|
||||
"test/vitest/vitest.agents-core.config.ts",
|
||||
"test/vitest/vitest.agents-tools.config.ts",
|
||||
]),
|
||||
).toEqual([
|
||||
{
|
||||
config: "test/vitest/vitest.agents-core.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: null,
|
||||
watchMode: false,
|
||||
},
|
||||
{
|
||||
config: "test/vitest/vitest.agents-tools.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: null,
|
||||
watchMode: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("accepts sentinel targets routed as whole config runs", () => {
|
||||
expect(findUnmatchedExplicitTestTargets(["ui/src/test-helpers/control-ui-e2e.ts"])).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -6,9 +6,12 @@ type GlobalWithOpenAiCodexTokenRefreshTestHook = typeof globalThis & {
|
||||
};
|
||||
|
||||
vi.mock("@earendil-works/pi-ai/oauth", () => ({
|
||||
getOAuthProvider: () => undefined,
|
||||
getOAuthApiKey: () => undefined,
|
||||
getOAuthProviders: () => [],
|
||||
loginOpenAICodex: vi.fn(),
|
||||
registerOAuthProvider: vi.fn(),
|
||||
resetOAuthProviders: vi.fn(),
|
||||
refreshOpenAICodexToken: vi.fn((...args: unknown[]) =>
|
||||
(globalThis as GlobalWithOpenAiCodexTokenRefreshTestHook)[openAiCodexTokenRefreshTestHook]?.(
|
||||
...args,
|
||||
|
||||
Reference in New Issue
Block a user