From 0f1f1a1fd7b9087f49e7efeb899bf18f651bfebe Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 2 Jun 2026 04:29:33 +0200 Subject: [PATCH] refactor: share startup config recovery test helpers --- .../server-startup-config.recovery.test.ts | 201 +++++++++--------- 1 file changed, 96 insertions(+), 105 deletions(-) diff --git a/src/gateway/server-startup-config.recovery.test.ts b/src/gateway/server-startup-config.recovery.test.ts index 8e5c83a7587..2828279aaa8 100644 --- a/src/gateway/server-startup-config.recovery.test.ts +++ b/src/gateway/server-startup-config.recovery.test.ts @@ -86,6 +86,8 @@ let configIo: typeof import("../config/io.js"); let configMutate: typeof import("../config/mutate.js"); const configPath = "/tmp/openclaw-startup-recovery.json"; +const telegramAutoEnableChange = "Telegram configured, enabled automatically."; +const runtimeOnlyAutoEnableLog = `gateway: auto-enabled plugins for this runtime without writing config:\n- ${telegramAutoEnableChange}`; const validConfig = { gateway: { mode: "local", @@ -163,6 +165,58 @@ function mockStartupSnapshot(snapshot: ConfigFileSnapshot) { }); } +async function expectStartupResult(params: { + snapshot: ConfigFileSnapshot; + log?: ReturnType; + initialSnapshotRead?: Parameters< + typeof loadGatewayStartupConfigSnapshot + >[0]["initialSnapshotRead"]; +}) { + await expect( + loadTestStartup({ + minimalTestGateway: false, + log: params.log, + initialSnapshotRead: params.initialSnapshotRead, + }), + ).resolves.toEqual({ + snapshot: params.snapshot, + wroteConfig: false, + pluginMetadataSnapshot, + }); +} + +function expectPluginAutoEnableFor(config: OpenClawConfig) { + expect(applyPluginAutoEnable).toHaveBeenCalledWith({ + config, + env: process.env, + manifestRegistry: pluginManifestRegistry, + }); +} + +function mockRuntimeAutoEnable(config: OpenClawConfig) { + applyPluginAutoEnable.mockReturnValueOnce({ + config, + changes: [telegramAutoEnableChange], + autoEnabledReasons: {}, + }); +} + +function expectRuntimeOnlyAutoEnableLogged(log: ReturnType) { + expect(log.info).toHaveBeenCalledWith(runtimeOnlyAutoEnableLog); + expect(log.warn).not.toHaveBeenCalled(); +} + +function withRuntimeConfig( + snapshot: ConfigFileSnapshot, + runtimeConfig: OpenClawConfig, +): ConfigFileSnapshot { + return { + ...snapshot, + runtimeConfig, + config: runtimeConfig, + }; +} + function buildInvalidConfigSnapshot(params: { rawConfig: unknown; config?: OpenClawConfig; @@ -183,6 +237,24 @@ function buildInvalidConfigSnapshot(params: { }); } +function pluginSlotRawConfig(gatewayMode: string) { + return { + gateway: { mode: gatewayMode }, + plugins: { slots: { memory: "source-only-pack" } }, + }; +} + +function enabledPluginRawConfig(gatewayMode: string) { + return { + gateway: { mode: gatewayMode }, + plugins: { + entries: { + feishu: { enabled: true }, + }, + }, + }; +} + function testStartupLog() { return { info: vi.fn(), warn: vi.fn() }; } @@ -278,23 +350,10 @@ describe("gateway startup config validation", () => { mockStartupSnapshot(snapshot); const log = testStartupLog(); - await expect( - loadTestStartup({ - minimalTestGateway: false, - log, - }), - ).resolves.toEqual({ - snapshot, - wroteConfig: false, - pluginMetadataSnapshot, - }); + await expectStartupResult({ snapshot, log }); expect(configIo.readConfigFileSnapshotWithPluginMetadata).toHaveBeenCalledTimes(1); - expect(applyPluginAutoEnable).toHaveBeenCalledWith({ - config: sourceConfig, - env: process.env, - manifestRegistry: pluginManifestRegistry, - }); + expectPluginAutoEnableFor(sourceConfig); expect(configMutate.replaceConfigFile).not.toHaveBeenCalled(); expect(log.info).not.toHaveBeenCalled(); }); @@ -312,27 +371,17 @@ describe("gateway startup config validation", () => { }); const log = testStartupLog(); - await expect( - loadTestStartup({ - minimalTestGateway: false, - log, - initialSnapshotRead: { - snapshot, - pluginMetadataSnapshot, - }, - }), - ).resolves.toEqual({ + await expectStartupResult({ snapshot, - wroteConfig: false, - pluginMetadataSnapshot, + log, + initialSnapshotRead: { + snapshot, + pluginMetadataSnapshot, + }, }); expect(configIo.readConfigFileSnapshotWithPluginMetadata).not.toHaveBeenCalled(); - expect(applyPluginAutoEnable).toHaveBeenCalledWith({ - config: validConfig, - env: process.env, - manifestRegistry: pluginManifestRegistry, - }); + expectPluginAutoEnableFor(validConfig); }); it("preserves empty model allowlist entries through runtime-only startup auto-enable", async () => { @@ -367,33 +416,15 @@ describe("gateway startup config validation", () => { } as unknown as OpenClawConfig; const initialSnapshot = buildRuntimeSnapshot(sourceConfig); mockStartupSnapshot(initialSnapshot); - applyPluginAutoEnable.mockReturnValueOnce({ - config: autoEnabledConfig, - changes: ["Telegram configured, enabled automatically."], - autoEnabledReasons: {}, - }); + mockRuntimeAutoEnable(autoEnabledConfig); const log = testStartupLog(); - await expect( - loadTestStartup({ - minimalTestGateway: false, - log, - }), - ).resolves.toEqual({ - snapshot: { - ...initialSnapshot, - runtimeConfig: autoEnabledConfig, - config: autoEnabledConfig, - }, - wroteConfig: false, - pluginMetadataSnapshot, + await expectStartupResult({ + snapshot: withRuntimeConfig(initialSnapshot, autoEnabledConfig), + log, }); - expect(applyPluginAutoEnable).toHaveBeenCalledWith({ - config: sourceConfig, - env: process.env, - manifestRegistry: pluginManifestRegistry, - }); + expectPluginAutoEnableFor(sourceConfig); expect(configMutate.replaceConfigFile).not.toHaveBeenCalled(); expect(configIo.readConfigFileSnapshotWithPluginMetadata).toHaveBeenCalledTimes(1); expect(initialSnapshot.sourceConfig.agents?.defaults?.models).toEqual({ @@ -408,10 +439,7 @@ describe("gateway startup config validation", () => { expect(autoEnabledConfig.channels?.telegram).toEqual({ enabled: true, }); - expect(log.info).toHaveBeenCalledWith( - "gateway: auto-enabled plugins for this runtime without writing config:\n- Telegram configured, enabled automatically.", - ); - expect(log.warn).not.toHaveBeenCalled(); + expectRuntimeOnlyAutoEnableLogged(log); }); it("keeps plugin auto-enable runtime-only in Nix mode", async () => { @@ -431,35 +459,18 @@ describe("gateway startup config validation", () => { } as unknown as OpenClawConfig; const snapshot = buildRuntimeSnapshot(sourceConfig); mockStartupSnapshot(snapshot); - applyPluginAutoEnable.mockReturnValueOnce({ - config: autoEnabledConfig, - changes: ["Telegram configured, enabled automatically."], - autoEnabledReasons: {}, - }); + mockRuntimeAutoEnable(autoEnabledConfig); configMocks.isNixMode.value = true; const log = testStartupLog(); - await expect( - loadTestStartup({ - minimalTestGateway: false, - log, - }), - ).resolves.toEqual({ - snapshot: { - ...snapshot, - runtimeConfig: autoEnabledConfig, - config: autoEnabledConfig, - }, - wroteConfig: false, - pluginMetadataSnapshot, + await expectStartupResult({ + snapshot: withRuntimeConfig(snapshot, autoEnabledConfig), + log, }); expect(configMutate.replaceConfigFile).not.toHaveBeenCalled(); expect(configIo.readConfigFileSnapshotWithPluginMetadata).toHaveBeenCalledTimes(1); - expect(log.info).toHaveBeenCalledWith( - "gateway: auto-enabled plugins for this runtime without writing config:\n- Telegram configured, enabled automatically.", - ); - expect(log.warn).not.toHaveBeenCalled(); + expectRuntimeOnlyAutoEnableLogged(log); }); it("rejects invalid config before startup without automatic recovery", async () => { @@ -472,10 +483,7 @@ describe("gateway startup config validation", () => { }); it("does not suggest doctor repair for plugin packaging compiled-output failures", async () => { - const rawConfig = { - gateway: { mode: "local" }, - plugins: { slots: { memory: "source-only-pack" } }, - }; + const rawConfig = pluginSlotRawConfig("local"); const invalidSnapshot = buildInvalidConfigSnapshot({ rawConfig, config: rawConfig as OpenClawConfig, @@ -505,10 +513,7 @@ describe("gateway startup config validation", () => { }); it("keeps doctor repair guidance for mixed plugin packaging and core invalidity", async () => { - const rawConfig = { - gateway: { mode: "invalid" }, - plugins: { slots: { memory: "source-only-pack" } }, - }; + const rawConfig = pluginSlotRawConfig("invalid"); const invalidSnapshot = buildInvalidConfigSnapshot({ rawConfig, config: rawConfig as unknown as OpenClawConfig, @@ -565,14 +570,7 @@ describe("gateway startup config validation", () => { }); it("rejects plugin-local startup invalidity without degraded startup", async () => { - const rawConfig = { - gateway: { mode: "local" }, - plugins: { - entries: { - feishu: { enabled: true }, - }, - }, - }; + const rawConfig = enabledPluginRawConfig("local"); const invalidSnapshot = buildInvalidConfigSnapshot({ rawConfig, config: rawConfig as OpenClawConfig, @@ -589,14 +587,7 @@ describe("gateway startup config validation", () => { }); it("keeps mixed plugin and core startup invalidity fatal", async () => { - const rawConfig = { - gateway: { mode: "invalid" }, - plugins: { - entries: { - feishu: { enabled: true }, - }, - }, - }; + const rawConfig = enabledPluginRawConfig("invalid"); const invalidSnapshot = buildInvalidConfigSnapshot({ rawConfig, config: rawConfig as unknown as OpenClawConfig,