From 0c1714d2aec2e8f1bc098c7df696eb415bd5535c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 2 Oct 2026 04:50:34 -0700 Subject: [PATCH] test: align roster fixtures with Doctor migrations --- .../node-inference.paired-node.e2e.test.ts | 46 +-- .../whatsapp/whatsapp-live.config.test.ts | 30 +- extensions/telegram/src/accounts.test.ts | 30 +- .../local.legacy-fleet.test.ts | 366 +++++++++++------- src/plugin-sdk/test-env.ts | 1 + test/cli-json-stdout.agents.e2e.test.ts | 26 +- .../plugins/canvas-agent-node.e2e.test.ts | 5 +- .../plugins/workspace-node-files.e2e.test.ts | 5 +- 8 files changed, 308 insertions(+), 201 deletions(-) diff --git a/extensions/ollama/src/node-inference.paired-node.e2e.test.ts b/extensions/ollama/src/node-inference.paired-node.e2e.test.ts index 80487e5d3426..996346e3ecf2 100644 --- a/extensions/ollama/src/node-inference.paired-node.e2e.test.ts +++ b/extensions/ollama/src/node-inference.paired-node.e2e.test.ts @@ -8,7 +8,7 @@ import { performance } from "node:perf_hooks"; import { setTimeout as delay } from "node:timers/promises"; import { GatewayClient } from "openclaw/plugin-sdk/gateway-runtime"; import type { OpenClawPluginNodeHostCommand } from "openclaw/plugin-sdk/plugin-entry"; -import { stopChildProcess } from "openclaw/plugin-sdk/test-env"; +import { createCanonicalAgentConfigFixture, stopChildProcess } from "openclaw/plugin-sdk/test-env"; import { createOpenClawTestState } from "openclaw/plugin-sdk/test-state"; import { describe, expect, it, vi } from "vitest"; import { createOllamaNodeHostCommands } from "./node-inference.js"; @@ -60,28 +60,30 @@ describe("Ollama paired-node Gateway inference", () => { let node: GatewayClient | undefined; try { - await state.writeConfig({ - gateway: { - mode: "local", - port: gatewayPort, - bind: "loopback", - auth: { mode: "token", token: gatewayToken }, - controlUi: { enabled: false }, - nodes: { commands: { allow: ["ollama.models", "ollama.chat"] } }, - }, - plugins: { - allow: ["ollama"], - }, - agents: { - defaults: { heartbeat: { every: "0m" }, skipBootstrap: true }, - entries: { main: { default: true, tools: { allow: ["node_inference"] } } }, - }, - models: { - providers: { - ollama: { api: "ollama", baseUrl: gatewayOllama.baseUrl, models: [] }, + await state.writeConfig( + createCanonicalAgentConfigFixture({ + gateway: { + mode: "local", + port: gatewayPort, + bind: "loopback", + auth: { mode: "token", token: gatewayToken }, + controlUi: { enabled: false }, + nodes: { commands: { allow: ["ollama.models", "ollama.chat"] } }, }, - }, - }); + plugins: { + allow: ["ollama"], + }, + agents: { + defaults: { heartbeat: { every: "0m" }, skipBootstrap: true }, + entries: { main: { default: true, tools: { allow: ["node_inference"] } } }, + }, + models: { + providers: { + ollama: { api: "ollama", baseUrl: gatewayOllama.baseUrl, models: [] }, + }, + }, + }).config, + ); const gatewayEntryArgs = process.env.OPENCLAW_E2E_USE_PREBUILT_DIST === "1" diff --git a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.config.test.ts b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.config.test.ts index f1b5e9245316..ea51726b04fc 100644 --- a/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.config.test.ts +++ b/extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.config.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { readConfigFileSnapshot, resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/health"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; -import { withTempHome } from "openclaw/plugin-sdk/test-env"; +import { createCanonicalAgentConfigFixture, withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; import { buildQaGatewayConfig } from "../../qa-gateway-config.js"; import { buildWhatsAppQaConfig } from "./whatsapp-live.config.js"; @@ -53,7 +53,25 @@ describe("WhatsApp QA broadcast config", () => { sutAccountId: "sut", }); - await fs.writeFile(path.join(home, ".openclaw", "openclaw.json"), JSON.stringify(cfg)); + const configPath = path.join(home, ".openclaw", "openclaw.json"); + const authored = JSON.stringify(cfg); + await fs.writeFile(configPath, authored); + if (roster === "legacy-default") { + const before = await readConfigFileSnapshot({ + pluginValidation: "core-only", + observe: false, + }); + expect(before.valid).toBe(false); + expect(before.sourceConfig.agents?.entries?.main?.default).toBe(true); + expect(await fs.readFile(configPath, "utf8")).toBe(authored); + } + const runtimeConfig = createCanonicalAgentConfigFixture(cfg, { + homedir: () => home, + }).config; + const originalRuntimeConfig = createCanonicalAgentConfigFixture(original, { + homedir: () => home, + }).config; + await fs.writeFile(configPath, JSON.stringify(runtimeConfig)); const snapshot = await readConfigFileSnapshot({ pluginValidation: "core-only", observe: false, @@ -61,12 +79,12 @@ describe("WhatsApp QA broadcast config", () => { expect(snapshot.valid, JSON.stringify(snapshot.issues)).toBe(true); expect(cfg.agents?.defaults).toMatchObject(original.agents?.defaults ?? {}); const route = { channel: "whatsapp", accountId: "sut" }; - expect(resolveAgentRoute({ cfg, ...route }).agentId).toBe( - resolveAgentRoute({ cfg: original, ...route }).agentId, + expect(resolveAgentRoute({ cfg: runtimeConfig, ...route }).agentId).toBe( + resolveAgentRoute({ cfg: originalRuntimeConfig, ...route }).agentId, ); for (const agentId of Object.keys(original.agents?.entries ?? {})) { - expect(resolveAgentWorkspaceDir(cfg, agentId)).toBe( - resolveAgentWorkspaceDir(original, agentId), + expect(resolveAgentWorkspaceDir(runtimeConfig, agentId)).toBe( + resolveAgentWorkspaceDir(originalRuntimeConfig, agentId), ); } expect(cfg.agents?.entries).toMatchObject(original.agents?.entries ?? {}); diff --git a/extensions/telegram/src/accounts.test.ts b/extensions/telegram/src/accounts.test.ts index 2846d3160af1..d778bed66f40 100644 --- a/extensions/telegram/src/accounts.test.ts +++ b/extensions/telegram/src/accounts.test.ts @@ -4,7 +4,11 @@ import path from "node:path"; import { createPluginRuntimeMock } from "openclaw/plugin-sdk/channel-test-helpers"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { readConfigFileSnapshotForWrite } from "openclaw/plugin-sdk/config-mutation"; -import { withEnv, withTempHome } from "openclaw/plugin-sdk/test-env"; +import { + createCanonicalAgentConfigFixture, + withEnv, + withTempHome, +} from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it } from "vitest"; import { listEnabledTelegramAccounts, @@ -183,7 +187,7 @@ describe("resolveDefaultTelegramAccountId", () => { expect(resolveDefaultTelegramAccountId(cfg)).toBe("work"); }); - it("preserves a loaded legacy owner's account until explicit fleet ownership is applied", async () => { + it("uses explicit channel defaults after Doctor repairs a loaded legacy roster", async () => { await withTempHome( async (home) => { const config: OpenClawConfig = { @@ -196,13 +200,25 @@ describe("resolveDefaultTelegramAccountId", () => { }, bindings: [{ agentId: "main", match: { channel: "telegram", accountId: "work" } }], }; - await fs.writeFile(path.join(home, ".openclaw", "openclaw.json"), JSON.stringify(config)); + const configPath = path.join(home, ".openclaw", "openclaw.json"); + const original = JSON.stringify(config); + await fs.writeFile(configPath, original); const { snapshot } = await readConfigFileSnapshotForWrite(); - expect(snapshot.valid).toBe(true); - expect(resolveDefaultTelegramAccountId(snapshot.config)).toBe("work"); - snapshot.config.agents!.ownership = "explicit"; - expect(resolveDefaultTelegramAccountId(snapshot.config)).toBe("alerts"); + expect(snapshot.valid).toBe(false); + expect(snapshot.sourceConfig.agents?.entries?.main?.default).toBe(true); + expect(snapshot.sourceConfig.bindings).toEqual(config.bindings); + expect(await fs.readFile(configPath, "utf8")).toBe(original); + + const { config: repaired } = createCanonicalAgentConfigFixture(snapshot.sourceConfig, { + homedir: () => home, + }); + expect(repaired.agents?.ownership).toBe("explicit"); + expect(repaired.agents?.entries?.main).not.toHaveProperty("default"); + expect(repaired.agents?.defaults?.systemAgent).toEqual({ agentId: "main" }); + expect(repaired.bindings).toContainEqual(config.bindings![0]); + expect(resolveDefaultTelegramAccountId(repaired)).toBe("alerts"); + expect(await fs.readFile(configPath, "utf8")).toBe(original); }, { env: { diff --git a/src/commands/onboard-non-interactive/local.legacy-fleet.test.ts b/src/commands/onboard-non-interactive/local.legacy-fleet.test.ts index 2c04964274c3..7d7f02edca32 100644 --- a/src/commands/onboard-non-interactive/local.legacy-fleet.test.ts +++ b/src/commands/onboard-non-interactive/local.legacy-fleet.test.ts @@ -10,6 +10,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { RuntimeEnv } from "../../runtime.js"; import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js"; import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js"; +import { runDoctorConfigPreflight } from "../doctor-config-preflight.js"; import { applyOnboardingPrimaryModel, type OnboardingAgentTarget, @@ -45,162 +46,227 @@ describe("local setup fleet owner persistence", () => { { legacy: true, include: true }, { legacy: false, include: false }, { legacy: false, include: true }, - ])("preserves a fleet through two real setup writes: %j", async ({ legacy, include }) => { - const { ensureOnboardingAgent } = - await vi.importActual("../onboard-agent.js"); - mocks.ensureOnboardingAgent.mockImplementation(ensureOnboardingAgent); - await withTempHome(async (rawHome) => { - const home = await fs.realpath(rawHome); - const stateDir = path.join(home, ".openclaw"); - const configPath = path.join(stateDir, "openclaw.json"); - const includePath = path.join(stateDir, "roster.json"); - const workspace = path.join(home, "existing-workspace"); - const siblingWorkspace = path.join(home, "alpha-workspace"); - const agentDir = path.join(stateDir, "agents", "beta", "agent"); - const entries = { - alpha: { model: "fixture/alpha", workspace: siblingWorkspace }, - beta: { model: "fixture/beta", ...(!legacy ? { workspace } : {}), agentDir }, - }; - // The legacy default is deliberately neither the first nor lexical agent. - const list = [ - { id: "alpha", ...entries.alpha }, - { id: "beta", default: true, ...entries.beta }, - ]; - const roster = legacy ? { list } : { entries }; - const includeRaw = JSON.stringify(roster); - if (include) { - await fs.writeFile(includePath, includeRaw); - } - const bindings = [ - { type: "route", agentId: "alpha", match: { channel: "discord", accountId: "*" } }, - ]; - const raw = JSON.stringify({ - agents: { - ...(include ? { $include: "./roster.json" } : roster), - ...(!legacy ? { ownership: "explicit" } : {}), - defaults: { - workspace, - model: "fixture/shared", - ...(!legacy ? { systemAgent: { agentId: "beta" } } : {}), + ])( + "preserves a fleet through Doctor and two real setup writes: %j", + async ({ legacy, include }) => { + const { ensureOnboardingAgent } = + await vi.importActual("../onboard-agent.js"); + mocks.ensureOnboardingAgent.mockImplementation(ensureOnboardingAgent); + await withTempHome(async (rawHome) => { + const home = await fs.realpath(rawHome); + const stateDir = path.join(home, ".openclaw"); + const configPath = path.join(stateDir, "openclaw.json"); + const includePath = path.join(stateDir, "roster.json"); + const workspace = path.join(home, "existing-workspace"); + const siblingWorkspace = path.join(home, "alpha-workspace"); + const agentDir = path.join(stateDir, "agents", "beta", "agent"); + const entries = { + alpha: { model: "fixture/alpha", workspace: siblingWorkspace }, + beta: { model: "fixture/beta", ...(!legacy ? { workspace } : {}), agentDir }, + }; + // The legacy default is deliberately neither the first nor lexical agent. + const list = [ + { id: "alpha", ...entries.alpha }, + { id: "beta", default: true, ...entries.beta }, + ]; + const roster = legacy ? { list } : { entries }; + const includeRaw = JSON.stringify(roster); + if (include) { + await fs.writeFile(includePath, includeRaw); + } + const bindings = [ + { type: "route", agentId: "alpha", match: { channel: "discord", accountId: "*" } }, + ]; + const raw = JSON.stringify({ + agents: { + ...(include ? { $include: "./roster.json" } : roster), + ...(!legacy ? { ownership: "explicit" } : {}), + defaults: { + workspace, + model: "fixture/shared", + ...(!legacy ? { systemAgent: { agentId: "beta" } } : {}), + }, }, - }, - bindings, - gateway: { mode: "local", port: 24680 }, - }); - await fs.writeFile(configPath, raw); - resetConfigRuntimeState(); - const before = await readConfigFileSnapshot(); - expect(before.valid, JSON.stringify(before.issues)).toBe(true); - expect(before.sourceConfigBeforeMigrations?.agents).toMatchObject(roster); - expect(before.sourceConfig.agents?.entries).toEqual(entries); - expect(before.sourceConfig.agents?.list).toBeUndefined(); - expect(await fs.readFile(configPath, "utf8")).toBe(raw); - const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as unknown as RuntimeEnv; - const profileId = "fixture:setup"; - mocks.applyAuthChoice.mockImplementation( - async ({ - nextConfig, - target, - }: { - nextConfig: OpenClawConfig; - target: OnboardingAgentTarget; - }) => { - expect(target).toEqual({ agentId: "beta", agentDir, workspaceDir: workspace }); - upsertAuthProfile({ - agentDir: target.agentDir, - profileId, - credential: { type: "api_key", provider: "fixture", key: "synthetic-setup-key" }, - }); - // Include-owned entries remain authored in their own file. Root-owned - // model edits exercise the writer's existing ownership projection. - return include - ? { ...nextConfig } - : applyOnboardingPrimaryModel(nextConfig, target, "fixture/selected"); - }, - ); - - for (let rerun = 0; rerun < 2; rerun += 1) { - await runNonInteractiveSetup( - { - nonInteractive: true, - mode: "local", - agentName: "ignored", - workspace: path.join(home, "requested-workspace"), - authChoice: "fixture-api-key", - skipBootstrap: true, - skipSkills: true, - skipHealth: true, - installDaemon: false, - }, - runtime, - ); - await expect(mocks.ensureOnboardingAgent.mock.results[rerun]?.value).resolves.toMatchObject( - { - agentId: "beta", - createdAgent: false, - }, - ); - expect(mocks.ensureWorkspaceAndSessions).toHaveBeenLastCalledWith( - workspace, - runtime, - expect.objectContaining({ agentId: "beta", skipBootstrap: true }), - ); - closeOpenClawAgentDatabasesForTest(); - closeOpenClawStateDatabaseForTest(); - resetConfigRuntimeState(); - const reopened = await readConfigFileSnapshot(); - expect(reopened.valid, JSON.stringify(reopened.issues)).toBe(true); - const expectedEntries = include - ? entries - : { - ...entries, - beta: { - ...entries.beta, - workspace, - model: { primary: "fixture/selected" }, - models: { "fixture/selected": {} }, + bindings, + gateway: { mode: "local", port: 24680 }, + models: { + providers: { + fixture: { + api: "openai-completions", + baseUrl: "https://fixture.example/v1", + models: ["alpha", "beta", "shared", "selected"].map((id) => ({ + id, + name: id, + contextWindow: 8192, + maxTokens: 1024, + })), }, - }; - expect(reopened.sourceConfig.agents?.entries).toEqual(expectedEntries); - expect(reopened.sourceConfig.agents?.defaults).toMatchObject({ - workspace, - model: "fixture/shared", - skipBootstrap: true, - systemAgent: { agentId: "beta" }, + }, + }, }); - expect(reopened.sourceConfig.bindings).toEqual(bindings); - expect(reopened.sourceConfig.gateway?.port).toBe(24680); - expect(reopened.sourceConfig.hooks?.internal?.entries?.["session-memory"]?.enabled).toBe( - true, - ); + await fs.writeFile(configPath, raw); + resetConfigRuntimeState(); + const before = await readConfigFileSnapshot(); + expect(before.valid, JSON.stringify(before.issues)).toBe(!legacy); + expect(before.sourceConfig.agents).toMatchObject(roster); + expect(await fs.readFile(configPath, "utf8")).toBe(raw); if (include) { expect(await fs.readFile(includePath, "utf8")).toBe(includeRaw); - expect(JSON.parse(await fs.readFile(configPath, "utf8")).agents.$include).toBe( - "./roster.json", + } + const canonicalEntries = { + ...entries, + beta: { ...entries.beta, workspace }, + }; + let setupIncludeRaw = includeRaw; + if (legacy) { + if (include) { + // Doctor needs the original list and owner marker when repair spans + // an included roster and root-owned roles. Re-split only after repair. + await fs.writeFile(configPath, JSON.stringify(before.sourceConfig)); + resetConfigRuntimeState(); + } + await runDoctorConfigPreflight({ + observe: false, + invocationPurpose: "doctor", + repairPrefixedConfig: true, + doctorOnlyStateMigrations: true, + preparePluginMetadataSnapshot: true, + }); + resetConfigRuntimeState(); + const repaired = await readConfigFileSnapshot(); + expect(repaired.valid, JSON.stringify(repaired.issues)).toBe(true); + expect(repaired.sourceConfig.agents?.entries).toEqual(canonicalEntries); + expect(repaired.sourceConfig.agents?.list).toBeUndefined(); + expect(repaired.sourceConfig.agents?.ownership).toBe("explicit"); + expect(repaired.sourceConfig.agents?.defaults?.systemAgent).toEqual({ agentId: "beta" }); + expect(repaired.sourceConfig.bindings).toEqual(bindings); + if (include) { + expect(await fs.readFile(includePath, "utf8")).toBe(includeRaw); + setupIncludeRaw = JSON.stringify({ entries: canonicalEntries }); + await fs.writeFile(includePath, setupIncludeRaw); + const agents = repaired.sourceConfig.agents; + if (!agents) { + throw new Error("Doctor did not retain the fleet"); + } + const { entries: _entries, ...rootOwnedAgents } = agents; + await fs.writeFile( + configPath, + JSON.stringify({ + ...repaired.sourceConfig, + agents: { $include: "./roster.json", ...rootOwnedAgents }, + }), + ); + resetConfigRuntimeState(); + expect((await readConfigFileSnapshot()).valid).toBe(true); + } + } + const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as unknown as RuntimeEnv; + const profileId = "fixture:setup"; + mocks.applyAuthChoice.mockImplementation( + async ({ + nextConfig, + target, + }: { + nextConfig: OpenClawConfig; + target: OnboardingAgentTarget; + }) => { + expect(target).toEqual({ agentId: "beta", agentDir, workspaceDir: workspace }); + upsertAuthProfile({ + agentDir: target.agentDir, + profileId, + credential: { type: "api_key", provider: "fixture", key: "synthetic-setup-key" }, + }); + // Include-owned entries remain authored in their own file. Root-owned + // model edits exercise the writer's existing ownership projection. + return include + ? { ...nextConfig } + : applyOnboardingPrimaryModel(nextConfig, target, "fixture/selected"); + }, + ); + + for (let rerun = 0; rerun < 2; rerun += 1) { + await runNonInteractiveSetup( + { + nonInteractive: true, + mode: "local", + agentName: "ignored", + workspace: path.join(home, "requested-workspace"), + authChoice: "fixture-api-key", + skipBootstrap: true, + skipSkills: true, + skipHealth: true, + installDaemon: false, + }, + runtime, ); - } else { - const persisted = JSON.parse(await fs.readFile(configPath, "utf8")); - expect(persisted.agents.entries).toEqual(expectedEntries); - expect(persisted.agents.list).toBeUndefined(); - expect(persisted.agents.ownership).toBe("explicit"); - expect(persisted.agents.defaults.systemAgent).toEqual({ agentId: "beta" }); - } - expect( - loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId], - ).toMatchObject({ provider: "fixture" }); - for (const nonOwner of ["alpha", "ignored"]) { + await expect( + mocks.ensureOnboardingAgent.mock.results[rerun]?.value, + ).resolves.toMatchObject({ + agentId: "beta", + createdAgent: false, + }); + expect(mocks.ensureWorkspaceAndSessions).toHaveBeenLastCalledWith( + workspace, + runtime, + expect.objectContaining({ agentId: "beta", skipBootstrap: true }), + ); + closeOpenClawAgentDatabasesForTest(); + closeOpenClawStateDatabaseForTest(); + resetConfigRuntimeState(); + const reopened = await readConfigFileSnapshot(); + expect(reopened.valid, JSON.stringify(reopened.issues)).toBe(true); + const expectedEntries = include + ? canonicalEntries + : { + ...canonicalEntries, + beta: { + ...entries.beta, + workspace, + model: { primary: "fixture/selected" }, + models: { "fixture/selected": {} }, + }, + }; + expect(reopened.sourceConfig.agents?.entries).toEqual(expectedEntries); + expect(reopened.sourceConfig.agents?.defaults).toMatchObject({ + workspace, + model: "fixture/shared", + skipBootstrap: true, + systemAgent: { agentId: "beta" }, + }); + expect(reopened.sourceConfig.bindings).toEqual(bindings); + expect(reopened.sourceConfig.gateway?.port).toBe(24680); + expect(reopened.sourceConfig.hooks?.internal?.entries?.["session-memory"]?.enabled).toBe( + true, + ); + if (include) { + expect(await fs.readFile(includePath, "utf8")).toBe(setupIncludeRaw); + expect(JSON.parse(await fs.readFile(configPath, "utf8")).agents.$include).toBe( + "./roster.json", + ); + } else { + const persisted = JSON.parse(await fs.readFile(configPath, "utf8")); + expect(persisted.agents.entries).toEqual(expectedEntries); + expect(persisted.agents.list).toBeUndefined(); + expect(persisted.agents.ownership).toBe("explicit"); + expect(persisted.agents.defaults.systemAgent).toEqual({ agentId: "beta" }); + } expect( - loadAuthProfileStoreWithoutExternalProfiles( - path.join(stateDir, "agents", nonOwner, "agent"), - ).profiles[profileId], - `credentials must not be stored for ${nonOwner}`, - ).toBeUndefined(); + loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles[profileId], + ).toMatchObject({ provider: "fixture" }); + for (const nonOwner of ["alpha", "ignored"]) { + expect( + loadAuthProfileStoreWithoutExternalProfiles( + path.join(stateDir, "agents", nonOwner, "agent"), + ).profiles[profileId], + `credentials must not be stored for ${nonOwner}`, + ).toBeUndefined(); + } } - } - expect(runtime.exit).not.toHaveBeenCalled(); - expect(runtime.error).toHaveBeenCalledWith( - expect.stringContaining("existing agents keep their current workspace"), - ); - }); - }); + expect(runtime.exit).not.toHaveBeenCalled(); + expect(runtime.error).toHaveBeenCalledWith( + expect.stringContaining("existing agents keep their current workspace"), + ); + }); + }, + ); }); diff --git a/src/plugin-sdk/test-env.ts b/src/plugin-sdk/test-env.ts index 1ecaf7fccfbb..7950fb0f06f2 100644 --- a/src/plugin-sdk/test-env.ts +++ b/src/plugin-sdk/test-env.ts @@ -14,6 +14,7 @@ export { export { createWindowsCmdShimFixture } from "../test-helpers/windows-cmd-shim.js"; export { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js"; export { useIsolatedStateGuard } from "../test-utils/state-path-guard.js"; +export { createCanonicalAgentConfigFixture } from "../test-utils/config-roster.js"; export { withStateDirEnv } from "../test-helpers/state-dir-env.js"; export { captureEnv, withEnv, withEnvAsync } from "../test-utils/env.js"; export { withFetchPreconnect, type FetchMock } from "../test-utils/fetch-mock.js"; diff --git a/test/cli-json-stdout.agents.e2e.test.ts b/test/cli-json-stdout.agents.e2e.test.ts index 7b0dfa051d9b..109675e411d9 100644 --- a/test/cli-json-stdout.agents.e2e.test.ts +++ b/test/cli-json-stdout.agents.e2e.test.ts @@ -1,7 +1,7 @@ import "../src/test-utils/prepare-compiled-subprocesses.js"; import fs from "node:fs/promises"; import path from "node:path"; -import { withTempHome } from "openclaw/plugin-sdk/test-env"; +import { createCanonicalAgentConfigFixture, withTempHome } from "openclaw/plugin-sdk/test-env"; import { describe, expect, it } from "vitest"; import { runBuiltCli } from "./cli-json-stdout.test-support.js"; @@ -184,18 +184,20 @@ describe("cli json stdout contract", () => { await withTempHome( async (tempHome) => { const configPath = path.join(tempHome, "openclaw.json"); - const existingConfig = `${JSON.stringify({ - agents: { - ownership: "explicit", - list: [ - { id: "main", workspace: path.join(tempHome, "main") }, - { id: "ops", workspace: path.join(tempHome, "ops") }, + const existingConfig = `${JSON.stringify( + createCanonicalAgentConfigFixture({ + agents: { + ownership: "explicit", + list: [ + { id: "main", workspace: path.join(tempHome, "main") }, + { id: "ops", workspace: path.join(tempHome, "ops") }, + ], + }, + bindings: [ + { type: "route", agentId: "ops", match: { channel: "telegram", accountId: "work" } }, ], - }, - bindings: [ - { type: "route", agentId: "ops", match: { channel: "telegram", accountId: "work" } }, - ], - })}\n`; + }).config, + )}\n`; if ("conflict" in testCase) { await fs.writeFile(configPath, existingConfig, "utf8"); } diff --git a/test/e2e/qa-lab/plugins/canvas-agent-node.e2e.test.ts b/test/e2e/qa-lab/plugins/canvas-agent-node.e2e.test.ts index 856f68857f1c..f34e72b17afa 100644 --- a/test/e2e/qa-lab/plugins/canvas-agent-node.e2e.test.ts +++ b/test/e2e/qa-lab/plugins/canvas-agent-node.e2e.test.ts @@ -24,6 +24,7 @@ import { restoreActivePluginRegistrySnapshot, setActivePluginRegistry, } from "../../../../src/plugins/runtime.js"; +import { createCanonicalAgentConfigFixture } from "../../../../src/test-utils/config-roster.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES, @@ -113,7 +114,7 @@ describe("Canvas agent tool over a paired macOS node", () => { // Released here until the started Gateway owns the claim. let unstartedPortClaim: typeof portClaim | undefined = portClaim; const gatewayToken = "qa-canvas-agent-node-token"; - const config: OpenClawConfig = { + const config: OpenClawConfig = createCanonicalAgentConfigFixture({ gateway: { mode: "local", port, @@ -129,7 +130,7 @@ describe("Canvas agent tool over a paired macOS node", () => { allow: ["canvas"], entries: { canvas: { enabled: true } }, }, - }; + }).config; await state.writeConfig(config); const invocations: CapturedInvocation[] = []; diff --git a/test/e2e/qa-lab/plugins/workspace-node-files.e2e.test.ts b/test/e2e/qa-lab/plugins/workspace-node-files.e2e.test.ts index 5e96908dc0b9..4a3ec683fa4c 100644 --- a/test/e2e/qa-lab/plugins/workspace-node-files.e2e.test.ts +++ b/test/e2e/qa-lab/plugins/workspace-node-files.e2e.test.ts @@ -13,6 +13,7 @@ import { disconnectGatewayClient, } from "../../../../src/gateway/test-helpers.e2e.js"; import { loadOrCreateDeviceIdentity } from "../../../../src/infra/device-identity.js"; +import { createCanonicalAgentConfigFixture } from "../../../../src/test-utils/config-roster.js"; import { stopQaGatewayFixture } from "../../../helpers/qa-gateway-cleanup.js"; import { stopChildProcess } from "../../../helpers/stop-child-process.js"; @@ -130,7 +131,7 @@ describe("node workspace document access", () => { }); const nodeId = nodeIdentity.deviceId; const attachmentFixture = await writeAttachmentFixture(state.root); - const config: OpenClawConfig = { + const config: OpenClawConfig = createCanonicalAgentConfigFixture({ gateway: { mode: "local", bind: "loopback", @@ -176,7 +177,7 @@ describe("node workspace document access", () => { }, }, }, - }; + }).config; const gatewayOwner = createQaGatewayChild(); let owner: GatewayClient | undefined; let reader: GatewayClient | undefined;