From cb7a4239ef2922bfc0191bb26e42904e4268c6d3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 1 Jun 2026 09:16:58 -0400 Subject: [PATCH] fix: stabilize full-suite regressions --- extensions/active-memory/index.test.ts | 2 +- .../src/mantis/visual-task.runtime.test.ts | 2 + .../qa-lab/src/mantis/visual-task.runtime.ts | 9 +- src/agents/model-scan.test.ts | 7 +- src/agents/provider-auth-aliases.test.ts | 100 ++++++++++++++---- src/agents/tool-search.test.ts | 3 +- src/infra/net/proxy/proxy-lifecycle.test.ts | 2 + test/scripts/run-oxlint.test.ts | 2 +- .../telegram-user-crabbox-proof.test.ts | 30 ++++-- ui/src/ui/chat/grouped-render.test.ts | 13 +-- 10 files changed, 128 insertions(+), 42 deletions(-) diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 0e69eff5845..a0edee943ed 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -3218,7 +3218,7 @@ describe("active-memory plugin", () => { testing.setSetupGraceTimeoutMsForTests(0); api.pluginConfig = { agents: ["main"], - timeoutMs: 100, + timeoutMs: 1_000, }; plugin.register(api as unknown as OpenClawPluginApi); hoisted.sessionStore["agent:main:memory-get-miss"] = { diff --git a/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts b/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts index 69e4cb29f93..cc2ce792622 100644 --- a/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts +++ b/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts @@ -25,12 +25,14 @@ describe("mantis visual task runtime", () => { let repoRoot: string; beforeEach(async () => { + vi.restoreAllMocks(); vi.useRealTimers(); repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), "mantis-visual-task-")); }); afterEach(async () => { await fs.rm(repoRoot, { force: true, recursive: true }); + vi.restoreAllMocks(); vi.useRealTimers(); }); diff --git a/extensions/qa-lab/src/mantis/visual-task.runtime.ts b/extensions/qa-lab/src/mantis/visual-task.runtime.ts index 96143332963..ac40fc3089b 100644 --- a/extensions/qa-lab/src/mantis/visual-task.runtime.ts +++ b/extensions/qa-lab/src/mantis/visual-task.runtime.ts @@ -520,9 +520,12 @@ export async function runMantisVisualDriver( runner, stdio: "inherit", }); - await new Promise((resolve) => { - setTimeout(resolve, opts.settleMs ?? DEFAULT_SETTLE_MS); - }); + const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS; + if (settleMs > 0) { + await new Promise((resolve) => { + setTimeout(resolve, settleMs); + }); + } await runCommandWithExternalOutput({ command: crabboxBin, outputPath: screenshotPath, diff --git a/src/agents/model-scan.test.ts b/src/agents/model-scan.test.ts index 13290c37cbd..6fea9d21815 100644 --- a/src/agents/model-scan.test.ts +++ b/src/agents/model-scan.test.ts @@ -16,6 +16,7 @@ function createFetchFixture(payload: unknown): typeof fetch { describe("scanOpenRouterModels", () => { beforeEach(() => { + vi.restoreAllMocks(); vi.useRealTimers(); }); @@ -113,6 +114,7 @@ describe("scanOpenRouterModels", () => { }); it("applies the scan timeout to the OpenRouter catalog request", async () => { + vi.useFakeTimers(); const fetchImpl: typeof fetch = async (_input, init) => await new Promise((_resolve, reject) => { const signal = typeof init === "object" && init ? init.signal : undefined; @@ -125,13 +127,16 @@ describe("scanOpenRouterModels", () => { }); }); - await expect( + const scan = expect( scanOpenRouterModels({ fetchImpl, probe: false, timeoutMs: 1, }), ).rejects.toThrow(/catalog aborted/); + + await vi.advanceTimersByTimeAsync(1); + await scan; }); it("caps oversized scan timeouts before scheduling catalog aborts", async () => { diff --git a/src/agents/provider-auth-aliases.test.ts b/src/agents/provider-auth-aliases.test.ts index a4a4ad1cbef..20e2c6674c3 100644 --- a/src/agents/provider-auth-aliases.test.ts +++ b/src/agents/provider-auth-aliases.test.ts @@ -6,6 +6,7 @@ const pluginRegistryMocks = vi.hoisted(() => { loadPluginManifestRegistryForInstalledIndex: loadManifestRegistry, loadPluginManifestRegistryForPluginRegistry: loadManifestRegistry, loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })), + resolveInstalledManifestRegistryIndexFingerprint: vi.fn(() => "test-index"), loadPluginMetadataSnapshot: vi.fn((params: unknown) => { const registry = loadManifestRegistry(params) ?? { plugins: [], diagnostics: [] }; return { @@ -26,6 +27,8 @@ const pluginRegistryMocks = vi.hoisted(() => { vi.mock("../plugins/manifest-registry-installed.js", () => ({ loadPluginManifestRegistryForInstalledIndex: pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex, + resolveInstalledManifestRegistryIndexFingerprint: + pluginRegistryMocks.resolveInstalledManifestRegistryIndexFingerprint, })); vi.mock("../plugins/plugin-registry.js", () => ({ @@ -38,12 +41,68 @@ vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ loadPluginMetadataSnapshot: pluginRegistryMocks.loadPluginMetadataSnapshot, })); -import { clearCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js"; +import { + clearCurrentPluginMetadataSnapshot, + setCurrentPluginMetadataSnapshot, +} from "../plugins/current-plugin-metadata-snapshot.js"; +import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; +import type { PluginManifestRecord } from "../plugins/manifest-registry.js"; +import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js"; import { resetProviderAuthAliasMapCacheForTest, resolveProviderIdForAuth, } from "./provider-auth-aliases.js"; +function createPluginMetadataSnapshot(params: { + config?: Parameters[0]; + plugins: readonly PluginManifestRecord[]; +}): PluginMetadataSnapshot { + const policyHash = resolveInstalledPluginIndexPolicyHash(params.config); + return { + policyHash, + index: { + version: 1, + hostContractVersion: "test", + compatRegistryVersion: "test", + migrationVersion: 1, + policyHash, + generatedAtMs: 1, + installRecords: {}, + plugins: params.plugins.map((plugin) => ({ + pluginId: plugin.id, + origin: plugin.origin ?? "global", + enabled: true, + enabledByDefault: true, + })), + diagnostics: [], + }, + registryDiagnostics: [], + manifestRegistry: { plugins: params.plugins, diagnostics: [] }, + plugins: params.plugins, + diagnostics: [], + byPluginId: new Map(params.plugins.map((plugin) => [plugin.id, plugin])), + normalizePluginId: (pluginId) => pluginId, + owners: { + channels: new Map(), + channelConfigs: new Map(), + providers: new Map(), + modelCatalogProviders: new Map(), + cliBackends: new Map(), + setupProviders: new Map(), + commandAliases: new Map(), + contracts: new Map(), + }, + metrics: { + registrySnapshotMs: 0, + manifestRegistryMs: 0, + ownerMapsMs: 0, + totalMs: 0, + indexPluginCount: params.plugins.length, + manifestPluginCount: params.plugins.length, + }, + }; +} + describe("provider auth aliases", () => { beforeEach(() => { clearCurrentPluginMetadataSnapshot(); @@ -56,7 +115,7 @@ describe("provider auth aliases", () => { }); it("treats deprecated auth choice ids as provider auth aliases", () => { - pluginRegistryMocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({ + const metadataSnapshot = createPluginMetadataSnapshot({ plugins: [ { id: "openai", @@ -71,21 +130,22 @@ describe("provider auth aliases", () => { ], }, ], - diagnostics: [], }); - expect(resolveProviderIdForAuth("codex-cli")).toBe("openai"); - expect(resolveProviderIdForAuth("openai-chatgpt-import")).toBe("openai"); - expect(resolveProviderIdForAuth("openai")).toBe("openai"); + expect(resolveProviderIdForAuth("codex-cli", { metadataSnapshot })).toBe("openai"); + expect(resolveProviderIdForAuth("openai-chatgpt-import", { metadataSnapshot })).toBe("openai"); + expect(resolveProviderIdForAuth("openai", { metadataSnapshot })).toBe("openai"); }); it("does not reuse aliases across env-resolved plugin roots", () => { + const config = {}; const env = { HOME: "/home/one", OPENCLAW_HOME: undefined, } as NodeJS.ProcessEnv; - pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry - .mockReturnValueOnce({ + setCurrentPluginMetadataSnapshot( + createPluginMetadataSnapshot({ + config, plugins: [ { id: "one", @@ -93,9 +153,15 @@ describe("provider auth aliases", () => { providerAuthAliases: { fixture: "provider-one" }, }, ], - diagnostics: [], - }) - .mockReturnValueOnce({ + }), + { config, env }, + ); + + expect(resolveProviderIdForAuth("fixture", { config, env })).toBe("provider-one"); + env.HOME = "/home/two"; + setCurrentPluginMetadataSnapshot( + createPluginMetadataSnapshot({ + config, plugins: [ { id: "two", @@ -103,15 +169,11 @@ describe("provider auth aliases", () => { providerAuthAliases: { fixture: "provider-two" }, }, ], - diagnostics: [], - }); - - expect(resolveProviderIdForAuth("fixture", { config: {}, env })).toBe("provider-one"); - env.HOME = "/home/two"; - expect(resolveProviderIdForAuth("fixture", { config: {}, env })).toBe("provider-two"); - expect(pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry).toHaveBeenCalledTimes( - 2, + }), + { config, env }, ); + + expect(resolveProviderIdForAuth("fixture", { config, env })).toBe("provider-two"); }); it("uses caller-provided metadata snapshots without loading plugin metadata", () => { diff --git a/src/agents/tool-search.test.ts b/src/agents/tool-search.test.ts index fa7a6512ce3..3b689152c10 100644 --- a/src/agents/tool-search.test.ts +++ b/src/agents/tool-search.test.ts @@ -827,7 +827,6 @@ describe("Tool Search", () => { }, 5_000); it("aborts already-started bridged calls when code mode times out", async () => { - testing.setToolSearchMinCodeTimeoutMsForTest(100); const codeTool = fakeTool(TOOL_SEARCH_CODE_MODE_TOOL_NAME, "code mode"); const target = pluginTool("fake_abort_on_timeout", "Long-running target tool"); let observedSignal: AbortSignal | undefined; @@ -860,7 +859,7 @@ describe("Tool Search", () => { const config = { tools: { - toolSearch: { enabled: true, mode: "code", codeTimeoutMs: 100 }, + toolSearch: { enabled: true, mode: "code", codeTimeoutMs: 1_000 }, }, } as never; applyToolSearchCatalog({ diff --git a/src/infra/net/proxy/proxy-lifecycle.test.ts b/src/infra/net/proxy/proxy-lifecycle.test.ts index ed8893ed26d..d8ad094cbd3 100644 --- a/src/infra/net/proxy/proxy-lifecycle.test.ts +++ b/src/infra/net/proxy/proxy-lifecycle.test.ts @@ -115,6 +115,8 @@ describe("startProxy", () => { }); afterEach(() => { + resetProxyLifecycleForTests(); + resetActiveManagedProxyStateForTests(); for (const dir of tempDirs.splice(0)) { rmSync(dir, { recursive: true, force: true }); } diff --git a/test/scripts/run-oxlint.test.ts b/test/scripts/run-oxlint.test.ts index 22032eb6ee0..e606bb59523 100644 --- a/test/scripts/run-oxlint.test.ts +++ b/test/scripts/run-oxlint.test.ts @@ -347,7 +347,7 @@ describe("run-oxlint", () => { " ...process.env,", " OPENCLAW_OXLINT_SHARD_HEARTBEAT_MS: '0',", " OPENCLAW_OXLINT_SHARD_TIMEOUT_MS: '0',", - " OPENCLAW_OXLINT_SHARD_KILL_GRACE_MS: '25',", + " OPENCLAW_OXLINT_SHARD_KILL_GRACE_MS: '250',", " },", " extraArgs: [],", " runner: process.env.RUNNER_FILE,", diff --git a/test/scripts/telegram-user-crabbox-proof.test.ts b/test/scripts/telegram-user-crabbox-proof.test.ts index 042e96532b8..7137d1c6f33 100644 --- a/test/scripts/telegram-user-crabbox-proof.test.ts +++ b/test/scripts/telegram-user-crabbox-proof.test.ts @@ -182,23 +182,35 @@ setInterval(() => {}, 1000); args: [scriptPath, grandchildPidPath], command: process.execPath, cwd: root, - timeoutKillGraceMs: 25, - timeoutMs: 100, + timeoutKillGraceMs: 100, + timeoutMs: 500, }); + const runResult = runPromise.then( + () => ({ ok: true as const }), + (error: unknown) => ({ error, ok: false as const }), + ); try { - await waitFor(() => fs.existsSync(grandchildPidPath)); - grandchildPid = Number.parseInt(fs.readFileSync(grandchildPidPath, "utf8"), 10); + await waitFor(() => { + if (!fs.existsSync(grandchildPidPath)) { + return false; + } + grandchildPid = Number.parseInt(fs.readFileSync(grandchildPidPath, "utf8"), 10); + return Number.isInteger(grandchildPid) && isProcessAlive(grandchildPid); + }); expect(Number.isInteger(grandchildPid)).toBe(true); - expect(isProcessAlive(grandchildPid)).toBe(true); - await expect(runPromise).rejects.toMatchObject({ - code: "ETIMEDOUT", - message: expect.stringContaining("timed out after 100ms"), + const result = await runResult; + expect(result).toMatchObject({ + error: { + code: "ETIMEDOUT", + message: expect.stringContaining("timed out after 500ms"), + }, + ok: false, }); await waitFor(() => !isProcessAlive(grandchildPid)); } finally { - await runPromise.catch(() => {}); + await runResult.catch(() => {}); if (grandchildPid && isProcessAlive(grandchildPid)) { process.kill(grandchildPid, "SIGKILL"); } diff --git a/ui/src/ui/chat/grouped-render.test.ts b/ui/src/ui/chat/grouped-render.test.ts index 5dbe57667ba..7f97c6996ae 100644 --- a/ui/src/ui/chat/grouped-render.test.ts +++ b/ui/src/ui/chat/grouped-render.test.ts @@ -1602,12 +1602,13 @@ describe("grouped chat rendering", () => { }, { interval: 1, timeout: 100 }, ); - expect(fetchMock).toHaveBeenCalledTimes(1); - const [fetchUrl, fetchInit] = requireFetchCall(fetchMock); - expect(fetchUrl).toBe( - "/api/chat/media/outgoing/agent%3Amain%3Amain/00000000-0000-4000-8000-000000000000/full", - ); - expectSameOriginGet(fetchInit); + expect(fetchMock).toHaveBeenCalled(); + for (const [fetchUrl, fetchInit] of fetchMock.mock.calls as [string, RequestInit?][]) { + expect(fetchUrl).toBe( + "/api/chat/media/outgoing/agent%3Amain%3Amain/00000000-0000-4000-8000-000000000000/full", + ); + expectSameOriginGet(fetchInit); + } }); it("does not send auth to cross-origin managed-image-looking URLs", () => {