Files
openclaw/src/config/runtime-snapshot.test.ts
2026-04-07 09:35:33 +01:00

253 lines
7.6 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
finalizeRuntimeSnapshotWrite,
getRuntimeConfigSourceSnapshot,
getRuntimeConfigSnapshot,
loadPinnedRuntimeConfig,
notifyRuntimeConfigWriteListeners,
registerRuntimeConfigWriteListener,
resetConfigRuntimeState,
setRuntimeConfigSnapshot,
setRuntimeConfigSnapshotRefreshHandler,
} from "./runtime-snapshot.js";
import type { OpenClawConfig } from "./types.js";
function resetRuntimeConfigState(): void {
setRuntimeConfigSnapshotRefreshHandler(null);
resetConfigRuntimeState();
}
describe("runtime snapshot state", () => {
afterEach(() => {
resetRuntimeConfigState();
});
it("pins the first successful load in memory until the snapshot is cleared", () => {
let freshPort = 18789;
let loadCount = 0;
const loadFresh = (): OpenClawConfig => {
loadCount += 1;
return { gateway: { port: freshPort } };
};
expect(loadPinnedRuntimeConfig(loadFresh).gateway?.port).toBe(18789);
expect(loadCount).toBe(1);
freshPort = 19001;
expect(loadPinnedRuntimeConfig(loadFresh).gateway?.port).toBe(18789);
expect(loadCount).toBe(1);
resetRuntimeConfigState();
expect(loadPinnedRuntimeConfig(loadFresh).gateway?.port).toBe(19001);
expect(loadCount).toBe(2);
});
it("returns the source snapshot when runtime snapshot is active", () => {
const sourceConfig: OpenClawConfig = {
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
};
const runtimeConfig: OpenClawConfig = {
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: "sk-runtime-resolved",
models: [],
},
},
},
};
setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);
expect(getRuntimeConfigSourceSnapshot()).toEqual(sourceConfig);
});
it("clears runtime source snapshot when runtime snapshot is cleared", () => {
setRuntimeConfigSnapshot({ gateway: { port: 18789 } }, { gateway: { port: 18789 } });
resetRuntimeConfigState();
expect(getRuntimeConfigSnapshot()).toBeNull();
expect(getRuntimeConfigSourceSnapshot()).toBeNull();
});
it("refreshes both snapshots from disk after a write when source + runtime snapshots exist", async () => {
const notifyCommittedWrite = vi.fn();
const loadFreshConfig = vi.fn<() => OpenClawConfig>(() => ({
gateway: { auth: { mode: "token" } },
}));
const nextSourceConfig: OpenClawConfig = {
gateway: { auth: { mode: "token" } },
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
};
setRuntimeConfigSnapshot(
{
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: "sk-runtime-resolved",
models: [],
},
},
},
},
nextSourceConfig,
);
await finalizeRuntimeSnapshotWrite({
nextSourceConfig,
hadRuntimeSnapshot: true,
hadBothSnapshots: true,
loadFreshConfig,
notifyCommittedWrite,
formatRefreshError: (error) => String(error),
createRefreshError: (detail, cause) => new Error(detail, { cause }),
});
expect(loadFreshConfig).toHaveBeenCalledTimes(1);
expect(getRuntimeConfigSnapshot()).toEqual({ gateway: { auth: { mode: "token" } } });
expect(getRuntimeConfigSourceSnapshot()).toEqual(nextSourceConfig);
expect(notifyCommittedWrite).toHaveBeenCalledTimes(1);
});
it("refreshes a plain runtime snapshot after writes without restoring a source snapshot", async () => {
const notifyCommittedWrite = vi.fn();
const loadFreshConfig = vi.fn(() => ({ gateway: { port: 19002 } }));
setRuntimeConfigSnapshot({ gateway: { port: 18789 } });
await finalizeRuntimeSnapshotWrite({
nextSourceConfig: { gateway: { port: 19002 } },
hadRuntimeSnapshot: true,
hadBothSnapshots: false,
loadFreshConfig,
notifyCommittedWrite,
formatRefreshError: (error) => String(error),
createRefreshError: (detail, cause) => new Error(detail, { cause }),
});
expect(loadFreshConfig).toHaveBeenCalledTimes(1);
expect(getRuntimeConfigSnapshot()).toEqual({ gateway: { port: 19002 } });
expect(getRuntimeConfigSourceSnapshot()).toBeNull();
expect(notifyCommittedWrite).toHaveBeenCalledTimes(1);
});
it("keeps the last-known-good runtime snapshot active while specialized refresh is pending", async () => {
const notifyCommittedWrite = vi.fn();
const loadFreshConfig = vi.fn<() => OpenClawConfig>(() => ({
gateway: { auth: { mode: "token" } },
}));
let releaseRefresh!: () => void;
const refreshPending = new Promise<boolean>((resolve) => {
releaseRefresh = () => resolve(true);
});
setRuntimeConfigSnapshot(
{
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: "sk-runtime-resolved",
models: [],
},
},
},
},
{
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
},
);
setRuntimeConfigSnapshotRefreshHandler({
refresh: async ({ sourceConfig }) => {
expect(sourceConfig.gateway?.auth).toEqual({ mode: "token" });
expect(getRuntimeConfigSnapshot()?.gateway?.auth).toBeUndefined();
return await refreshPending;
},
});
const writePromise = finalizeRuntimeSnapshotWrite({
nextSourceConfig: {
gateway: { auth: { mode: "token" } },
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
},
hadRuntimeSnapshot: true,
hadBothSnapshots: true,
loadFreshConfig,
notifyCommittedWrite,
formatRefreshError: (error) => String(error),
createRefreshError: (detail, cause) => new Error(detail, { cause }),
});
await Promise.resolve();
expect(getRuntimeConfigSnapshot()?.gateway?.auth).toBeUndefined();
expect(loadFreshConfig).not.toHaveBeenCalled();
releaseRefresh();
await writePromise;
expect(notifyCommittedWrite).toHaveBeenCalledTimes(1);
});
it("notifies registered write listeners with committed runtime snapshots", () => {
const seen: Array<{ configPath: string; runtimeConfig: OpenClawConfig }> = [];
const unsubscribe = registerRuntimeConfigWriteListener((event) => {
seen.push({
configPath: event.configPath,
runtimeConfig: event.runtimeConfig,
});
});
try {
notifyRuntimeConfigWriteListeners({
configPath: "/tmp/openclaw.json",
sourceConfig: { gateway: { port: 18789 } },
runtimeConfig: { gateway: { port: 19003 } },
persistedHash: "abc123",
writtenAtMs: 1,
});
} finally {
unsubscribe();
}
expect(seen).toEqual([
{
configPath: "/tmp/openclaw.json",
runtimeConfig: { gateway: { port: 19003 } },
},
]);
});
});