fix(test): restore bundled loader coverage

This commit is contained in:
Peter Steinberger
2026-04-06 18:14:37 +01:00
parent 96b39e01b4
commit 348cd6b17a
15 changed files with 207 additions and 58 deletions

View File

@@ -1,37 +1,9 @@
import {
applyAgentDefaultModelPrimary,
resolveAgentModelPrimaryValue,
export {
applyOpencodeZenModelDefault,
OPENCODE_ZEN_DEFAULT_MODEL,
} from "openclaw/plugin-sdk/provider-onboard";
import { OPENCODE_ZEN_DEFAULT_MODEL_REF } from "./onboard.js";
export {
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
OPENCODE_ZEN_DEFAULT_MODEL_REF,
} from "./onboard.js";
const LEGACY_OPENCODE_ZEN_DEFAULT_MODELS = new Set([
"opencode/claude-opus-4-5",
"opencode-zen/claude-opus-4-5",
]);
export const OPENCODE_ZEN_DEFAULT_MODEL = OPENCODE_ZEN_DEFAULT_MODEL_REF;
export function applyOpencodeZenModelDefault(
cfg: import("openclaw/plugin-sdk/provider-onboard").OpenClawConfig,
): {
next: import("openclaw/plugin-sdk/provider-onboard").OpenClawConfig;
changed: boolean;
} {
const current = resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model);
const normalizedCurrent =
current && LEGACY_OPENCODE_ZEN_DEFAULT_MODELS.has(current)
? OPENCODE_ZEN_DEFAULT_MODEL
: current;
if (normalizedCurrent === OPENCODE_ZEN_DEFAULT_MODEL) {
return { next: cfg, changed: false };
}
return {
next: applyAgentDefaultModelPrimary(cfg, OPENCODE_ZEN_DEFAULT_MODEL),
changed: true,
};
}

View File

