diff --git a/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts b/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts index 7aaf16e93f4..e78f0b28a3c 100644 --- a/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts +++ b/extensions/feishu/src/monitor.reply-once.lifecycle.test.ts @@ -10,7 +10,14 @@ const monitorWebSocketMock = vi.hoisted(() => vi.fn(async () => {})); const monitorWebhookMock = vi.hoisted(() => vi.fn(async () => {})); const createFeishuThreadBindingManagerMock = vi.hoisted(() => vi.fn(() => ({ stop: vi.fn() }))); const createFeishuReplyDispatcherMock = vi.hoisted(() => vi.fn()); -const resolveBoundConversationMock = vi.hoisted(() => vi.fn(() => null)); +const resolveBoundConversationMock = vi.hoisted(() => + vi.fn< + () => { + bindingId: string; + targetSessionKey: string; + } | null + >(() => null), +); const touchBindingMock = vi.hoisted(() => vi.fn()); const resolveAgentRouteMock = vi.hoisted(() => vi.fn()); const dispatchReplyFromConfigMock = vi.hoisted(() => vi.fn()); @@ -110,6 +117,7 @@ function createLifecycleConfig(): ClawdbotConfig { function createLifecycleAccount(): ResolvedFeishuAccount { return { accountId: "acct-lifecycle", + selectionSource: "explicit", enabled: true, configured: true, appId: "cli_test", @@ -129,7 +137,7 @@ function createLifecycleAccount(): ResolvedFeishuAccount { }, }, }, - } as ResolvedFeishuAccount; + } as unknown as ResolvedFeishuAccount; } function createRuntimeEnv(): RuntimeEnv { diff --git a/src/memory/index.test.ts b/src/memory/index.test.ts index 95d6e8556ee..189fbc0c09d 100644 --- a/src/memory/index.test.ts +++ b/src/memory/index.test.ts @@ -3,13 +3,14 @@ import { mkdirSync, rmSync } from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import "./test-runtime-mocks.js"; import type { MemoryIndexManager } from "./index.js"; type MemoryIndexModule = typeof import("./index.js"); let getMemorySearchManager: MemoryIndexModule["getMemorySearchManager"]; +let closeAllMemorySearchManagers: MemoryIndexModule["closeAllMemorySearchManagers"]; let embedBatchCalls = 0; let embedBatchInputCalls = 0; @@ -125,14 +126,12 @@ describe("memory index", () => { }), ].join("\n"); - // Perf: keep managers open across tests, but only reset the one a test uses. - const managersByCacheKey = new Map(); const managersForCleanup = new Set(); beforeAll(async () => { vi.resetModules(); await import("./test-runtime-mocks.js"); - ({ getMemorySearchManager } = await import("./index.js")); + ({ getMemorySearchManager, closeAllMemorySearchManagers } = await import("./index.js")); fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-fixtures-")); workspaceDir = path.join(fixtureRoot, "workspace"); memoryDir = path.join(workspaceDir, "memory"); @@ -158,6 +157,11 @@ describe("memory index", () => { await fs.rm(fixtureRoot, { recursive: true, force: true }); }); + afterEach(async () => { + await closeAllMemorySearchManagers(); + managersForCleanup.clear(); + }); + beforeEach(async () => { // Perf: most suites don't need atomic swap behavior for full reindexes. // Keep atomic reindex tests on the safe path. @@ -166,7 +170,6 @@ describe("memory index", () => { embedBatchInputCalls = 0; providerCalls = []; - // Keep the workspace stable to allow manager reuse across tests. mkdirSync(memoryDir, { recursive: true }); // Clean additional paths that may have been created by earlier cases. @@ -243,30 +246,9 @@ describe("memory index", () => { return result.manager as MemoryIndexManager; } - function getManagerCacheKey(cfg: TestCfg): string { - const memorySearch = cfg.agents?.defaults?.memorySearch; - const storePath = memorySearch?.store?.path; - if (!storePath) { - throw new Error("store path missing"); - } - return JSON.stringify({ - workspaceDir, - storePath, - memorySearch, - }); - } - async function getPersistentManager(cfg: TestCfg): Promise { - const cacheKey = getManagerCacheKey(cfg); - const cached = managersByCacheKey.get(cacheKey); - if (cached) { - resetManagerForTest(cached); - return cached; - } - const result = await getMemorySearchManager({ cfg, agentId: "main" }); const manager = requireManager(result); - managersByCacheKey.set(cacheKey, manager); managersForCleanup.add(manager); resetManagerForTest(manager); return manager; diff --git a/src/plugins/http-registry.test.ts b/src/plugins/http-registry.test.ts index 05bd337eed6..cee87a16114 100644 --- a/src/plugins/http-registry.test.ts +++ b/src/plugins/http-registry.test.ts @@ -4,6 +4,7 @@ import { createEmptyPluginRegistry } from "./registry.js"; import { pinActivePluginHttpRouteRegistry, releasePinnedPluginHttpRouteRegistry, + resetPluginRuntimeStateForTest, setActivePluginRegistry, } from "./runtime.js"; @@ -45,7 +46,7 @@ function expectRouteRegistrationDenied(params: { describe("registerPluginHttpRoute", () => { afterEach(() => { releasePinnedPluginHttpRouteRegistry(); - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); }); it("registers route and unregisters it", () => { diff --git a/src/plugins/runtime.test.ts b/src/plugins/runtime.test.ts index b62a81314aa..e37b97f96bb 100644 --- a/src/plugins/runtime.test.ts +++ b/src/plugins/runtime.test.ts @@ -3,6 +3,7 @@ import { createEmptyPluginRegistry } from "./registry.js"; import { pinActivePluginHttpRouteRegistry, releasePinnedPluginHttpRouteRegistry, + resetPluginRuntimeStateForTest, resolveActivePluginHttpRouteRegistry, setActivePluginRegistry, } from "./runtime.js"; @@ -10,7 +11,7 @@ import { describe("plugin runtime route registry", () => { afterEach(() => { releasePinnedPluginHttpRouteRegistry(); - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); }); it("keeps the pinned route registry when the active plugin registry changes", () => { diff --git a/src/plugins/runtime.ts b/src/plugins/runtime.ts index f5f8133e5ba..c1c8974adc2 100644 --- a/src/plugins/runtime.ts +++ b/src/plugins/runtime.ts @@ -98,3 +98,12 @@ export function getActivePluginRegistryKey(): string | null { export function getActivePluginRegistryVersion(): number { return state.version; } + +export function resetPluginRuntimeStateForTest(): void { + const emptyRegistry = createEmptyPluginRegistry(); + state.registry = emptyRegistry; + state.httpRouteRegistry = emptyRegistry; + state.httpRouteRegistryPinned = false; + state.key = null; + state.version += 1; +} diff --git a/src/secrets/runtime.test.ts b/src/secrets/runtime.test.ts index 5afff36b175..92e942ab12f 100644 --- a/src/secrets/runtime.test.ts +++ b/src/secrets/runtime.test.ts @@ -3,7 +3,12 @@ import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ensureAuthProfileStore, type AuthProfileStore } from "../agents/auth-profiles.js"; -import { loadConfig, type OpenClawConfig, writeConfigFile } from "../config/config.js"; +import { + clearConfigCache, + loadConfig, + type OpenClawConfig, + writeConfigFile, +} from "../config/config.js"; import { withTempHome } from "../config/home-env.test-harness.js"; import type { PluginWebSearchProviderEntry } from "../plugins/types.js"; import { @@ -121,6 +126,8 @@ describe("secrets runtime snapshot", () => { afterEach(() => { clearSecretsRuntimeSnapshot(); + clearConfigCache(); + resolvePluginWebSearchProvidersMock.mockReset(); }); const allowInsecureTempSecretFile = process.platform === "win32";