refactor: share startup config recovery test helpers

This commit is contained in:
Vincent Koc
2026-06-02 04:29:33 +02:00
parent d944aaa9ec
commit 0f1f1a1fd7

View File

@@ -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<typeof testStartupLog>;
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<typeof testStartupLog>) {
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,