@@ -68,6 +68,8 @@ setCliRunnerExecuteTestDeps({
setCliRunnerPrepareTestDeps({
makeBootstrapWarn: () => () => {},
resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock,
resolveHeartbeatPrompt: async () => "",
resolveOpenClawDocsPath: async () => null,
});
type MockRunExit = {
@@ -369,6 +371,8 @@ export function restoreCliRunnerPrepareTestDeps() {
setCliRunnerPrepareTestDeps({
makeBootstrapWarn: () => () => {},
resolveBootstrapContextForRun: hoisted.resolveBootstrapContextForRunMock,
resolveHeartbeatPrompt: async () => "",
resolveOpenClawDocsPath: async () => null,
});
}

View File

@@ -1,4 +1,3 @@
import { resolveHeartbeatPrompt } from "../../auto-reply/heartbeat.js";
import {
createMcpLoopbackServerConfig,
getActiveMcpLoopbackRuntime,
@@ -17,7 +16,6 @@ import {
import { resolveCliAuthEpoch } from "../cli-auth-epoch.js";
import { resolveCliBackendConfig } from "../cli-backends.js";
import { hashCliSessionText, resolveCliSessionReuse } from "../cli-session.js";
import { resolveOpenClawDocsPath } from "../docs-path.js";
import {
resolveBootstrapMaxChars,
resolveBootstrapPromptTruncationWarningMode,
@@ -35,6 +33,12 @@ const prepareDeps = {
resolveBootstrapContextForRun: resolveBootstrapContextForRunImpl,
getActiveMcpLoopbackRuntime,
createMcpLoopbackServerConfig,
resolveHeartbeatPrompt: async (
prompt: Parameters<typeof import("../../auto-reply/heartbeat.js").resolveHeartbeatPrompt>[0],
) => (await import("../../auto-reply/heartbeat.js")).resolveHeartbeatPrompt(prompt),
resolveOpenClawDocsPath: async (
params: Parameters<typeof import("../docs-path.js").resolveOpenClawDocsPath>[0],
) => (await import("../docs-path.js")).resolveOpenClawDocsPath(params),
};
export function setCliRunnerPrepareTestDeps(overrides: Partial<typeof prepareDeps>): void {
@@ -146,9 +150,9 @@ export async function prepareCliRunContext(
}
const heartbeatPrompt =
sessionAgentId === defaultAgentId
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
? await prepareDeps.resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
: undefined;
const docsPath = await resolveOpenClawDocsPath({
const docsPath = await prepareDeps.resolveOpenClawDocsPath({
workspaceDir,
argv1: process.argv[1],
cwd: process.cwd(),

View File

@@ -45,6 +45,7 @@ function makeCfg(home: string) {
describe("getReplyFromConfig media note plumbing", () => {
beforeEach(async () => {
vi.resetModules();
vi.stubEnv("OPENCLAW_TEST_FAST", "1");
resetReplyRuntimeMocks(agentMocks);
({ getReplyFromConfig } = await import("./reply.js"));
});

View File

@@ -1,4 +1,5 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import type {
@@ -23,10 +24,13 @@ type GeneratedBundledChannelEntry = {
const log = createSubsystemLogger("channels");
const OPENCLAW_PACKAGE_ROOT =
resolveOpenClawPackageRootSync({
cwd: process.cwd(),
moduleUrl: import.meta.url,
argv1: process.argv[1],
}) ?? process.cwd();
cwd: process.cwd(),
moduleUrl: import.meta.url.startsWith("file:") ? import.meta.url : undefined,
}) ??
(import.meta.url.startsWith("file:")
? path.resolve(fileURLToPath(new URL("../../..", import.meta.url)))
: process.cwd());
function resolveChannelPluginModuleEntry(
moduleExport: unknown,
@@ -94,15 +98,35 @@ function resolveBundledChannelBoundaryRoot(params: {
return path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions", params.metadata.dirName);
}
function resolveGeneratedBundledChannelModulePath(params: {
metadata: BundledChannelPluginMetadata;
entry: BundledChannelPluginMetadata["source"] | BundledChannelPluginMetadata["setupSource"];
}): string | null {
if (!params.entry) {
return null;
}
const candidateRoots = [
path.resolve(OPENCLAW_PACKAGE_ROOT, "dist", "extensions", params.metadata.dirName),
path.resolve(OPENCLAW_PACKAGE_ROOT, "extensions", params.metadata.dirName),
];
for (const rootDir of candidateRoots) {
const resolved = resolveBundledChannelGeneratedPath(
rootDir,
params.entry,
params.metadata.dirName,
);
if (resolved) {
return resolved;
}
}
return null;
}
function loadGeneratedBundledChannelModule(params: {
metadata: BundledChannelPluginMetadata;
entry: BundledChannelPluginMetadata["source"] | BundledChannelPluginMetadata["setupSource"];
}): unknown {
const modulePath = resolveBundledChannelGeneratedPath(
OPENCLAW_PACKAGE_ROOT,
params.entry,
params.metadata.dirName,
);
const modulePath = resolveGeneratedBundledChannelModulePath(params);
if (!modulePath) {
throw new Error(`missing generated module for bundled channel ${params.metadata.manifest.id}`);
}

View File

@@ -71,7 +71,7 @@ describe("CronService store load", () => {
const jobs = await cron.list({ includeDisabled: true });
expect(jobs[0]?.state.lastStatus).toBe("skipped");
expect(jobs[0]?.state.lastError).toMatch(/main job requires/i);
expect(jobs[0]?.state.lastError).toMatch(/main cron jobs require payload\.kind/i);
cron.stop();
});

View File

@@ -367,7 +367,7 @@ type PreparedManualRun =
| {
ok: true;
ran: false;
reason: "already-running" | "not-due";
reason: "already-running" | "not-due" | "invalid-spec";
}
| {
ok: true;
@@ -399,6 +399,48 @@ function createCronTaskRunId(jobId: string, startedAt: number): string {
return `cron:${jobId}:${startedAt}`;
}
async function skipInvalidPersistedManualRun(params: {
state: CronServiceState;
job: CronJob;
mode?: "due" | "force";
error: unknown;
}) {
const endedAt = params.state.deps.nowMs();
const errorText = normalizeCronRunErrorText(params.error);
const shouldDelete = applyJobResult(
params.state,
params.job,
{
status: "skipped",
error: errorText,
startedAt: endedAt,
endedAt,
},
{ preserveSchedule: params.mode === "force" },
);
emit(params.state, {
jobId: params.job.id,
action: "finished",
status: "skipped",
error: errorText,
runAtMs: endedAt,
durationMs: params.job.state.lastDurationMs,
nextRunAtMs: params.job.state.nextRunAtMs,
deliveryStatus: params.job.state.lastDeliveryStatus,
deliveryError: params.job.state.lastDeliveryError,
});
if (shouldDelete && params.state.store) {
params.state.store.jobs = params.state.store.jobs.filter((entry) => entry.id !== params.job.id);
emit(params.state, { jobId: params.job.id, action: "removed" });
}
recomputeNextRunsForMaintenance(params.state, { recomputeExpired: true });
await persist(params.state);
armTimer(params.state);
}
function tryCreateManualTaskRun(params: {
state: CronServiceState;
job: CronJob;
@@ -489,7 +531,12 @@ async function inspectManualRunPreflight(
// persist does not block manual triggers for up to STUCK_RUN_MS (#17554).
recomputeNextRunsForMaintenance(state);
const job = findJobOrThrow(state, id);
assertSupportedJobSpec(job);
try {
assertSupportedJobSpec(job);
} catch (error) {
await skipInvalidPersistedManualRun({ state, job, mode, error });
return { ok: true, ran: false, reason: "invalid-spec" as const };
}
if (typeof job.state.runningAtMs === "number") {
return { ok: true, ran: false, reason: "already-running" as const };
}

View File

@@ -2,7 +2,7 @@ import actualFs from "node:fs";
import actualFsPromises from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
type FakeFsEntry = { kind: "file"; content: string } | { kind: "dir" };
@@ -115,7 +115,8 @@ describe("resolveOpenClawPackageRoot", () => {
state.realpathErrors.clear();
});
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
({ resolveOpenClawPackageRoot, resolveOpenClawPackageRootSync } =
await import("./openclaw-root.js"));
});

View File

@@ -32,6 +32,21 @@ vi.mock("./outbound-session.js", () => ({
resolveOutboundSessionRoute: vi.fn(async () => null),
}));
vi.mock("../../channels/plugins/bootstrap-registry.js", () => ({
getBootstrapChannelPlugin: (id: string) =>
id === "feishu"
? {
actions: {
messageActionTargetAliases: {
pin: { aliases: ["messageId"] },
unpin: { aliases: ["messageId"] },
"list-pins": { aliases: ["chatId"] },
},
},
}
: undefined,
}));
vi.mock("./message-action-threading.js", () => ({
resolveAndApplyOutboundThreadId: vi.fn(
(
@@ -231,6 +246,11 @@ describe("runMessageAction plugin dispatch", () => {
},
capabilities: { chatTypes: ["direct", "channel"] },
config: createAlwaysConfiguredPluginConfig(),
messaging: {
targetResolver: {
looksLikeId: () => true,
},
},
actions: {
describeMessageTool: () => ({ actions: ["pin", "list-pins", "member-info"] }),
supportsAction: ({ action }) =>
@@ -335,7 +355,8 @@ describe("runMessageAction plugin dispatch", () => {
sessionId: "session-123",
agentId: "alpha",
toolContext: {
currentChannelId: "chat:oc_123",
currentChannelId: "oc_123",
currentChannelProvider: "feishu",
currentThreadTs: "thread-456",
currentMessageId: "msg-789",
},
@@ -352,7 +373,8 @@ describe("runMessageAction plugin dispatch", () => {
agentId: "alpha",
mediaLocalRoots: expect.arrayContaining([expectedWorkspaceRoot]),
toolContext: expect.objectContaining({
currentChannelId: "chat:oc_123",
currentChannelId: "oc_123",
currentChannelProvider: "feishu",
currentThreadTs: "thread-456",
currentMessageId: "msg-789",
}),

View File

@@ -25,6 +25,13 @@ export type AgentModelAliasEntry =
alias?: string;
};
const LEGACY_OPENCODE_ZEN_DEFAULT_MODELS = new Set([
"opencode/claude-opus-4-5",
"opencode-zen/claude-opus-4-5",
]);
export const OPENCODE_ZEN_DEFAULT_MODEL = "opencode/claude-opus-4-6";
export type ProviderOnboardPresetAppliers<TArgs extends unknown[]> = {
applyProviderConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig;
applyConfig: (cfg: OpenClawConfig, ...args: TArgs) => OpenClawConfig;
@@ -51,6 +58,20 @@ function normalizeAgentModelAliasEntry(entry: AgentModelAliasEntry): {
return entry;
}
function resolveCurrentPrimaryModel(model: unknown): string | undefined {
if (typeof model === "string") {
return model.trim() || undefined;
}
if (
model &&
typeof model === "object" &&
typeof (model as { primary?: unknown }).primary === "string"
) {
return ((model as { primary: string }).primary || "").trim() || undefined;
}
return undefined;
}
type ProviderModelMergeState = {
providers: Record<string, ModelProviderConfig>;
existingProvider?: ModelProviderConfig;
@@ -211,6 +232,24 @@ export function applyAgentDefaultModelPrimary(
};
}
export function applyOpencodeZenModelDefault(cfg: OpenClawConfig): {
next: OpenClawConfig;
changed: boolean;
} {
const current = resolveCurrentPrimaryModel(cfg.agents?.defaults?.model);
const normalizedCurrent =
current && LEGACY_OPENCODE_ZEN_DEFAULT_MODELS.has(current)
? OPENCODE_ZEN_DEFAULT_MODEL
: current;
if (normalizedCurrent === OPENCODE_ZEN_DEFAULT_MODEL) {
return { next: cfg, changed: false };
}
return {
next: applyAgentDefaultModelPrimary(cfg, OPENCODE_ZEN_DEFAULT_MODEL),
changed: true,
};
}
export function applyProviderConfigWithDefaultModels(
cfg: OpenClawConfig,
params: {

View File

@@ -25,6 +25,8 @@ const ALLOWED_CONTRACT_BUNDLED_PATH_HELPERS = new Set([
]);
const ALLOWED_CHANNEL_BUNDLED_METADATA_CONSUMERS = new Set([
"src/channels/plugins/bundled.ts",
"src/channels/plugins/contracts/runtime-artifacts.ts",
"src/channels/plugins/session-conversation.bundled-fallback.test.ts",
]);

View File

@@ -1,4 +1,8 @@
import type { OpenClawConfig } from "../config/config.js";
export {
applyOpencodeZenModelDefault,
OPENCODE_ZEN_DEFAULT_MODEL,
} from "../plugin-sdk/opencode.js";
import { ensureModelAllowlistEntry } from "./provider-model-allowlist.js";
import { applyAgentDefaultPrimaryModel } from "./provider-model-primary.js";

View File

@@ -37,6 +37,13 @@ const { buildVitestArgs, buildVitestRunPlans, createVitestRunSpecs, parseTestPro
};
};
const VITEST_NODE_PREFIX = [
"exec",
"node",
"--no-maglev",
expect.stringContaining("/node_modules/vitest/vitest.mjs"),
];
describe("test-projects args", () => {
it("drops a pnpm passthrough separator while preserving targeted filters", () => {
expect(parseTestProjectsArgs(["--", "src/foo.test.ts", "-t", "target"])).toEqual({
@@ -48,8 +55,7 @@ describe("test-projects args", () => {
it("keeps watch mode explicit without leaking the sentinel to Vitest", () => {
expect(buildVitestArgs(["--watch", "--", "src/foo.test.ts"])).toEqual([
"exec",
"vitest",
...VITEST_NODE_PREFIX,
"--config",
"vitest.unit.config.ts",
"src/foo.test.ts",
@@ -58,8 +64,7 @@ describe("test-projects args", () => {
it("uses run mode by default", () => {
expect(buildVitestArgs(["src/foo.test.ts"])).toEqual([
"exec",
"vitest",
...VITEST_NODE_PREFIX,
"run",
"--config",
"vitest.unit.config.ts",
@@ -704,8 +709,7 @@ describe("test-projects args", () => {
]);
expect(spec?.pnpmArgs).toEqual([
"exec",
"vitest",
...VITEST_NODE_PREFIX,
"run",
"--config",
"vitest.extension-channels.config.ts",

View File

@@ -10,6 +10,7 @@ const ALLOWED_EXTENSION_PUBLIC_SURFACE_BASENAMES = new Set(
);
const allowedNonExtensionTests = new Set<string>([
"src/agents/pi-embedded-runner-extraparams-moonshot.test.ts",
"src/agents/pi-embedded-runner-extraparams.test.ts",
"src/channels/plugins/contracts/dm-policy.contract.test.ts",
"src/channels/plugins/contracts/group-policy.contract.test.ts",
@@ -20,6 +21,17 @@ const allowedNonExtensionTests = new Set<string>([
"src/plugins/interactive.test.ts",
"src/plugins/contracts/discovery.contract.test.ts",
"src/plugin-sdk/telegram-command-config.test.ts",
"src/secrets/runtime-channel-inactive-variants.test.ts",
"src/secrets/runtime-discord-surface.test.ts",
"src/secrets/runtime-inactive-telegram-surfaces.test.ts",
"src/secrets/runtime-legacy-x-search.test.ts",
"src/secrets/runtime-matrix-shadowing.test.ts",
"src/secrets/runtime-matrix-top-level.test.ts",
"src/secrets/runtime-nextcloud-talk-file-precedence.test.ts",
"src/secrets/runtime-telegram-token-inheritance.test.ts",
"src/secrets/runtime-zalo-token-activity.test.ts",
"src/security/audit-channel-slack-command-findings.test.ts",
"src/security/audit-feishu-doc-risk.test.ts",
]);
function walk(dir: string, entries: string[] = []): string[] {

View File

@@ -77,13 +77,26 @@ describe("local-heavy-check-runtime", () => {
it("serializes local oxlint runs onto one thread on constrained hosts", () => {
const { args } = applyLocalOxlintPolicy([], makeEnv(), CONSTRAINED_HOST);
expect(args).toEqual(["--type-aware", "--tsconfig", "tsconfig.oxlint.json", "--threads=1"]);
expect(args).toEqual([
"--type-aware",
"--tsconfig",
"tsconfig.oxlint.json",
"--report-unused-disable-directives-severity",
"error",
"--threads=1",
]);
});
it("keeps local oxlint parallel on roomy hosts in auto mode", () => {
const { args } = applyLocalOxlintPolicy([], makeEnv(), ROOMY_HOST);
expect(args).toEqual(["--type-aware", "--tsconfig", "tsconfig.oxlint.json"]);
expect(args).toEqual([
"--type-aware",
"--tsconfig",
"tsconfig.oxlint.json",
"--report-unused-disable-directives-severity",
"error",
]);
});
it("reclaims stale local heavy-check locks from dead pids", () => {