Files
openclaw/src/gateway/plugin-activation-runtime-config.test.ts
Peter Steinberger 3616fba951 fix(gateway): make hot reload transactional (#105289)
* fix(gateway): make hot reload transactional

Replace partial reload side effects with a deferred transaction that publishes config, secrets, auth, and subsystem state together, and drains in-flight reload work before shutdown.

Co-authored-by: LZY3538 <293718838+LZY3538@users.noreply.github.com>

* fix(auth): preserve state-only credential ownership

Keep derived runtime snapshots in place for main-store state mutations so order refreshes do not look like credential replacement.

* fix(gateway): close reload transaction gaps

* fix(gateway): close merged reload gaps

* chore: move reload note to PR context

* fix(gateway): exclude restart emission root

---------

Co-authored-by: LZY3538 <293718838+LZY3538@users.noreply.github.com>
2026-07-12 18:16:15 -07:00

96 lines
3.8 KiB
TypeScript

// Covers the shared startup-plan activation config assembly used by both gateway boot
// (prepareGatewayPluginBootstrap) and the /status plugins should-run drift check.
import { describe, expect, it, vi } from "vitest";
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveGatewayStartupPluginActivationConfig } from "./plugin-activation-runtime-config.js";
import { resolveGatewayReloadPluginActivationCandidate } from "./plugin-activation-runtime-config.js";
vi.mock("../config/plugin-auto-enable.js", () => ({
applyPluginAutoEnable: vi.fn(),
}));
const applyPluginAutoEnableMock = vi.mocked(applyPluginAutoEnable);
describe("resolveGatewayStartupPluginActivationConfig", () => {
it("auto-enables the source config, then merges activation into the runtime config", () => {
const runtimeConfig = {
plugins: { entries: { keep: { enabled: true, runtimeOnly: 1 } } },
} as unknown as OpenClawConfig;
const sourceConfig = {
plugins: { entries: { keep: { enabled: true } } },
} as unknown as OpenClawConfig;
// Auto-enable runs against the source config and yields an activation config that
// enables an extra plugin; only enable/allow surfaces should carry into runtime config.
applyPluginAutoEnableMock.mockReturnValue({
config: { plugins: { entries: { added: { enabled: true } } } },
} as unknown as ReturnType<typeof applyPluginAutoEnable>);
const result = resolveGatewayStartupPluginActivationConfig({
runtimeConfig,
activationSourceConfig: sourceConfig,
env: {} as NodeJS.ProcessEnv,
});
// Activation is computed from the operator source config, not the runtime config.
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith(
expect.objectContaining({ config: sourceConfig }),
);
// Runtime-only field is preserved; the auto-enabled activation entry is merged in.
expect(result.plugins?.entries?.keep).toEqual({ enabled: true, runtimeOnly: 1 });
expect(result.plugins?.entries?.added).toEqual({ enabled: true });
});
it("passes manifestRegistry and discovery through to auto-enable when provided", () => {
applyPluginAutoEnableMock.mockReturnValue({
config: {},
} as unknown as ReturnType<typeof applyPluginAutoEnable>);
const manifestRegistry = { plugins: [] } as never;
const discovery = { candidates: [] } as never;
resolveGatewayStartupPluginActivationConfig({
runtimeConfig: {} as OpenClawConfig,
activationSourceConfig: {} as OpenClawConfig,
env: {} as NodeJS.ProcessEnv,
manifestRegistry,
discovery,
});
expect(applyPluginAutoEnableMock).toHaveBeenCalledWith(
expect.objectContaining({ manifestRegistry, discovery }),
);
});
});
describe("resolveGatewayReloadPluginActivationCandidate", () => {
it("retains implicit provider and channel activation on a logging-only reload", () => {
const sourceConfig = { logging: { level: "debug" as const } };
const autoEnabledConfig = {
...sourceConfig,
channels: { telegram: { enabled: true } },
plugins: {
allow: ["openai-codex", "telegram"],
entries: {
"openai-codex": { enabled: true },
telegram: { enabled: true },
},
},
} as OpenClawConfig;
applyPluginAutoEnableMock.mockReturnValue({
config: autoEnabledConfig,
changes: [],
autoEnabledReasons: {},
});
const result = resolveGatewayReloadPluginActivationCandidate({
runtimeConfig: sourceConfig,
sourceConfig,
env: {},
});
expect(result.compareConfig).toBe(autoEnabledConfig);
expect(result.runtimeConfig.plugins).toEqual(autoEnabledConfig.plugins);
expect(result.runtimeConfig.channels?.telegram?.enabled).toBe(true);
});
});