diff --git a/scripts/deadcode-exports.baseline.mjs b/scripts/deadcode-exports.baseline.mjs index 19269a0f1d74..0e924c9c9ee3 100644 --- a/scripts/deadcode-exports.baseline.mjs +++ b/scripts/deadcode-exports.baseline.mjs @@ -100,32 +100,12 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [ "src/cron/service/timer.ts: executeJobCore", "src/cron/service/timer.ts: onTimer", "src/cron/session-reaper.ts: resetReaperThrottle", - "src/entry.compile-cache.ts: buildOpenClawCompileCacheRespawnPlan", - "src/entry.compile-cache.ts: isNodeVersionAffectedByCompileCacheDeadlock", - "src/entry.compile-cache.ts: isSourceCheckoutInstallRoot", - "src/entry.compile-cache.ts: resolveOpenClawCompileCacheDirectory", - "src/entry.compile-cache.ts: runOpenClawCompileCacheRespawnPlan", - "src/entry.compile-cache.ts: shouldEnableOpenClawCompileCache", "src/flows/doctor-health-contributions.ts: createDoctorHealthContribution", "src/flows/doctor-health-contributions.ts: resolveDoctorHealthContributions", - "src/logging/diagnostic-run-activity.ts: markDiagnosticModelStartedForTest", - "src/logging/diagnostic-run-activity.ts: markDiagnosticRunProgressForTest", - "src/logging/diagnostic-run-activity.ts: markDiagnosticToolStartedForTest", "src/logging/redact-internal.ts: withFullContextToolPayloadRedaction", - "src/logging/secret-redaction-registry.ts: resetSecretRedactionRegistryForTest", - "src/node-host/invoke.ts: testing", - "src/plugin-state/plugin-state-store.sqlite.ts: probePluginStateStore", - "src/plugin-state/plugin-state-store.sqlite.ts: seedPluginStateDatabaseEntriesForTests", - "src/plugin-state/plugin-state-store.sqlite.ts: setMaxPluginStateEntriesPerPluginForTests", - "src/plugin-state/plugin-state-store.ts: clearPluginStateStoreForTests", "src/sessions/session-lifecycle-admission.ts: runExclusiveSessionLifecycle", "src/sessions/session-upstream-monitor.ts: runSessionUpstreamMonitorTick", "src/sessions/user-turn-transcript.ts: persistUserTurnTranscript", - "src/skills/lifecycle/install.ts: testing", - "src/skills/lifecycle/upload-store.ts: createSkillUploadStore", - "src/skills/runtime/refresh.ts: resetSkillsRefreshForTest", - "src/skills/runtime/remote-skills.ts: resetRemoteNodeSkillsForTests", - "src/system-agent/agent-turn.ts: runSystemAgentTurnWithDeps", "src/tasks/detached-task-runtime.ts: resetDetachedTaskLifecycleRuntimeForTests", "src/tasks/detached-task-runtime.ts: setDetachedTaskLifecycleRuntime", "src/tasks/generated-media-task-activity.ts: resetGeneratedMediaTaskActivityForTests", diff --git a/src/agents/embedded-agent-runner/runs.test.ts b/src/agents/embedded-agent-runner/runs.test.ts index 566e004c0170..5ff02f3d8962 100644 --- a/src/agents/embedded-agent-runner/runs.test.ts +++ b/src/agents/embedded-agent-runner/runs.test.ts @@ -11,10 +11,8 @@ import { isReplyRunActiveForSessionId, } from "../../auto-reply/reply/reply-run-registry.js"; import { setDiagnosticsEnabledForProcess } from "../../infra/diagnostic-events.js"; -import { - markDiagnosticToolStartedForTest, - resetDiagnosticRunActivityForTest, -} from "../../logging/diagnostic-run-activity.js"; +import { resetDiagnosticRunActivityForTest } from "../../logging/diagnostic-run-activity.js"; +import { markDiagnosticToolStartedForTest } from "../../logging/diagnostic-run-activity.test-support.js"; import { getDiagnosticSessionState, resetDiagnosticSessionStateForTest, diff --git a/src/agents/mcp-connection-resolver.test.ts b/src/agents/mcp-connection-resolver.test.ts index 4c895f63e523..0bacd8616eda 100644 --- a/src/agents/mcp-connection-resolver.test.ts +++ b/src/agents/mcp-connection-resolver.test.ts @@ -63,8 +63,10 @@ describe("mcp connection resolver helpers", () => { }); it("registers resolved header and signed-URL credentials for redaction", async () => { - const { isSecretValueRegisteredForRedaction, resetSecretRedactionRegistryForTest } = + const { isSecretValueRegisteredForRedaction } = await import("../logging/secret-redaction-registry.js"); + const { resetSecretRedactionRegistryForTest } = + await import("../logging/secret-redaction-registry.test-support.js"); resetSecretRedactionRegistryForTest(); testing.setMcpServerConnectionResolversForTest([ { diff --git a/src/agents/provider-transport-fetch.integration.test.ts b/src/agents/provider-transport-fetch.integration.test.ts index 6743fb1423d0..aa241916e532 100644 --- a/src/agents/provider-transport-fetch.integration.test.ts +++ b/src/agents/provider-transport-fetch.integration.test.ts @@ -3,7 +3,7 @@ import type { AddressInfo } from "node:net"; import type { Model } from "openclaw/plugin-sdk/llm"; import { afterEach, describe, expect, it } from "vitest"; import { redactSensitiveText } from "../logging/redact.js"; -import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js"; import { mintSecretSentinel } from "../secrets/sentinel.js"; import { buildGuardedModelFetch } from "./provider-transport-fetch.js"; diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts index df087cc50427..2736c5e9160c 100644 --- a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -8,7 +8,7 @@ import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import type { PluginRegistry } from "../plugins/registry.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js"; import type { ProviderPlugin } from "../plugins/types.js"; -import { resetSkillsRefreshForTest } from "../skills/runtime/refresh.js"; +import { resetSkillsRefreshForTest } from "../skills/runtime/refresh.test-support.js"; import { clearSessionAuthProfileOverrideMock, compactEmbeddedAgentSessionMock, diff --git a/src/auto-reply/reply/reply-turn-admission.test.ts b/src/auto-reply/reply/reply-turn-admission.test.ts index 080be2749b11..9b8d739336ee 100644 --- a/src/auto-reply/reply/reply-turn-admission.test.ts +++ b/src/auto-reply/reply/reply-turn-admission.test.ts @@ -9,10 +9,10 @@ import { } from "../../config/sessions/session-accessor.js"; import type { SessionEntry } from "../../config/sessions/types.js"; import { - markDiagnosticToolStartedForTest, resetDiagnosticRunActivityForTest, RUN_STALE_TAKEOVER_MS, } from "../../logging/diagnostic-run-activity.js"; +import { markDiagnosticToolStartedForTest } from "../../logging/diagnostic-run-activity.test-support.js"; import { interruptSessionWorkAdmissions, runExclusiveSessionLifecycleMutation, diff --git a/src/commands/doctor-state-migrations.test.ts b/src/commands/doctor-state-migrations.test.ts index b7e568fd7e88..cfb09644f936 100644 --- a/src/commands/doctor-state-migrations.test.ts +++ b/src/commands/doctor-state-migrations.test.ts @@ -14,8 +14,10 @@ import { createPluginStateKeyedStore, resetPluginStateStoreForTests, } from "../plugin-state/plugin-state-store.js"; -import { setMaxPluginStateEntriesPerPluginForTests } from "../plugin-state/plugin-state-store.sqlite.js"; -import { seedPluginStateEntriesForTests } from "../plugin-state/plugin-state-store.test-helpers.js"; +import { + seedPluginStateEntriesForTests, + setMaxPluginStateEntriesPerPluginForTests, +} from "../plugin-state/plugin-state-store.test-helpers.js"; import { readPersistedInstalledPluginIndex, writePersistedInstalledPluginIndex, diff --git a/src/entry.compile-cache.test-support.ts b/src/entry.compile-cache.test-support.ts new file mode 100644 index 000000000000..81584c934dac --- /dev/null +++ b/src/entry.compile-cache.test-support.ts @@ -0,0 +1,80 @@ +import type { ChildProcess } from "node:child_process"; +import type { RespawnChildRuntime } from "./process/respawn-child-runner.js"; +import "./entry.compile-cache.js"; + +type CompileCacheParams = { + env?: NodeJS.ProcessEnv; + installRoot: string; + nodeVersion?: string; + platform?: NodeJS.Platform; +}; + +type CompileCacheRespawnPlan = { + command: string; + args: string[]; + env: NodeJS.ProcessEnv; + detachForProcessTree: boolean; +}; + +type CompileCacheTestApi = { + buildOpenClawCompileCacheRespawnPlan(params: { + currentFile: string; + env?: NodeJS.ProcessEnv; + execArgv?: string[]; + execPath?: string; + installRoot: string; + argv?: string[]; + compileCacheDir?: string; + nodeVersion?: string; + platform?: NodeJS.Platform; + }): CompileCacheRespawnPlan | undefined; + isNodeVersionAffectedByCompileCacheDeadlock(nodeVersion: string | undefined): boolean; + isSourceCheckoutInstallRoot(installRoot: string): boolean; + resolveOpenClawCompileCacheDirectory(params: { + env?: NodeJS.ProcessEnv; + installRoot: string; + }): string; + runOpenClawCompileCacheRespawnPlan( + plan: CompileCacheRespawnPlan, + runtime?: RespawnChildRuntime & { writeError(message: string): void }, + ): ChildProcess; + shouldEnableOpenClawCompileCache(params: CompileCacheParams): boolean; +}; + +function getTestApi(): CompileCacheTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.entryCompileCacheTestApi") + ] as CompileCacheTestApi; +} + +export function buildOpenClawCompileCacheRespawnPlan( + params: Parameters[0], +): CompileCacheRespawnPlan | undefined { + return getTestApi().buildOpenClawCompileCacheRespawnPlan(params); +} + +export function isNodeVersionAffectedByCompileCacheDeadlock( + nodeVersion: string | undefined, +): boolean { + return getTestApi().isNodeVersionAffectedByCompileCacheDeadlock(nodeVersion); +} + +export function isSourceCheckoutInstallRoot(installRoot: string): boolean { + return getTestApi().isSourceCheckoutInstallRoot(installRoot); +} + +export function resolveOpenClawCompileCacheDirectory( + params: Parameters[0], +): string { + return getTestApi().resolveOpenClawCompileCacheDirectory(params); +} + +export function runOpenClawCompileCacheRespawnPlan( + ...args: Parameters +): ChildProcess { + return getTestApi().runOpenClawCompileCacheRespawnPlan(...args); +} + +export function shouldEnableOpenClawCompileCache(params: CompileCacheParams): boolean { + return getTestApi().shouldEnableOpenClawCompileCache(params); +} diff --git a/src/entry.compile-cache.test.ts b/src/entry.compile-cache.test.ts index f7daecfe0f1a..21edda8cc098 100644 --- a/src/entry.compile-cache.test.ts +++ b/src/entry.compile-cache.test.ts @@ -5,15 +5,15 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanupTempDirs, makeTempDir } from "../test/helpers/temp-dir.js"; +import { resolveEntryInstallRoot } from "./entry.compile-cache.js"; import { buildOpenClawCompileCacheRespawnPlan, isNodeVersionAffectedByCompileCacheDeadlock, isSourceCheckoutInstallRoot, resolveOpenClawCompileCacheDirectory, - resolveEntryInstallRoot, runOpenClawCompileCacheRespawnPlan, shouldEnableOpenClawCompileCache, -} from "./entry.compile-cache.js"; +} from "./entry.compile-cache.test-support.js"; function requireFirstMockCall(mock: { mock: { calls: unknown[][] } }, label: string): unknown[] { const [call] = mock.mock.calls; diff --git a/src/entry.compile-cache.ts b/src/entry.compile-cache.ts index c2211143cd0e..892a38a0fb9d 100644 --- a/src/entry.compile-cache.ts +++ b/src/entry.compile-cache.ts @@ -24,7 +24,7 @@ export function resolveEntryInstallRoot(entryFile: string): string { return entryParent === "dist" || entryParent === "src" ? path.dirname(entryDir) : entryDir; } -export function isSourceCheckoutInstallRoot(installRoot: string): boolean { +function isSourceCheckoutInstallRoot(installRoot: string): boolean { return ( existsSync(path.join(installRoot, ".git")) || existsSync(path.join(installRoot, "src", "entry.ts")) @@ -39,9 +39,7 @@ function isNodeCompileCacheRequested(env: NodeJS.ProcessEnv | undefined): boolea return env?.NODE_COMPILE_CACHE !== undefined && !isNodeCompileCacheDisabled(env); } -export function isNodeVersionAffectedByCompileCacheDeadlock( - nodeVersion: string | undefined, -): boolean { +function isNodeVersionAffectedByCompileCacheDeadlock(nodeVersion: string | undefined): boolean { if (!nodeVersion) { return false; } @@ -57,7 +55,7 @@ export function isNodeVersionAffectedByCompileCacheDeadlock( return minor < MIN_COMPILE_CACHE_NODE_24_MINOR; } -export function shouldEnableOpenClawCompileCache(params: { +function shouldEnableOpenClawCompileCache(params: { env?: NodeJS.ProcessEnv; installRoot: string; nodeVersion?: string; @@ -98,7 +96,7 @@ function readPackageVersion(packageJsonPath: string): string { return "unknown"; } -export function resolveOpenClawCompileCacheDirectory(params: { +function resolveOpenClawCompileCacheDirectory(params: { env?: NodeJS.ProcessEnv; installRoot: string; }): string { @@ -135,7 +133,7 @@ type OpenClawCompileCacheRespawnRuntime = RespawnChildRuntime & { writeError: (message: string) => void; }; -export function buildOpenClawCompileCacheRespawnPlan(params: { +function buildOpenClawCompileCacheRespawnPlan(params: { currentFile: string; env?: NodeJS.ProcessEnv; execArgv?: string[]; @@ -196,7 +194,7 @@ export function respawnWithoutOpenClawCompileCacheIfNeeded(params: { return true; } -export function runOpenClawCompileCacheRespawnPlan( +function runOpenClawCompileCacheRespawnPlan( plan: OpenClawCompileCacheRespawnPlan, runtime: OpenClawCompileCacheRespawnRuntime = { spawn, @@ -234,3 +232,14 @@ export function enableOpenClawCompileCache(params: { // Best-effort only; never block startup. } } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.entryCompileCacheTestApi")] = { + buildOpenClawCompileCacheRespawnPlan, + isNodeVersionAffectedByCompileCacheDeadlock, + isSourceCheckoutInstallRoot, + resolveOpenClawCompileCacheDirectory, + runOpenClawCompileCacheRespawnPlan, + shouldEnableOpenClawCompileCache, + }; +} diff --git a/src/gateway/server-methods/nodes.test.ts b/src/gateway/server-methods/nodes.test.ts index c5bc3aa62e28..a50074505f34 100644 --- a/src/gateway/server-methods/nodes.test.ts +++ b/src/gateway/server-methods/nodes.test.ts @@ -13,7 +13,7 @@ import { type DiagnosticSecurityEvent, } from "../../infra/diagnostic-events.js"; import { approveNodePairing, requestNodePairing } from "../../infra/node-pairing.js"; -import { resetRemoteNodeSkillsForTests } from "../../skills/runtime/remote-skills.js"; +import { resetRemoteNodeSkillsForTests } from "../../skills/runtime/remote-skills.test-support.js"; import { createOpenClawTestState, type OpenClawTestState, diff --git a/src/gateway/worker-environments/credential.test.ts b/src/gateway/worker-environments/credential.test.ts index a55529de1dea..18e15061bc95 100644 --- a/src/gateway/worker-environments/credential.test.ts +++ b/src/gateway/worker-environments/credential.test.ts @@ -1,8 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { - isSecretValueRegisteredForRedaction, - resetSecretRedactionRegistryForTest, -} from "../../logging/secret-redaction-registry.js"; +import { isSecretValueRegisteredForRedaction } from "../../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../../logging/secret-redaction-registry.test-support.js"; import { createWorkerCredentialMaterial, hashWorkerCredential } from "./credential.js"; afterEach(resetSecretRedactionRegistryForTest); diff --git a/src/logging/diagnostic-run-activity.test-support.ts b/src/logging/diagnostic-run-activity.test-support.ts new file mode 100644 index 000000000000..978826a83795 --- /dev/null +++ b/src/logging/diagnostic-run-activity.test-support.ts @@ -0,0 +1,46 @@ +import type { DiagnosticEventPayload } from "../infra/diagnostic-events.js"; +import "./diagnostic-run-activity.js"; + +type DiagnosticModelStartedActivityEvent = Pick< + Extract, + "runId" | "sessionId" | "sessionKey" | "provider" | "model" +> & { seq?: number }; + +type DiagnosticRunProgressActivityEvent = Pick< + Extract, + "runId" | "sessionId" | "sessionKey" | "reason" +>; + +type DiagnosticRunActivityTestApi = { + markDiagnosticModelStartedForTest(params: DiagnosticModelStartedActivityEvent): void; + markDiagnosticRunProgressForTest(params: DiagnosticRunProgressActivityEvent): void; + markDiagnosticToolStartedForTest(params: { + sessionId?: string; + sessionKey?: string; + runId?: string; + toolName: string; + toolCallId?: string; + }): void; +}; + +function getTestApi(): DiagnosticRunActivityTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.diagnosticRunActivityTestApi") + ] as DiagnosticRunActivityTestApi; +} + +export function markDiagnosticModelStartedForTest( + params: DiagnosticModelStartedActivityEvent, +): void { + getTestApi().markDiagnosticModelStartedForTest(params); +} + +export function markDiagnosticRunProgressForTest(params: DiagnosticRunProgressActivityEvent): void { + getTestApi().markDiagnosticRunProgressForTest(params); +} + +export function markDiagnosticToolStartedForTest( + params: Parameters[0], +): void { + getTestApi().markDiagnosticToolStartedForTest(params); +} diff --git a/src/logging/diagnostic-run-activity.ts b/src/logging/diagnostic-run-activity.ts index 18c48cf179f2..ecc08ae4d9ad 100644 --- a/src/logging/diagnostic-run-activity.ts +++ b/src/logging/diagnostic-run-activity.ts @@ -627,11 +627,11 @@ export function getDiagnosticEmbeddedRunActivitySequence(): number { return embeddedRunSequence; } -export function markDiagnosticRunProgressForTest(params: DiagnosticRunProgressActivityEvent): void { +function markDiagnosticRunProgressForTest(params: DiagnosticRunProgressActivityEvent): void { markDiagnosticRunProgress(params); } -export function markDiagnosticToolStartedForTest(params: { +function markDiagnosticToolStartedForTest(params: { sessionId?: string; sessionKey?: string; runId?: string; @@ -641,9 +641,7 @@ export function markDiagnosticToolStartedForTest(params: { recordToolStarted(params); } -export function markDiagnosticModelStartedForTest( - params: DiagnosticModelStartedActivityEvent, -): void { +function markDiagnosticModelStartedForTest(params: DiagnosticModelStartedActivityEvent): void { recordModelStarted(params); } @@ -691,3 +689,13 @@ function registerDiagnosticRunActivityListener(): void { } registerDiagnosticRunActivityListener(); + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[ + Symbol.for("openclaw.diagnosticRunActivityTestApi") + ] = { + markDiagnosticModelStartedForTest, + markDiagnosticRunProgressForTest, + markDiagnosticToolStartedForTest, + }; +} diff --git a/src/logging/diagnostic.test.ts b/src/logging/diagnostic.test.ts index 021d802fc0f8..a02c0dc1f336 100644 --- a/src/logging/diagnostic.test.ts +++ b/src/logging/diagnostic.test.ts @@ -14,11 +14,13 @@ import { getDiagnosticSessionActivitySnapshot, markDiagnosticEmbeddedRunEnded, markDiagnosticEmbeddedRunStarted, + resetDiagnosticRunActivityForTest, +} from "./diagnostic-run-activity.js"; +import { markDiagnosticModelStartedForTest, markDiagnosticRunProgressForTest, markDiagnosticToolStartedForTest, - resetDiagnosticRunActivityForTest, -} from "./diagnostic-run-activity.js"; +} from "./diagnostic-run-activity.test-support.js"; import type { SessionAttentionClassification } from "./diagnostic-session-attention.js"; import { requestStuckSessionRecovery, diff --git a/src/logging/redact.test.ts b/src/logging/redact.test.ts index b1570f90713e..be4f3236bf90 100644 --- a/src/logging/redact.test.ts +++ b/src/logging/redact.test.ts @@ -15,10 +15,8 @@ import { redactToolPayloadTextWithConfig, resolveRedactOptions, } from "./redact.js"; -import { - registerSecretValueForRedaction, - resetSecretRedactionRegistryForTest, -} from "./secret-redaction-registry.js"; +import { registerSecretValueForRedaction } from "./secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "./secret-redaction-registry.test-support.js"; const defaults = getDefaultRedactPatterns(); let tempDirs: string[] = []; diff --git a/src/logging/secret-redaction-registry.test-support.ts b/src/logging/secret-redaction-registry.test-support.ts new file mode 100644 index 000000000000..547a0bb7a190 --- /dev/null +++ b/src/logging/secret-redaction-registry.test-support.ts @@ -0,0 +1,15 @@ +import "./secret-redaction-registry.js"; + +type SecretRedactionRegistryTestApi = { + resetSecretRedactionRegistryForTest(): void; +}; + +function getTestApi(): SecretRedactionRegistryTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.secretRedactionRegistryTestApi") + ] as SecretRedactionRegistryTestApi; +} + +export function resetSecretRedactionRegistryForTest(): void { + getTestApi().resetSecretRedactionRegistryForTest(); +} diff --git a/src/logging/secret-redaction-registry.ts b/src/logging/secret-redaction-registry.ts index dc95f67fb7b5..68a424c4a506 100644 --- a/src/logging/secret-redaction-registry.ts +++ b/src/logging/secret-redaction-registry.ts @@ -87,8 +87,13 @@ export function redactRegisteredSecretValues( return text.replace(compiledMatcher, (value) => mask(value)); } -/** Test-only reset for process-global redaction state. */ -export function resetSecretRedactionRegistryForTest(): void { +function resetSecretRedactionRegistryForTest(): void { registeredValues.clear(); rebuildProbe(); } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[ + Symbol.for("openclaw.secretRedactionRegistryTestApi") + ] = { resetSecretRedactionRegistryForTest }; +} diff --git a/src/node-host/invoke.mcp.test.ts b/src/node-host/invoke.mcp.test.ts index 878de523a304..f86c6dced037 100644 --- a/src/node-host/invoke.mcp.test.ts +++ b/src/node-host/invoke.mcp.test.ts @@ -1,7 +1,8 @@ /** Tests the built-in node-host MCP invocation command. */ import { describe, expect, it, vi } from "vitest"; import type { GatewayClient } from "../gateway/client.js"; -import { handleInvoke, testing } from "./invoke.js"; +import { handleInvoke } from "./invoke.js"; +import { testing } from "./invoke.test-support.js"; import { NodeHostMcpError, type NodeHostMcpManager } from "./mcp.js"; async function invokeMcp(manager: NodeHostMcpManager, params: unknown) { diff --git a/src/node-host/invoke.run-command.test.ts b/src/node-host/invoke.run-command.test.ts index 0d9fd26b0eee..15b792d81360 100644 --- a/src/node-host/invoke.run-command.test.ts +++ b/src/node-host/invoke.run-command.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { testing } from "./invoke.js"; +import { testing } from "./invoke.test-support.js"; describe("runCommand", () => { afterEach(() => { diff --git a/src/node-host/invoke.test-support.ts b/src/node-host/invoke.test-support.ts new file mode 100644 index 000000000000..f34d0c018b00 --- /dev/null +++ b/src/node-host/invoke.test-support.ts @@ -0,0 +1,35 @@ +import type { RunResult } from "./invoke-types.js"; +import "./invoke.js"; + +type NodeHostInvokeTestApi = { + readonly MCP_TEXT_CONTENT_MAX_BYTES: number; + readonly MCP_INVOKE_PAYLOAD_MAX_BYTES: number; + clarifyNodeExecCwdSpawnError(error: NodeJS.ErrnoException, cwd: string | undefined): string; + runCommand( + argv: string[], + cwd: string | undefined, + env: Record | undefined, + timeoutMs: number | undefined, + ): Promise; +}; + +function getTestApi(): NodeHostInvokeTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.nodeHostInvokeTestApi") + ] as NodeHostInvokeTestApi; +} + +export const testing: NodeHostInvokeTestApi = { + get MCP_TEXT_CONTENT_MAX_BYTES() { + return getTestApi().MCP_TEXT_CONTENT_MAX_BYTES; + }, + get MCP_INVOKE_PAYLOAD_MAX_BYTES() { + return getTestApi().MCP_INVOKE_PAYLOAD_MAX_BYTES; + }, + clarifyNodeExecCwdSpawnError(error, cwd) { + return getTestApi().clarifyNodeExecCwdSpawnError(error, cwd); + }, + runCommand(argv, cwd, env, timeoutMs) { + return getTestApi().runCommand(argv, cwd, env, timeoutMs); + }, +}; diff --git a/src/node-host/invoke.ts b/src/node-host/invoke.ts index 9dd5d9191ec6..a31534195a65 100644 --- a/src/node-host/invoke.ts +++ b/src/node-host/invoke.ts @@ -1075,10 +1075,15 @@ async function sendNodeEvent(client: NodeHostClient, event: string, payload: unk } } -export const testing = { +const testing = { MCP_TEXT_CONTENT_MAX_BYTES, MCP_INVOKE_PAYLOAD_MAX_BYTES, clarifyNodeExecCwdSpawnError, runCommand, } as const; + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.nodeHostInvokeTestApi")] = + testing; +} /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/plugin-state/plugin-state-store.e2e.test.ts b/src/plugin-state/plugin-state-store.e2e.test.ts index 8b3caca2391d..cc084b3c54a0 100644 --- a/src/plugin-state/plugin-state-store.e2e.test.ts +++ b/src/plugin-state/plugin-state-store.e2e.test.ts @@ -9,7 +9,7 @@ import { resetPluginStateStoreForTests, sweepExpiredPluginStateEntries, } from "./plugin-state-store.js"; -import { probePluginStateStore } from "./plugin-state-store.sqlite.js"; +import { probePluginStateStore } from "./plugin-state-store.test-helpers.js"; afterEach(() => { vi.useRealTimers(); diff --git a/src/plugin-state/plugin-state-store.sqlite.ts b/src/plugin-state/plugin-state-store.sqlite.ts index 4f6fa4e51bee..a8f40f2397f6 100644 --- a/src/plugin-state/plugin-state-store.sqlite.ts +++ b/src/plugin-state/plugin-state-store.sqlite.ts @@ -905,7 +905,7 @@ export function clearPluginStateDatabaseForTests(): void { ); } -export function setMaxPluginStateEntriesPerPluginForTests(value?: number): void { +function setMaxPluginStateEntriesPerPluginForTests(value?: number): void { maxPluginStateEntriesPerPluginForTests = value; } @@ -923,7 +923,7 @@ export function countPluginStateLiveEntries(pluginId: string): number { } } -export function seedPluginStateDatabaseEntriesForTests( +function seedPluginStateDatabaseEntriesForTests( entries: readonly PluginStateSeedEntryForTests[], ): void { if (entries.length === 0) { @@ -948,7 +948,7 @@ export function seedPluginStateDatabaseEntriesForTests( }); } -export function probePluginStateStore(): PluginStateStoreProbeResult { +function probePluginStateStore(): PluginStateStoreProbeResult { const databasePath = resolveOpenClawStateSqlitePath(process.env); const steps: PluginStateStoreProbeStep[] = []; const wasOpen = cachedDatabase !== null; @@ -1039,4 +1039,12 @@ export function closePluginStateDatabase(): void { cachedDatabase = null; closeOpenClawStateDatabase(); } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.pluginStateSqliteTestApi")] = { + probePluginStateStore, + seedPluginStateDatabaseEntriesForTests, + setMaxPluginStateEntriesPerPluginForTests, + }; +} /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/plugin-state/plugin-state-store.test-helpers.ts b/src/plugin-state/plugin-state-store.test-helpers.ts index 060d10c5f097..a0d17144b93a 100644 --- a/src/plugin-state/plugin-state-store.test-helpers.ts +++ b/src/plugin-state/plugin-state-store.test-helpers.ts @@ -1,5 +1,7 @@ // Plugin state test helpers seed SQLite plugin state fixtures. -import { seedPluginStateDatabaseEntriesForTests } from "./plugin-state-store.sqlite.js"; +import type { PluginStateStoreProbeResult } from "./plugin-state-store.types.js"; +import "./plugin-state-store.js"; +import "./plugin-state-store.sqlite.js"; // Test-only seed helpers for plugin state. Values are serialized through the // same JSON storage path used by the production sqlite store. @@ -12,13 +14,56 @@ type PluginStateSeedEntry = { expiresAt?: number | null; }; +type PluginStateDatabaseSeedEntry = { + pluginId: string; + namespace: string; + key: string; + valueJson: string; + createdAt?: number; + expiresAt?: number | null; +}; + +type PluginStateSqliteTestApi = { + probePluginStateStore(): PluginStateStoreProbeResult; + seedPluginStateDatabaseEntriesForTests(entries: readonly PluginStateDatabaseSeedEntry[]): void; + setMaxPluginStateEntriesPerPluginForTests(value?: number): void; +}; + +type PluginStateStoreTestApi = { + clearPluginStateStoreForTests(): void; +}; + +function getSqliteTestApi(): PluginStateSqliteTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.pluginStateSqliteTestApi") + ] as PluginStateSqliteTestApi; +} + +function getStoreTestApi(): PluginStateStoreTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.pluginStateStoreTestApi") + ] as PluginStateStoreTestApi; +} + +export function clearPluginStateStoreForTests(): void { + getStoreTestApi().clearPluginStateStoreForTests(); +} + +export function probePluginStateStore(): PluginStateStoreProbeResult { + return getSqliteTestApi().probePluginStateStore(); +} + +export function setMaxPluginStateEntriesPerPluginForTests(value?: number): void { + getSqliteTestApi().setMaxPluginStateEntriesPerPluginForTests(value); +} + /** Seeds plugin state entries for tests without opening public store handles. */ export function seedPluginStateEntriesForTests(entries: PluginStateSeedEntry[]): void { if (entries.length === 0) { return; } - seedPluginStateDatabaseEntriesForTests( + getSqliteTestApi().seedPluginStateDatabaseEntriesForTests( entries.map((entry) => { const valueJson = JSON.stringify(entry.value); if (valueJson == null) { diff --git a/src/plugin-state/plugin-state-store.test.ts b/src/plugin-state/plugin-state-store.test.ts index 3fb3bc44ae92..165456f8fcd5 100644 --- a/src/plugin-state/plugin-state-store.test.ts +++ b/src/plugin-state/plugin-state-store.test.ts @@ -11,7 +11,6 @@ import { type OpenClawTestState, } from "../test-utils/openclaw-test-state.js"; import { - clearPluginStateStoreForTests, closePluginStateDatabase, createCorePluginStateSyncKeyedStore, createPluginStateKeyedStore, @@ -20,10 +19,11 @@ import { sweepExpiredPluginStateEntries, } from "./plugin-state-store.js"; import { + clearPluginStateStoreForTests, probePluginStateStore, + seedPluginStateEntriesForTests, setMaxPluginStateEntriesPerPluginForTests, -} from "./plugin-state-store.sqlite.js"; -import { seedPluginStateEntriesForTests } from "./plugin-state-store.test-helpers.js"; +} from "./plugin-state-store.test-helpers.js"; import { PluginStateStoreError } from "./plugin-state-store.types.js"; let testState: OpenClawTestState | undefined; diff --git a/src/plugin-state/plugin-state-store.ts b/src/plugin-state/plugin-state-store.ts index a767c0f1a006..82ebb24c6d17 100644 --- a/src/plugin-state/plugin-state-store.ts +++ b/src/plugin-state/plugin-state-store.ts @@ -511,7 +511,7 @@ export function createCorePluginStateSyncKeyedStore( } /** Clears plugin-state rows and option signatures for tests. */ -export function clearPluginStateStoreForTests(): void { +function clearPluginStateStoreForTests(): void { clearPluginStateDatabaseForTests(); namespaceOptionSignatures.clear(); } @@ -524,3 +524,9 @@ export function resetPluginStateStoreForTests(options: { closeDatabase?: boolean } namespaceOptionSignatures.clear(); } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.pluginStateStoreTestApi")] = { + clearPluginStateStoreForTests, + }; +} diff --git a/src/proxy-capture/runtime.test.ts b/src/proxy-capture/runtime.test.ts index ad90a45308ab..526798087fb1 100644 --- a/src/proxy-capture/runtime.test.ts +++ b/src/proxy-capture/runtime.test.ts @@ -1,9 +1,7 @@ // Proxy capture runtime tests cover session creation and capture lifecycle. import { beforeEach, describe, expect, it, vi } from "vitest"; -import { - registerSecretValueForRedaction, - resetSecretRedactionRegistryForTest, -} from "../logging/secret-redaction-registry.js"; +import { registerSecretValueForRedaction } from "../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js"; import type { DebugProxySettings } from "./env.js"; import { captureHttpExchange, diff --git a/src/secrets/runtime.test.ts b/src/secrets/runtime.test.ts index c661966194cb..585e861bf8b5 100644 --- a/src/secrets/runtime.test.ts +++ b/src/secrets/runtime.test.ts @@ -1,7 +1,7 @@ /** Tests runtime SecretRef resolution across core config and auth-profile surfaces. */ import { afterEach, describe, expect, it } from "vitest"; import { redactSensitiveText } from "../logging/redact.js"; -import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js"; import { asConfig, setupSecretsRuntimeSnapshotTestHooks } from "./runtime.test-support.ts"; const EMPTY_LOADABLE_PLUGIN_ORIGINS = new Map(); diff --git a/src/secrets/sentinel.test.ts b/src/secrets/sentinel.test.ts index 20d4687ae773..0f95b71c73ea 100644 --- a/src/secrets/sentinel.test.ts +++ b/src/secrets/sentinel.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it } from "vitest"; import { redactSensitiveText } from "../logging/redact.js"; -import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.js"; +import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js"; import { looksLikeSecretSentinel, mintSecretSentinel, diff --git a/src/skills/lifecycle/install-fallback.test.ts b/src/skills/lifecycle/install-fallback.test.ts index bbf5827a85a0..187f6428bb8f 100644 --- a/src/skills/lifecycle/install-fallback.test.ts +++ b/src/skills/lifecycle/install-fallback.test.ts @@ -25,14 +25,11 @@ vi.mock("../loading/workspace.js", () => ({ let installSkill: typeof import("./install.js").installSkill; let resolveInstallerKindReadiness: typeof import("./install.js").resolveInstallerKindReadiness; -let skillsInstallTesting: typeof import("./install.js").testing; +let skillsInstallTesting: typeof import("./install.test-support.js").skillsInstallTesting; async function loadSkillsInstallModulesForTest() { - ({ - installSkill, - resolveInstallerKindReadiness, - testing: skillsInstallTesting, - } = await import("./install.js")); + ({ installSkill, resolveInstallerKindReadiness } = await import("./install.js")); + ({ skillsInstallTesting } = await import("./install.test-support.js")); } function makeSkillEntry( diff --git a/src/skills/lifecycle/install.test-support.ts b/src/skills/lifecycle/install.test-support.ts new file mode 100644 index 000000000000..844eab0c3517 --- /dev/null +++ b/src/skills/lifecycle/install.test-support.ts @@ -0,0 +1,39 @@ +import type { resolveSkillsInstallPreferences } from "../loading/config.js"; +import type { loadWorkspaceSkillEntries } from "../loading/workspace.js"; +import "./install.js"; + +type SkillsInstallDeps = { + hasBinary(bin: string): boolean; + loadWorkspaceSkillEntries: typeof loadWorkspaceSkillEntries; + resolveNodeInstallStateDir(): string; + resolveBrewExecutable(): string | undefined; + isContainerEnvironment(): boolean; + resolveSkillsInstallPreferences: typeof resolveSkillsInstallPreferences; +}; + +type ResolveDefaultNodeInstallStateDirParams = { + cwd?: string; + getuid?: () => number; + homedir?: () => string; + platform?: NodeJS.Platform; +}; + +type SkillsInstallTestApi = { + resolveDefaultNodeInstallStateDir(params?: ResolveDefaultNodeInstallStateDirParams): string; + setDepsForTest(overrides?: Partial): void; +}; + +function getTestApi(): SkillsInstallTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.skillsInstallTestApi") + ] as SkillsInstallTestApi; +} + +export const skillsInstallTesting: SkillsInstallTestApi = { + resolveDefaultNodeInstallStateDir(params) { + return getTestApi().resolveDefaultNodeInstallStateDir(params); + }, + setDepsForTest(overrides) { + getTestApi().setDepsForTest(overrides); + }, +}; diff --git a/src/skills/lifecycle/install.test.ts b/src/skills/lifecycle/install.test.ts index efe4d683bd52..f548d048a527 100644 --- a/src/skills/lifecycle/install.test.ts +++ b/src/skills/lifecycle/install.test.ts @@ -13,7 +13,8 @@ import { resolveOpenClawMetadata, resolveSkillInvocationPolicy } from "../loadin import { loadSkillsFromDirSafe, readSkillFrontmatterSafe } from "../loading/local-loader.js"; import { runCommandWithTimeoutMock } from "../test-support/install-test-mocks.js"; import type { SkillEntry } from "../types.js"; -import { installSkill, testing as skillsInstallTesting } from "./install.js"; +import { installSkill } from "./install.js"; +import { skillsInstallTesting } from "./install.test-support.js"; vi.mock("../../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), diff --git a/src/skills/lifecycle/install.ts b/src/skills/lifecycle/install.ts index 90f1ca5c68dc..5cb27bbba0c8 100644 --- a/src/skills/lifecycle/install.ts +++ b/src/skills/lifecycle/install.ts @@ -819,7 +819,7 @@ export async function installSkill(params: SkillInstallRequest): Promise): void { skillsInstallDeps = { @@ -828,4 +828,9 @@ export const testing = { }; }, }; + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.skillsInstallTestApi")] = + testing; +} /* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */ diff --git a/src/skills/lifecycle/upload-store.test-support.ts b/src/skills/lifecycle/upload-store.test-support.ts new file mode 100644 index 000000000000..1c1cfb853d98 --- /dev/null +++ b/src/skills/lifecycle/upload-store.test-support.ts @@ -0,0 +1,22 @@ +import type { SkillUploadStore } from "./upload-store.js"; +import "./upload-store.js"; + +type SkillUploadStoreTestApi = { + createSkillUploadStore(options?: { + rootDir?: string; + now?: () => number; + ttlMs?: number; + }): SkillUploadStore; +}; + +function getTestApi(): SkillUploadStoreTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.skillUploadStoreTestApi") + ] as SkillUploadStoreTestApi; +} + +export function createSkillUploadStore( + options?: Parameters[0], +): SkillUploadStore { + return getTestApi().createSkillUploadStore(options); +} diff --git a/src/skills/lifecycle/upload-store.test.ts b/src/skills/lifecycle/upload-store.test.ts index 51ff1679ac43..e75f7124aa0d 100644 --- a/src/skills/lifecycle/upload-store.test.ts +++ b/src/skills/lifecycle/upload-store.test.ts @@ -5,7 +5,8 @@ import os from "node:os"; import path from "node:path"; import { MAX_DATE_TIMESTAMP_MS } from "@openclaw/normalization-core/number-coercion"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { createSkillUploadStore, SkillUploadRequestError } from "./upload-store.js"; +import { SkillUploadRequestError } from "./upload-store.js"; +import { createSkillUploadStore } from "./upload-store.test-support.js"; const ACTIVE_UPLOAD_LIMIT = 32; diff --git a/src/skills/lifecycle/upload-store.ts b/src/skills/lifecycle/upload-store.ts index 563de9ec78c5..e673ba177a65 100644 --- a/src/skills/lifecycle/upload-store.ts +++ b/src/skills/lifecycle/upload-store.ts @@ -361,7 +361,7 @@ async function readCommittedRecord( return record; } -export function createSkillUploadStore(options?: { +function createSkillUploadStore(options?: { rootDir?: string; now?: () => number; ttlMs?: number; @@ -608,3 +608,9 @@ export function createSkillUploadStore(options?: { } export const defaultSkillUploadStore = createSkillUploadStore(); + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.skillUploadStoreTestApi")] = { + createSkillUploadStore, + }; +} diff --git a/src/skills/runtime/refresh.test-support.ts b/src/skills/runtime/refresh.test-support.ts new file mode 100644 index 000000000000..c730a55cb89a --- /dev/null +++ b/src/skills/runtime/refresh.test-support.ts @@ -0,0 +1,15 @@ +import "./refresh.js"; + +type SkillsRefreshTestApi = { + resetSkillsRefreshForTest(): Promise; +}; + +function getTestApi(): SkillsRefreshTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.skillsRefreshTestApi") + ] as SkillsRefreshTestApi; +} + +export async function resetSkillsRefreshForTest(): Promise { + await getTestApi().resetSkillsRefreshForTest(); +} diff --git a/src/skills/runtime/refresh.test.ts b/src/skills/runtime/refresh.test.ts index d037640966b0..005ec8d47553 100644 --- a/src/skills/runtime/refresh.test.ts +++ b/src/skills/runtime/refresh.test.ts @@ -38,6 +38,7 @@ const watchMock = vi.fn(() => { }); let refreshModule: typeof import("./refresh.js"); +let refreshTestSupport: typeof import("./refresh.test-support.js"); vi.mock("chokidar", () => ({ default: { watch: watchMock }, @@ -50,6 +51,7 @@ vi.mock("../loading/plugin-skills.js", () => ({ describe("ensureSkillsWatcher", () => { beforeAll(async () => { refreshModule = await import("./refresh.js"); + refreshTestSupport = await import("./refresh.test-support.js"); }); beforeEach(() => { @@ -59,7 +61,7 @@ describe("ensureSkillsWatcher", () => { afterEach(async () => { vi.useRealTimers(); - await refreshModule.resetSkillsRefreshForTest(); + await refreshTestSupport.resetSkillsRefreshForTest(); }); it("watches skill roots and filters non-skill churn", async () => { diff --git a/src/skills/runtime/refresh.ts b/src/skills/runtime/refresh.ts index 1eea952dea08..071efbad1f41 100644 --- a/src/skills/runtime/refresh.ts +++ b/src/skills/runtime/refresh.ts @@ -712,7 +712,7 @@ export function ensureSkillsWatcher(params: { workspaceDir: string; config?: Ope evictIdleWorkspaceWatchStates(now); } -export async function resetSkillsRefreshForTest(): Promise { +async function resetSkillsRefreshForTest(): Promise { resetSkillsRefreshStateForTest(); const active = Array.from(pathWatchers.values()); @@ -733,3 +733,9 @@ export async function resetSkillsRefreshForTest(): Promise { }), ); } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.skillsRefreshTestApi")] = { + resetSkillsRefreshForTest, + }; +} diff --git a/src/skills/runtime/remote-skills.test-support.ts b/src/skills/runtime/remote-skills.test-support.ts new file mode 100644 index 000000000000..2c1ab8a50d0f --- /dev/null +++ b/src/skills/runtime/remote-skills.test-support.ts @@ -0,0 +1,15 @@ +import "./remote-skills.js"; + +type RemoteNodeSkillsTestApi = { + resetRemoteNodeSkillsForTests(): void; +}; + +function getTestApi(): RemoteNodeSkillsTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.remoteNodeSkillsTestApi") + ] as RemoteNodeSkillsTestApi; +} + +export function resetRemoteNodeSkillsForTests(): void { + getTestApi().resetRemoteNodeSkillsForTests(); +} diff --git a/src/skills/runtime/remote-skills.test.ts b/src/skills/runtime/remote-skills.test.ts index 9fc95b1cdaa4..4842d2f8ef2e 100644 --- a/src/skills/runtime/remote-skills.test.ts +++ b/src/skills/runtime/remote-skills.test.ts @@ -10,8 +10,8 @@ import { recordRemoteSkillNodeInfo, removeRemoteNodeSkills, replaceRemoteNodeSkills, - resetRemoteNodeSkillsForTests, } from "./remote-skills.js"; +import { resetRemoteNodeSkillsForTests } from "./remote-skills.test-support.js"; function content(name: string, description: string, body = "# Instructions"): string { return `---\nname: ${name}\ndescription: ${description}\n---\n\n${body}\n`; diff --git a/src/skills/runtime/remote-skills.ts b/src/skills/runtime/remote-skills.ts index 372ba41c2c77..330ebd7fad4c 100644 --- a/src/skills/runtime/remote-skills.ts +++ b/src/skills/runtime/remote-skills.ts @@ -246,6 +246,12 @@ export function mergeRemoteNodeSkillEntries( ); } -export function resetRemoteNodeSkillsForTests(): void { +function resetRemoteNodeSkillsForTests(): void { remoteSkillNodes.clear(); } + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.remoteNodeSkillsTestApi")] = { + resetRemoteNodeSkillsForTests, + }; +} diff --git a/src/skills/runtime/remote.test.ts b/src/skills/runtime/remote.test.ts index 83b9f4b1120b..947e882746fe 100644 --- a/src/skills/runtime/remote.test.ts +++ b/src/skills/runtime/remote.test.ts @@ -7,7 +7,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { NodeRegistry } from "../../gateway/node-registry.js"; import { getSkillsSnapshotVersion } from "./refresh-state.js"; -import { resetSkillsRefreshForTest } from "./refresh.js"; +import { resetSkillsRefreshForTest } from "./refresh.test-support.js"; import { getRemoteSkillEligibility, recordRemoteNodeBins, diff --git a/src/system-agent/agent-turn.test-support.ts b/src/system-agent/agent-turn.test-support.ts new file mode 100644 index 000000000000..0d8b5952b884 --- /dev/null +++ b/src/system-agent/agent-turn.test-support.ts @@ -0,0 +1,41 @@ +import type { SystemAgentTurnRunner } from "./agent-turn.js"; +import "./agent-turn.js"; +import type { SystemAgentVerifiedInferenceDeps } from "./verified-inference.js"; + +type SystemAgentRunEmbeddedAgent = ( + params: Parameters[0] & { + systemAgentTool?: import("../agents/tools/system-agent-tool.js").SystemAgentToolOptions; + }, +) => ReturnType; + +type SystemAgentRunCliAgent = ( + params: Parameters[0] & { + systemAgentTool?: import("../agents/tools/system-agent-tool.js").SystemAgentToolOptions; + }, +) => ReturnType; + +export type SystemAgentTurnDeps = SystemAgentVerifiedInferenceDeps & { + runEmbeddedAgent?: SystemAgentRunEmbeddedAgent; + runCliAgent?: SystemAgentRunCliAgent; + readConfigFileSnapshot?: typeof import("../config/config.js").readConfigFileSnapshot; +}; + +type SystemAgentTurnTestApi = { + runSystemAgentTurnWithDeps( + params: Parameters[0], + deps?: SystemAgentTurnDeps, + ): ReturnType; +}; + +function getTestApi(): SystemAgentTurnTestApi { + return (globalThis as Record)[ + Symbol.for("openclaw.systemAgentTurnTestApi") + ] as SystemAgentTurnTestApi; +} + +export function runSystemAgentTurnWithDeps( + params: Parameters[0], + deps: SystemAgentTurnDeps = {}, +): ReturnType { + return getTestApi().runSystemAgentTurnWithDeps(params, deps); +} diff --git a/src/system-agent/agent-turn.test.ts b/src/system-agent/agent-turn.test.ts index 1574355e4472..0c40c2684c09 100644 --- a/src/system-agent/agent-turn.test.ts +++ b/src/system-agent/agent-turn.test.ts @@ -8,9 +8,9 @@ import type { CliBackendConfig, OpenClawConfig } from "../config/types.js"; import { cleanupSystemAgentSession, createSystemAgentSession, - runSystemAgentTurnWithDeps, type SystemAgentSession, } from "./agent-turn.js"; +import { runSystemAgentTurnWithDeps, type SystemAgentTurnDeps } from "./agent-turn.test-support.js"; import { SystemAgentInferenceUnavailableError } from "./inference-error.js"; import { resolveSystemAgentConfiguredRouteFromConfig } from "./inference-route.js"; import { createSystemAgentVerifiedInferenceTestFixture } from "./system-agent.test-helpers.js"; @@ -29,7 +29,6 @@ vi.mock("../agents/harness/runtime-plugin.js", async (importOriginal) => ({ ), })); -type SystemAgentTurnDeps = NonNullable[1]>; type RunCliAgentParams = Parameters>[0]; type RunEmbeddedAgentParams = Parameters>[0]; diff --git a/src/system-agent/agent-turn.ts b/src/system-agent/agent-turn.ts index 3f2a751ec589..da2025d57d18 100644 --- a/src/system-agent/agent-turn.ts +++ b/src/system-agent/agent-turn.ts @@ -272,7 +272,7 @@ async function mirrorSystemAgentToolStateFromEvents(params: { * output failures are typed so callers may try another inference path without * mistaking the failure for deterministic setup authority. */ -export async function runSystemAgentTurnWithDeps( +async function runSystemAgentTurnWithDeps( params: SystemAgentTurnParams, deps: SystemAgentTurnDeps = {}, ): Promise { @@ -435,3 +435,9 @@ export async function runSystemAgentTurnWithDeps( export const runSystemAgentTurn: SystemAgentTurnRunner = (params) => runSystemAgentTurnWithDeps(params); + +if (process.env.VITEST || process.env.NODE_ENV === "test") { + (globalThis as Record)[Symbol.for("openclaw.systemAgentTurnTestApi")] = { + runSystemAgentTurnWithDeps, + }; +} diff --git a/src/system-agent/chat-engine.test.ts b/src/system-agent/chat-engine.test.ts index 23d880fdd023..ca1b2d4869a5 100644 --- a/src/system-agent/chat-engine.test.ts +++ b/src/system-agent/chat-engine.test.ts @@ -12,7 +12,7 @@ import { import { hashSystemAgentOperation } from "../agents/tools/system-agent-tool.js"; import type { ConfigFileSnapshot, OpenClawConfig } from "../config/types.openclaw.js"; import type { WizardPrompter } from "../wizard/prompts.js"; -import { runSystemAgentTurnWithDeps } from "./agent-turn.js"; +import { runSystemAgentTurnWithDeps } from "./agent-turn.test-support.js"; import { classifySystemAgentApprovalText } from "./approval-intent.js"; import { SystemAgentChatEngine as RuntimeSystemAgentChatEngine, diff --git a/src/system-agent/setup-inference.test.ts b/src/system-agent/setup-inference.test.ts index edbece787657..bfa267ce7264 100644 --- a/src/system-agent/setup-inference.test.ts +++ b/src/system-agent/setup-inference.test.ts @@ -32,11 +32,8 @@ import { import { ensurePluginRegistryLoaded } from "../plugins/runtime/runtime-registry-loader.js"; import type { ProviderPlugin } from "../plugins/types.js"; import { disposeOpenClawAgentDatabaseByPath } from "../state/openclaw-agent-db.js"; -import { - cleanupSystemAgentSession, - createSystemAgentSession, - runSystemAgentTurnWithDeps, -} from "./agent-turn.js"; +import { cleanupSystemAgentSession, createSystemAgentSession } from "./agent-turn.js"; +import { runSystemAgentTurnWithDeps } from "./agent-turn.test-support.js"; import { resolveSystemAgentConfiguredRouteFromConfig } from "./inference-route.js"; import { applySystemAgentModelSelection } from "./setup-apply.js"; import { resolveSetupInferenceProbeStreamParams } from "./setup-inference-probe.js";