mirror of https://github.com/openclaw/openclaw.git
209 lines
6.3 KiB
TypeScript
209 lines
6.3 KiB
TypeScript
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resolveBrowserConfig, resolveProfile } from "./config.js";
|
|
import {
|
|
refreshResolvedBrowserConfigFromDisk,
|
|
resolveBrowserProfileWithHotReload,
|
|
} from "./resolved-config-refresh.js";
|
|
import type { BrowserServerState } from "./server-context.types.js";
|
|
|
|
let cfgProfiles: Record<string, { cdpPort?: number; cdpUrl?: string; color?: string }> = {};
|
|
|
|
// Simulate module-level cache behavior
|
|
let cachedConfig: ReturnType<typeof buildConfig> | null = null;
|
|
|
|
function buildConfig() {
|
|
return {
|
|
browser: {
|
|
enabled: true,
|
|
color: "#FF4500",
|
|
headless: true,
|
|
defaultProfile: "openclaw",
|
|
profiles: { ...cfgProfiles },
|
|
},
|
|
};
|
|
}
|
|
|
|
vi.mock("../config/config.js", () => ({
|
|
createConfigIO: () => ({
|
|
loadConfig: () => {
|
|
// Always return fresh config for createConfigIO to simulate fresh disk read
|
|
return buildConfig();
|
|
},
|
|
}),
|
|
loadConfig: () => {
|
|
// simulate stale loadConfig that doesn't see updates unless cache cleared
|
|
if (!cachedConfig) {
|
|
cachedConfig = buildConfig();
|
|
}
|
|
return cachedConfig;
|
|
},
|
|
writeConfigFile: vi.fn(async () => {}),
|
|
}));
|
|
|
|
describe("server-context hot-reload profiles", () => {
|
|
let loadConfig: typeof import("../config/config.js").loadConfig;
|
|
|
|
beforeAll(async () => {
|
|
({ loadConfig } = await import("../config/config.js"));
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
cfgProfiles = {
|
|
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
|
};
|
|
cachedConfig = null; // Clear simulated cache
|
|
});
|
|
|
|
it("forProfile hot-reloads newly added profiles from config", async () => {
|
|
// Start with only openclaw profile
|
|
// 1. Prime the cache by calling loadConfig() first
|
|
const cfg = loadConfig();
|
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
|
|
// Verify cache is primed (without desktop)
|
|
expect(cfg.browser?.profiles?.desktop).toBeUndefined();
|
|
const state = {
|
|
server: null,
|
|
port: 18791,
|
|
resolved,
|
|
profiles: new Map(),
|
|
};
|
|
|
|
// Initially, "desktop" profile should not exist
|
|
expect(
|
|
resolveBrowserProfileWithHotReload({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
name: "desktop",
|
|
}),
|
|
).toBeNull();
|
|
|
|
// 2. Simulate adding a new profile to config (like user editing openclaw.json)
|
|
cfgProfiles.desktop = { cdpUrl: "http://127.0.0.1:9222", color: "#0066CC" };
|
|
|
|
// 3. Verify without clearConfigCache, loadConfig() still returns stale cached value
|
|
const staleCfg = loadConfig();
|
|
expect(staleCfg.browser?.profiles?.desktop).toBeUndefined(); // Cache is stale!
|
|
|
|
// 4. Hot-reload should read fresh config for the lookup (createConfigIO().loadConfig()),
|
|
// without flushing the global loadConfig cache.
|
|
const profile = resolveBrowserProfileWithHotReload({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
name: "desktop",
|
|
});
|
|
expect(profile?.name).toBe("desktop");
|
|
expect(profile?.cdpUrl).toBe("http://127.0.0.1:9222");
|
|
|
|
// 5. Verify the new profile was merged into the cached state
|
|
expect(state.resolved.profiles.desktop).toBeDefined();
|
|
|
|
// 6. Verify GLOBAL cache was NOT cleared - subsequent simple loadConfig() still sees STALE value
|
|
// This confirms the fix: we read fresh config for the specific profile lookup without flushing the global cache
|
|
const stillStaleCfg = loadConfig();
|
|
expect(stillStaleCfg.browser?.profiles?.desktop).toBeUndefined();
|
|
});
|
|
|
|
it("forProfile still throws for profiles that don't exist in fresh config", async () => {
|
|
const cfg = loadConfig();
|
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
const state = {
|
|
server: null,
|
|
port: 18791,
|
|
resolved,
|
|
profiles: new Map(),
|
|
};
|
|
|
|
// Profile that doesn't exist anywhere should still throw
|
|
expect(
|
|
resolveBrowserProfileWithHotReload({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
name: "nonexistent",
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("forProfile refreshes existing profile config after loadConfig cache updates", async () => {
|
|
const cfg = loadConfig();
|
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
const state = {
|
|
server: null,
|
|
port: 18791,
|
|
resolved,
|
|
profiles: new Map(),
|
|
};
|
|
|
|
cfgProfiles.openclaw = { cdpPort: 19999, color: "#FF4500" };
|
|
cachedConfig = null;
|
|
|
|
const after = resolveBrowserProfileWithHotReload({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
name: "openclaw",
|
|
});
|
|
expect(after?.cdpPort).toBe(19999);
|
|
expect(state.resolved.profiles.openclaw?.cdpPort).toBe(19999);
|
|
});
|
|
|
|
it("listProfiles refreshes config before enumerating profiles", async () => {
|
|
const cfg = loadConfig();
|
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
const state = {
|
|
server: null,
|
|
port: 18791,
|
|
resolved,
|
|
profiles: new Map(),
|
|
};
|
|
|
|
cfgProfiles.desktop = { cdpPort: 19999, color: "#0066CC" };
|
|
cachedConfig = null;
|
|
|
|
refreshResolvedBrowserConfigFromDisk({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
mode: "cached",
|
|
});
|
|
expect(Object.keys(state.resolved.profiles)).toContain("desktop");
|
|
});
|
|
|
|
it("marks existing runtime state for reconcile when profile invariants change", async () => {
|
|
const cfg = loadConfig();
|
|
const resolved = resolveBrowserConfig(cfg.browser, cfg);
|
|
const openclawProfile = resolveProfile(resolved, "openclaw");
|
|
expect(openclawProfile).toBeTruthy();
|
|
const state: BrowserServerState = {
|
|
server: null,
|
|
port: 18791,
|
|
resolved,
|
|
profiles: new Map([
|
|
[
|
|
"openclaw",
|
|
{
|
|
profile: openclawProfile!,
|
|
running: { pid: 123 } as never,
|
|
lastTargetId: "tab-1",
|
|
reconcile: null,
|
|
},
|
|
],
|
|
]),
|
|
};
|
|
|
|
cfgProfiles.openclaw = { cdpPort: 19999, color: "#FF4500" };
|
|
cachedConfig = null;
|
|
|
|
refreshResolvedBrowserConfigFromDisk({
|
|
current: state,
|
|
refreshConfigFromDisk: true,
|
|
mode: "cached",
|
|
});
|
|
|
|
const runtime = state.profiles.get("openclaw");
|
|
expect(runtime).toBeTruthy();
|
|
expect(runtime?.profile.cdpPort).toBe(19999);
|
|
expect(runtime?.lastTargetId).toBeNull();
|
|
expect(runtime?.reconcile?.reason).toContain("cdpPort");
|
|
});
|
|
});
|