From 4e7dae42225551be5f920bef39105041d355ea4f Mon Sep 17 00:00:00 2001 From: Amp Date: Fri, 21 Aug 2026 05:06:04 +0000 Subject: [PATCH] test(system-agent): extract setup apply harness --- src/system-agent/setup-apply.test-harness.ts | 217 ++++++++++++++++++ src/system-agent/setup-apply.test.ts | 222 ++----------------- 2 files changed, 231 insertions(+), 208 deletions(-) create mode 100644 src/system-agent/setup-apply.test-harness.ts diff --git a/src/system-agent/setup-apply.test-harness.ts b/src/system-agent/setup-apply.test-harness.ts new file mode 100644 index 000000000000..219479f22f9f --- /dev/null +++ b/src/system-agent/setup-apply.test-harness.ts @@ -0,0 +1,217 @@ +import { vi } from "vitest"; +import { resolveAgentEntry } from "../agents/agent-scope-config.js"; +import * as configModule from "../config/config.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { RuntimeEnv } from "../runtime.js"; + +type ConfigSnapshot = { + exists: boolean; + valid: boolean; + path: string; + hash: string | null; + parsed: unknown; + sourceConfigBeforeMigrations?: OpenClawConfig; + config: OpenClawConfig; + sourceConfig: OpenClawConfig; + runtimeConfig?: OpenClawConfig; + issues: Array<{ path?: string; message: string }>; +}; + +export type CommitTransform = ( + currentConfig: OpenClawConfig, + context: { + previousHash: string | null; + snapshot: ConfigSnapshot; + attempt: number; + }, +) => + | { nextConfig: OpenClawConfig; result?: unknown } + | Promise<{ nextConfig: OpenClawConfig; result?: unknown }>; + +const mocks = vi.hoisted(() => ({ + state: { + initialSnapshot: {} as ConfigSnapshot, + commitConfig: {} as OpenClawConfig, + commitSnapshot: {} as ConfigSnapshot, + commitPreviousHash: "probe" as string | null, + persistedConfig: undefined as OpenClawConfig | undefined, + }, + events: [] as string[], + readSnapshot: vi.fn<() => Promise>(), + readVerifiedSnapshot: vi.fn<() => Promise>(), + readVerifiedSnapshotWithPluginMetadata: vi.fn(), + commit: vi.fn(), + configureGateway: vi.fn(), + ensureWorkspace: vi.fn(), + ensureGatewayService: vi.fn(), + waitForGatewayReachable: vi.fn<() => Promise<{ ok: boolean; detail?: string }>>(), + refreshPluginRegistry: vi.fn(), + updateExecApprovals: vi.fn(), + ensureOnboardingAgent: vi.fn(), + verifySetupInferenceConfig: vi.fn(), +})); + +vi.mock("../commands/onboard-agent.js", async (importOriginal) => ({ + ...(await importOriginal()), + ensureOnboardingAgent: mocks.ensureOnboardingAgent, +})); + +vi.mock("./setup-inference.js", () => ({ + verifySetupInferenceConfig: mocks.verifySetupInferenceConfig, +})); + +vi.mock("../config/config.js", async (importOriginal) => ({ + ...(await importOriginal()), + readConfigFileSnapshot: mocks.readVerifiedSnapshot, + readConfigFileSnapshotWithPluginMetadata: mocks.readVerifiedSnapshotWithPluginMetadata, +})); + +vi.mock("../wizard/setup.shared.js", async (importOriginal) => ({ + ...(await importOriginal()), + readSetupConfigFileSnapshot: mocks.readSnapshot, +})); + +vi.mock("../commands/onboard-helpers.js", () => ({ + applyWizardMetadata: (config: OpenClawConfig) => ({ + ...config, + wizard: { + ...config.wizard, + lastRunAt: "2026-07-10T00:00:00.000Z", + lastRunVersion: "test", + lastRunCommand: "onboard", + lastRunMode: "local", + }, + }), + ensureWorkspaceAndSessions: mocks.ensureWorkspace, + resolveLocalControlUiProbeLinks: ({ port }: { port: number }) => ({ + wsUrl: `ws://127.0.0.1:${port}`, + }), + waitForGatewayReachable: mocks.waitForGatewayReachable, +})); + +vi.mock("../plugins/install-record-commit.js", async (importOriginal) => ({ + ...(await importOriginal()), + transformConfigWithPendingPluginInstalls: mocks.commit, +})); + +vi.mock("../wizard/setup.gateway-config.js", () => ({ + configureGatewayForSetup: mocks.configureGateway, +})); + +vi.mock("../wizard/setup.finalize.js", () => ({ + ensureGatewayServiceForOnboarding: mocks.ensureGatewayService, +})); + +vi.mock("../plugins/registry-refresh.js", () => ({ + refreshPluginRegistryAfterConfigMutation: mocks.refreshPluginRegistry, +})); + +vi.mock("../infra/exec-approvals.js", () => ({ + updateExecApprovals: mocks.updateExecApprovals, +})); + +vi.mock("../agents/agent-scope.js", async (importOriginal) => ({ + ...(await importOriginal()), + resolveAgentDir: (config: OpenClawConfig, agentId: string) => + resolveAgentEntry(config, agentId)?.agentDir ?? `/agents/${agentId}`, +})); + +export function getSetupApplyMocks() { + return mocks; +} + +export const runtime: RuntimeEnv = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), +}; + +export function snapshot( + hash: string | null, + sourceConfig: OpenClawConfig, + runtimeConfig: OpenClawConfig = sourceConfig, +): ConfigSnapshot { + return { + exists: hash !== null, + valid: true, + path: "/tmp/openclaw.json", + hash, + parsed: structuredClone(sourceConfig), + sourceConfigBeforeMigrations: structuredClone(sourceConfig), + config: runtimeConfig, + sourceConfig: runtimeConfig, + runtimeConfig, + issues: [], + }; +} + +export function codexPluginMetadataSnapshot(homeScope: "agent" | "user") { + return { + manifestRegistry: { + diagnostics: [], + plugins: [ + { + id: "codex", + origin: "global", + channels: [], + providers: [], + cliBackends: [], + skills: [], + settingsFiles: [], + hooks: [], + rootDir: "/tmp/codex", + source: "/tmp/codex/index.js", + manifestPath: "/tmp/codex/openclaw.plugin.json", + configSchema: { + type: "object", + additionalProperties: false, + properties: { + codexDynamicToolsLoading: { type: "string", default: "searchable" }, + appServer: { + type: "object", + additionalProperties: false, + properties: { + transport: { type: "string", default: "stdio" }, + homeScope: { type: "string", default: homeScope }, + requestTimeoutMs: { type: "number", default: 60_000 }, + }, + }, + }, + }, + }, + ], + }, + } as never; +} + +export function materializePluginDefaults( + config: OpenClawConfig, + pluginMetadataSnapshot: ReturnType, +): OpenClawConfig { + const result = configModule.validateConfigObjectWithPlugins(config, { pluginMetadataSnapshot }); + if (!result.ok) { + throw new Error(result.issues[0]?.message ?? "test config failed validation"); + } + return result.config; +} + +export function baseParams( + overrides: Partial[0]> = {}, +) { + return { + workspace: "/tmp/openclaw-workspace", + surface: "gateway" as const, + runtime, + ...overrides, + }; +} + +export function mainAgentModelConfig(model = "openai/gpt-5.5"): OpenClawConfig { + return { agents: { defaults: { model }, entries: { main: { default: true } } } }; +} + +export function setSetupCommitState(config: OpenClawConfig, initialSnapshot: ConfigSnapshot): void { + mocks.state.initialSnapshot = initialSnapshot; + mocks.state.commitConfig = config; + mocks.state.commitSnapshot = initialSnapshot; +} diff --git a/src/system-agent/setup-apply.test.ts b/src/system-agent/setup-apply.test.ts index 0c32d9cbde5d..d01dfdcf3d1d 100644 --- a/src/system-agent/setup-apply.test.ts +++ b/src/system-agent/setup-apply.test.ts @@ -1,223 +1,29 @@ +// Preserve module setup before modules that consume it. +// oxfmt-ignore +import { + baseParams, + type CommitTransform, + codexPluginMetadataSnapshot, + getSetupApplyMocks, + mainAgentModelConfig, + materializePluginDefaults, + runtime, + setSetupCommitState, + snapshot, +} from "./setup-apply.test-harness.js"; import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { resolveAgentEntry } from "../agents/agent-scope-config.js"; import * as configModule from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { RuntimeEnv } from "../runtime.js"; import { withEnvAsync } from "../test-utils/env.js"; import { projectDefaultInferenceRoute } from "./inference-route.js"; - -type ConfigSnapshot = { - exists: boolean; - valid: boolean; - path: string; - hash: string | null; - parsed: unknown; - sourceConfigBeforeMigrations?: OpenClawConfig; - config: OpenClawConfig; - sourceConfig: OpenClawConfig; - runtimeConfig?: OpenClawConfig; - issues: Array<{ path?: string; message: string }>; -}; - -type CommitTransform = ( - currentConfig: OpenClawConfig, - context: { - previousHash: string | null; - snapshot: ConfigSnapshot; - attempt: number; - }, -) => - | { nextConfig: OpenClawConfig; result?: unknown } - | Promise<{ nextConfig: OpenClawConfig; result?: unknown }>; - -const mocks = vi.hoisted(() => ({ - state: { - initialSnapshot: {} as ConfigSnapshot, - commitConfig: {} as OpenClawConfig, - commitSnapshot: {} as ConfigSnapshot, - commitPreviousHash: "probe" as string | null, - persistedConfig: undefined as OpenClawConfig | undefined, - }, - events: [] as string[], - readSnapshot: vi.fn<() => Promise>(), - readVerifiedSnapshot: vi.fn<() => Promise>(), - readVerifiedSnapshotWithPluginMetadata: vi.fn(), - commit: vi.fn(), - configureGateway: vi.fn(), - ensureWorkspace: vi.fn(), - ensureGatewayService: vi.fn(), - waitForGatewayReachable: vi.fn<() => Promise<{ ok: boolean; detail?: string }>>(), - refreshPluginRegistry: vi.fn(), - updateExecApprovals: vi.fn(), - ensureOnboardingAgent: vi.fn(), - verifySetupInferenceConfig: vi.fn(), -})); - -vi.mock("../commands/onboard-agent.js", async (importOriginal) => ({ - ...(await importOriginal()), - ensureOnboardingAgent: mocks.ensureOnboardingAgent, -})); - -vi.mock("./setup-inference.js", () => ({ - verifySetupInferenceConfig: mocks.verifySetupInferenceConfig, -})); - -vi.mock("../config/config.js", async (importOriginal) => ({ - ...(await importOriginal()), - readConfigFileSnapshot: mocks.readVerifiedSnapshot, - readConfigFileSnapshotWithPluginMetadata: mocks.readVerifiedSnapshotWithPluginMetadata, -})); - -vi.mock("../wizard/setup.shared.js", async (importOriginal) => ({ - ...(await importOriginal()), - readSetupConfigFileSnapshot: mocks.readSnapshot, -})); - -vi.mock("../commands/onboard-helpers.js", () => ({ - applyWizardMetadata: (config: OpenClawConfig) => ({ - ...config, - wizard: { - ...config.wizard, - lastRunAt: "2026-07-10T00:00:00.000Z", - lastRunVersion: "test", - lastRunCommand: "onboard", - lastRunMode: "local", - }, - }), - ensureWorkspaceAndSessions: mocks.ensureWorkspace, - resolveLocalControlUiProbeLinks: ({ port }: { port: number }) => ({ - wsUrl: `ws://127.0.0.1:${port}`, - }), - waitForGatewayReachable: mocks.waitForGatewayReachable, -})); - -vi.mock("../plugins/install-record-commit.js", async (importOriginal) => ({ - ...(await importOriginal()), - transformConfigWithPendingPluginInstalls: mocks.commit, -})); - -vi.mock("../wizard/setup.gateway-config.js", () => ({ - configureGatewayForSetup: mocks.configureGateway, -})); - -vi.mock("../wizard/setup.finalize.js", () => ({ - ensureGatewayServiceForOnboarding: mocks.ensureGatewayService, -})); - -vi.mock("../plugins/registry-refresh.js", () => ({ - refreshPluginRegistryAfterConfigMutation: mocks.refreshPluginRegistry, -})); - -vi.mock("../infra/exec-approvals.js", () => ({ - updateExecApprovals: mocks.updateExecApprovals, -})); - -vi.mock("../agents/agent-scope.js", async (importOriginal) => ({ - ...(await importOriginal()), - resolveAgentDir: (config: OpenClawConfig, agentId: string) => - resolveAgentEntry(config, agentId)?.agentDir ?? `/agents/${agentId}`, -})); - import { applySystemAgentSetup } from "./setup-apply.js"; -const runtime: RuntimeEnv = { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn(), -}; +const mocks = getSetupApplyMocks(); const testTempDirs = useAutoCleanupTempDirTracker(afterEach); -function snapshot( - hash: string | null, - sourceConfig: OpenClawConfig, - runtimeConfig: OpenClawConfig = sourceConfig, -): ConfigSnapshot { - return { - exists: hash !== null, - valid: true, - path: "/tmp/openclaw.json", - hash, - parsed: structuredClone(sourceConfig), - sourceConfigBeforeMigrations: structuredClone(sourceConfig), - config: runtimeConfig, - sourceConfig: runtimeConfig, - runtimeConfig, - issues: [], - }; -} - -function codexPluginMetadataSnapshot(homeScope: "agent" | "user") { - return { - manifestRegistry: { - diagnostics: [], - plugins: [ - { - id: "codex", - origin: "global", - channels: [], - providers: [], - cliBackends: [], - skills: [], - settingsFiles: [], - hooks: [], - rootDir: "/tmp/codex", - source: "/tmp/codex/index.js", - manifestPath: "/tmp/codex/openclaw.plugin.json", - configSchema: { - type: "object", - additionalProperties: false, - properties: { - codexDynamicToolsLoading: { type: "string", default: "searchable" }, - appServer: { - type: "object", - additionalProperties: false, - properties: { - transport: { type: "string", default: "stdio" }, - homeScope: { type: "string", default: homeScope }, - requestTimeoutMs: { type: "number", default: 60_000 }, - }, - }, - }, - }, - }, - ], - }, - } as never; -} - -function materializePluginDefaults( - config: OpenClawConfig, - pluginMetadataSnapshot: ReturnType, -): OpenClawConfig { - const result = configModule.validateConfigObjectWithPlugins(config, { pluginMetadataSnapshot }); - if (!result.ok) { - throw new Error(result.issues[0]?.message ?? "test config failed validation"); - } - return result.config; -} - -function baseParams(overrides: Partial[0]> = {}) { - return { - workspace: "/tmp/openclaw-workspace", - surface: "gateway" as const, - runtime, - ...overrides, - }; -} - -function mainAgentModelConfig(model = "openai/gpt-5.5"): OpenClawConfig { - return { agents: { defaults: { model }, entries: { main: { default: true } } } }; -} - -function setSetupCommitState(config: OpenClawConfig, initialSnapshot: ConfigSnapshot): void { - mocks.state.initialSnapshot = initialSnapshot; - mocks.state.commitConfig = config; - mocks.state.commitSnapshot = initialSnapshot; -} - describe("applySystemAgentSetup transaction boundaries", () => { beforeEach(() => { vi.resetAllMocks();