From 4ed58956375be8b16fb3559699f1b0cb3661dbce Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 26 Mar 2026 15:44:25 +0000 Subject: [PATCH] test: dedupe config compatibility fixtures --- src/config/config.plugin-validation.test.ts | 109 +++++++------------- src/config/io.compat.test.ts | 88 ++++++---------- src/config/runtime-schema.test.ts | 33 +++--- 3 files changed, 82 insertions(+), 148 deletions(-) diff --git a/src/config/config.plugin-validation.test.ts b/src/config/config.plugin-validation.test.ts index 30fa74cce94..874fa2ddbc0 100644 --- a/src/config/config.plugin-validation.test.ts +++ b/src/config/config.plugin-validation.test.ts @@ -74,6 +74,25 @@ async function writeManifestlessClaudeBundleFixture(params: { dir: string }) { await fs.writeFile(path.join(params.dir, "settings.json"), '{"hideThinkingBlock":true}', "utf-8"); } +function expectRemovedPluginWarnings( + result: { ok: boolean; warnings?: Array<{ path: string; message: string }> }, + removedId: string, + removedLabel: string, +) { + expect(result.ok).toBe(true); + if (result.ok) { + const message = `plugin removed: ${removedLabel} (stale config entry ignored; remove it from plugins config)`; + expect(result.warnings).toEqual( + expect.arrayContaining([ + { path: `plugins.entries.${removedId}`, message }, + { path: "plugins.allow", message }, + { path: "plugins.deny", message }, + { path: "plugins.slots.memory", message }, + ]), + ); + } +} + describe("config plugin validation", () => { let fixtureRoot = ""; let suiteHome = ""; @@ -99,6 +118,18 @@ describe("config plugin validation", () => { const validateInSuite = (raw: unknown) => validateConfigObjectWithPlugins(raw, { env: suiteEnv() }); + const validateRemovedPluginConfig = (removedId: string) => + validateInSuite({ + agents: { list: [{ id: "pi" }] }, + plugins: { + enabled: false, + entries: { [removedId]: { enabled: true } }, + allow: [removedId], + deny: [removedId], + slots: { memory: removedId }, + }, + }); + beforeAll(async () => { fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-plugin-validation-")); await chmodSafeDir(fixtureRoot); @@ -267,84 +298,14 @@ describe("config plugin validation", () => { it("warns for removed legacy plugin ids instead of failing validation", async () => { const removedId = "google-antigravity-auth"; - const res = validateInSuite({ - agents: { list: [{ id: "pi" }] }, - plugins: { - enabled: false, - entries: { [removedId]: { enabled: true } }, - allow: [removedId], - deny: [removedId], - slots: { memory: removedId }, - }, - }); - expect(res.ok).toBe(true); - if (res.ok) { - expect(res.warnings).toEqual( - expect.arrayContaining([ - { - path: `plugins.entries.${removedId}`, - message: - "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.allow", - message: - "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.deny", - message: - "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.slots.memory", - message: - "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", - }, - ]), - ); - } + const res = validateRemovedPluginConfig(removedId); + expectRemovedPluginWarnings(res, removedId, removedId); }); it("warns for removed google gemini auth plugin ids instead of failing validation", async () => { const removedId = "google-gemini-cli-auth"; - const res = validateInSuite({ - agents: { list: [{ id: "pi" }] }, - plugins: { - enabled: false, - entries: { [removedId]: { enabled: true } }, - allow: [removedId], - deny: [removedId], - slots: { memory: removedId }, - }, - }); - expect(res.ok).toBe(true); - if (res.ok) { - expect(res.warnings).toEqual( - expect.arrayContaining([ - { - path: `plugins.entries.${removedId}`, - message: - "plugin removed: google-gemini-cli-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.allow", - message: - "plugin removed: google-gemini-cli-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.deny", - message: - "plugin removed: google-gemini-cli-auth (stale config entry ignored; remove it from plugins config)", - }, - { - path: "plugins.slots.memory", - message: - "plugin removed: google-gemini-cli-auth (stale config entry ignored; remove it from plugins config)", - }, - ]), - ); - } + const res = validateRemovedPluginConfig(removedId); + expectRemovedPluginWarnings(res, removedId, removedId); }); it("does not auto-allow config-loaded overrides of bundled web search plugin ids", async () => { diff --git a/src/config/io.compat.test.ts b/src/config/io.compat.test.ts index 7c292e79472..563ea072a4a 100644 --- a/src/config/io.compat.test.ts +++ b/src/config/io.compat.test.ts @@ -35,6 +35,36 @@ function createIoForHome(home: string, env: NodeJS.ProcessEnv = {} as NodeJS.Pro }); } +async function expectNoNewerVersionWarning(touchedVersion: string) { + await withTempHome(async (home) => { + const configDir = path.join(home, ".openclaw"); + await fs.mkdir(configDir, { recursive: true }); + const configPath = path.join(configDir, "openclaw.json"); + await fs.writeFile( + configPath, + JSON.stringify({ meta: { lastTouchedVersion: touchedVersion } }, null, 2), + ); + + const logger = { + warn: vi.fn(), + error: vi.fn(), + }; + + const io = createConfigIO({ + env: {} as NodeJS.ProcessEnv, + homedir: () => home, + logger, + }); + + io.loadConfig(); + + expect(logger.warn).not.toHaveBeenCalledWith( + expect.stringContaining("Config was last written by a newer OpenClaw"), + ); + expect(io.configPath).toBe(configPath); + }); +} + describe("config io paths", () => { it("uses ~/.openclaw/openclaw.json when config exists", async () => { await withTempHome(async (home) => { @@ -166,34 +196,7 @@ describe("config io paths", () => { throw new Error(`Unable to parse VERSION: ${VERSION}`); } const touchedVersion = `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}-${(parsedVersion.revision ?? 0) + 1}`; - - await withTempHome(async (home) => { - const configDir = path.join(home, ".openclaw"); - await fs.mkdir(configDir, { recursive: true }); - const configPath = path.join(configDir, "openclaw.json"); - await fs.writeFile( - configPath, - JSON.stringify({ meta: { lastTouchedVersion: touchedVersion } }, null, 2), - ); - - const logger = { - warn: vi.fn(), - error: vi.fn(), - }; - - const io = createConfigIO({ - env: {} as NodeJS.ProcessEnv, - homedir: () => home, - logger, - }); - - io.loadConfig(); - - expect(logger.warn).not.toHaveBeenCalledWith( - expect.stringContaining("Config was last written by a newer OpenClaw"), - ); - expect(io.configPath).toBe(configPath); - }); + await expectNoNewerVersionWarning(touchedVersion); }); it("does not warn for same-base prerelease configs when current version is newer", async () => { @@ -202,33 +205,6 @@ describe("config io paths", () => { throw new Error(`Unable to parse VERSION: ${VERSION}`); } const touchedVersion = `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}-beta.1`; - - await withTempHome(async (home) => { - const configDir = path.join(home, ".openclaw"); - await fs.mkdir(configDir, { recursive: true }); - const configPath = path.join(configDir, "openclaw.json"); - await fs.writeFile( - configPath, - JSON.stringify({ meta: { lastTouchedVersion: touchedVersion } }, null, 2), - ); - - const logger = { - warn: vi.fn(), - error: vi.fn(), - }; - - const io = createConfigIO({ - env: {} as NodeJS.ProcessEnv, - homedir: () => home, - logger, - }); - - io.loadConfig(); - - expect(logger.warn).not.toHaveBeenCalledWith( - expect.stringContaining("Config was last written by a newer OpenClaw"), - ); - expect(io.configPath).toBe(configPath); - }); + await expectNoNewerVersionWarning(touchedVersion); }); }); diff --git a/src/config/runtime-schema.test.ts b/src/config/runtime-schema.test.ts index f949d56cd5b..9be6fdb3829 100644 --- a/src/config/runtime-schema.test.ts +++ b/src/config/runtime-schema.test.ts @@ -38,6 +38,19 @@ function makeSnapshot(params: { valid: boolean; config?: OpenClawConfig }): Conf }; } +async function readSchemaNodes() { + const { readBestEffortRuntimeConfigSchema } = await import("./runtime-schema.js"); + const result = await readBestEffortRuntimeConfigSchema(); + const schema = result.schema as { properties?: Record }; + const channelsNode = schema.properties?.channels as Record | undefined; + const channelProps = channelsNode?.properties as Record | undefined; + const pluginsNode = schema.properties?.plugins as Record | undefined; + const pluginProps = pluginsNode?.properties as Record | undefined; + const entriesNode = pluginProps?.entries as Record | undefined; + const entryProps = entriesNode?.properties as Record | undefined; + return { channelProps, entryProps }; +} + describe("readBestEffortRuntimeConfigSchema", () => { beforeEach(() => { vi.clearAllMocks(); @@ -90,15 +103,7 @@ describe("readBestEffortRuntimeConfigSchema", () => { channelSetups: [], }); - const { readBestEffortRuntimeConfigSchema } = await import("./runtime-schema.js"); - const result = await readBestEffortRuntimeConfigSchema(); - const schema = result.schema as { properties?: Record }; - const channelsNode = schema.properties?.channels as Record | undefined; - const channelProps = channelsNode?.properties as Record | undefined; - const pluginsNode = schema.properties?.plugins as Record | undefined; - const pluginProps = pluginsNode?.properties as Record | undefined; - const entriesNode = pluginProps?.entries as Record | undefined; - const entryProps = entriesNode?.properties as Record | undefined; + const { channelProps, entryProps } = await readSchemaNodes(); expect(mockLoadOpenClawPlugins).toHaveBeenCalledWith( expect.objectContaining({ @@ -157,15 +162,7 @@ describe("readBestEffortRuntimeConfigSchema", () => { ], }); - const { readBestEffortRuntimeConfigSchema } = await import("./runtime-schema.js"); - const result = await readBestEffortRuntimeConfigSchema(); - const schema = result.schema as { properties?: Record }; - const channelsNode = schema.properties?.channels as Record | undefined; - const channelProps = channelsNode?.properties as Record | undefined; - const pluginsNode = schema.properties?.plugins as Record | undefined; - const pluginProps = pluginsNode?.properties as Record | undefined; - const entriesNode = pluginProps?.entries as Record | undefined; - const entryProps = entriesNode?.properties as Record | undefined; + const { channelProps, entryProps } = await readSchemaNodes(); expect(mockLoadOpenClawPlugins).toHaveBeenCalledWith( expect.objectContaining({