mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 06:32:00 +00:00
test(gateway): stabilize suite session-store config (#52193)
* test(gateway): stabilize suite session-store config * test(gateway): preserve seeded config semantics * test(gateway): update seeded session store overrides
This commit is contained in:
@@ -6,8 +6,8 @@ const GATEWAY_CLIENT_CONSTRUCTOR_PATTERN = /new\s+GatewayClient\s*\(/;
|
||||
|
||||
const ALLOWED_GATEWAY_CLIENT_CALLSITES = new Set([
|
||||
"src/acp/server.ts",
|
||||
"extensions/discord/src/monitor/exec-approvals.ts",
|
||||
"src/gateway/call.ts",
|
||||
"src/gateway/operator-approvals-client.ts",
|
||||
"src/gateway/probe.ts",
|
||||
"src/node-host/runner.ts",
|
||||
"src/tui/gateway-chat.ts",
|
||||
|
||||
@@ -109,6 +109,7 @@ const METHOD_SCOPE_GROUPS: Record<OperatorScope, readonly string[]> = {
|
||||
"chat.abort",
|
||||
"sessions.create",
|
||||
"sessions.send",
|
||||
"sessions.steer",
|
||||
"sessions.abort",
|
||||
"browser.request",
|
||||
"push.test",
|
||||
|
||||
@@ -185,6 +185,26 @@ const hoisted = vi.hoisted(() => ({
|
||||
testTailscaleWhois: { value: null as TailscaleWhoisIdentity | null },
|
||||
getReplyFromConfig: vi.fn<GetReplyFromConfigFn>().mockResolvedValue(undefined),
|
||||
sendWhatsAppMock: vi.fn().mockResolvedValue({ messageId: "msg-1", toJid: "jid-1" }),
|
||||
testState: {
|
||||
agentConfig: undefined as Record<string, unknown> | undefined,
|
||||
agentsConfig: undefined as Record<string, unknown> | undefined,
|
||||
bindingsConfig: undefined as AgentBinding[] | undefined,
|
||||
channelsConfig: undefined as Record<string, unknown> | undefined,
|
||||
sessionStorePath: undefined as string | undefined,
|
||||
sessionConfig: undefined as Record<string, unknown> | undefined,
|
||||
allowFrom: undefined as string[] | undefined,
|
||||
cronStorePath: undefined as string | undefined,
|
||||
cronEnabled: false as boolean | undefined,
|
||||
gatewayBind: undefined as "auto" | "lan" | "tailnet" | "loopback" | undefined,
|
||||
gatewayAuth: undefined as Record<string, unknown> | undefined,
|
||||
gatewayControlUi: undefined as Record<string, unknown> | undefined,
|
||||
hooksConfig: undefined as HooksConfig | undefined,
|
||||
canvasHostPort: undefined as number | undefined,
|
||||
legacyIssues: [] as Array<{ path: string; message: string }>,
|
||||
legacyParsed: {} as Record<string, unknown>,
|
||||
migrationConfig: null as Record<string, unknown> | null,
|
||||
migrationChanges: [] as string[],
|
||||
},
|
||||
}));
|
||||
|
||||
const pluginRegistryState = {
|
||||
@@ -218,26 +238,7 @@ export const cronIsolatedRun = hoisted.cronIsolatedRun;
|
||||
export const agentCommand: Mock<() => void> = hoisted.agentCommand;
|
||||
export const getReplyFromConfig: Mock<GetReplyFromConfigFn> = hoisted.getReplyFromConfig;
|
||||
|
||||
export const testState = {
|
||||
agentConfig: undefined as Record<string, unknown> | undefined,
|
||||
agentsConfig: undefined as Record<string, unknown> | undefined,
|
||||
bindingsConfig: undefined as AgentBinding[] | undefined,
|
||||
channelsConfig: undefined as Record<string, unknown> | undefined,
|
||||
sessionStorePath: undefined as string | undefined,
|
||||
sessionConfig: undefined as Record<string, unknown> | undefined,
|
||||
allowFrom: undefined as string[] | undefined,
|
||||
cronStorePath: undefined as string | undefined,
|
||||
cronEnabled: false as boolean | undefined,
|
||||
gatewayBind: undefined as "auto" | "lan" | "tailnet" | "loopback" | undefined,
|
||||
gatewayAuth: undefined as Record<string, unknown> | undefined,
|
||||
gatewayControlUi: undefined as Record<string, unknown> | undefined,
|
||||
hooksConfig: undefined as HooksConfig | undefined,
|
||||
canvasHostPort: undefined as number | undefined,
|
||||
legacyIssues: [] as Array<{ path: string; message: string }>,
|
||||
legacyParsed: {} as Record<string, unknown>,
|
||||
migrationConfig: null as Record<string, unknown> | null,
|
||||
migrationChanges: [] as string[],
|
||||
};
|
||||
export const testState = hoisted.testState;
|
||||
|
||||
export const testIsNixMode = hoisted.testIsNixMode;
|
||||
export const sessionStoreSaveDelayMs = hoisted.sessionStoreSaveDelayMs;
|
||||
@@ -530,8 +531,11 @@ vi.mock("../config/config.js", async () => {
|
||||
} catch {
|
||||
fileConfig = {};
|
||||
}
|
||||
return applyPluginAutoEnable({ config: composeTestConfig(fileConfig), env: process.env })
|
||||
.config;
|
||||
const config = applyPluginAutoEnable({
|
||||
config: composeTestConfig(fileConfig),
|
||||
env: process.env,
|
||||
}).config;
|
||||
return config;
|
||||
},
|
||||
parseConfigJson5: (raw: string) => {
|
||||
try {
|
||||
|
||||
@@ -4,7 +4,16 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, expect, vi } from "vitest";
|
||||
import { WebSocket } from "ws";
|
||||
import { resolveMainSessionKeyFromConfig, type SessionEntry } from "../config/sessions.js";
|
||||
import {
|
||||
clearConfigCache,
|
||||
clearRuntimeConfigSnapshot,
|
||||
parseConfigJson5,
|
||||
} from "../config/config.js";
|
||||
import {
|
||||
clearSessionStoreCacheForTest,
|
||||
resolveMainSessionKeyFromConfig,
|
||||
type SessionEntry,
|
||||
} from "../config/sessions.js";
|
||||
import { resetAgentRunContextForTest } from "../infra/agent-events.js";
|
||||
import {
|
||||
loadOrCreateDeviceIdentity,
|
||||
@@ -63,6 +72,58 @@ let tempHome: string | undefined;
|
||||
let tempConfigRoot: string | undefined;
|
||||
let suiteConfigRootSeq = 0;
|
||||
|
||||
async function persistTestSessionStorePath(storePath: string): Promise<void> {
|
||||
const configPaths = new Set<string>();
|
||||
if (process.env.OPENCLAW_CONFIG_PATH) {
|
||||
configPaths.add(process.env.OPENCLAW_CONFIG_PATH);
|
||||
}
|
||||
if (process.env.OPENCLAW_STATE_DIR) {
|
||||
configPaths.add(path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json"));
|
||||
}
|
||||
const parsedConfigs = new Map<string, Record<string, unknown>>();
|
||||
let preservedTemplateStore: string | undefined;
|
||||
for (const configPath of configPaths) {
|
||||
let config: Record<string, unknown> = {};
|
||||
try {
|
||||
const raw = await fs.readFile(configPath, "utf-8");
|
||||
const parsed = parseConfigJson5(raw);
|
||||
if (
|
||||
parsed.ok &&
|
||||
parsed.parsed &&
|
||||
typeof parsed.parsed === "object" &&
|
||||
!Array.isArray(parsed.parsed)
|
||||
) {
|
||||
config = parsed.parsed as Record<string, unknown>;
|
||||
}
|
||||
} catch {
|
||||
config = {};
|
||||
}
|
||||
parsedConfigs.set(configPath, config);
|
||||
const session =
|
||||
config.session && typeof config.session === "object" && !Array.isArray(config.session)
|
||||
? (config.session as Record<string, unknown>)
|
||||
: undefined;
|
||||
const existingStore = typeof session?.store === "string" ? session.store.trim() : "";
|
||||
if (!preservedTemplateStore && existingStore.includes("{agentId}")) {
|
||||
preservedTemplateStore = existingStore;
|
||||
}
|
||||
}
|
||||
const nextStoreValue = preservedTemplateStore || storePath;
|
||||
for (const configPath of configPaths) {
|
||||
const config = { ...parsedConfigs.get(configPath) };
|
||||
const session =
|
||||
config.session && typeof config.session === "object" && !Array.isArray(config.session)
|
||||
? { ...(config.session as Record<string, unknown>) }
|
||||
: {};
|
||||
session.store = nextStoreValue;
|
||||
config.session = session;
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
||||
}
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
}
|
||||
|
||||
export async function writeSessionStore(params: {
|
||||
entries: Record<string, Partial<SessionEntry>>;
|
||||
storePath?: string;
|
||||
@@ -87,8 +148,13 @@ export async function writeSessionStore(params: {
|
||||
});
|
||||
store[storeKey] = entry;
|
||||
}
|
||||
// Gateway suites often reuse the same store path across tests while writing the
|
||||
// file directly; clear the in-process cache so handlers reload the seeded state.
|
||||
clearSessionStoreCacheForTest();
|
||||
await persistTestSessionStorePath(storePath);
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(storePath, JSON.stringify(store, null, 2), "utf-8");
|
||||
clearSessionStoreCacheForTest();
|
||||
}
|
||||
|
||||
async function setupGatewayTestHome() {
|
||||
@@ -133,6 +199,8 @@ async function resetGatewayTestState(options: { uniqueConfigRoot: boolean }) {
|
||||
await fs.mkdir(tempConfigRoot, { recursive: true });
|
||||
}
|
||||
setTestConfigRoot(tempConfigRoot);
|
||||
clearRuntimeConfigSnapshot();
|
||||
clearConfigCache();
|
||||
sessionStoreSaveDelayMs.value = 0;
|
||||
testTailnetIPv4.value = undefined;
|
||||
testTailscaleWhois.value = null;
|
||||
@@ -197,10 +265,10 @@ export function installGatewayTestHooks(options?: { scope?: "test" | "suite" })
|
||||
if (scope === "suite") {
|
||||
beforeAll(async () => {
|
||||
await setupGatewayTestHome();
|
||||
await resetGatewayTestState({ uniqueConfigRoot: true });
|
||||
await resetGatewayTestState({ uniqueConfigRoot: false });
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await resetGatewayTestState({ uniqueConfigRoot: true });
|
||||
await resetGatewayTestState({ uniqueConfigRoot: false });
|
||||
}, 60_000);
|
||||
afterEach(async () => {
|
||||
await cleanupGatewayTestHome({ restoreEnv: false });
|
||||
|
||||
Reference in New Issue
Block a user