test: streamline runtime wrapper test reloads

This commit is contained in:
Peter Steinberger
2026-04-03 04:39:38 +01:00
parent ffd34f8896
commit e3674bcc04
31 changed files with 137 additions and 174 deletions

View File

@@ -30,7 +30,6 @@ describe("normalizeChatType", () => {
describe("WA_WEB_AUTH_DIR", () => {
afterEach(() => {
vi.doUnmock("../plugins/runtime/runtime-whatsapp-boundary.js");
vi.resetModules();
});
it("resolves lazily and caches across the legacy and channels/web entrypoints", async () => {

View File

@@ -3,7 +3,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
afterEach(() => {
vi.doUnmock("../../plugins/discovery.js");
vi.doUnmock("../../plugins/manifest-registry.js");
vi.resetModules();
});
describe("bundled channel entry shape guards", () => {

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
describe("bundled channel config runtime", () => {
beforeEach(() => {
@@ -6,11 +6,6 @@ describe("bundled channel config runtime", () => {
vi.doUnmock("../channels/plugins/bundled.js");
});
afterEach(() => {
vi.resetModules();
vi.doUnmock("../channels/plugins/bundled.js");
});
it("tolerates an unavailable bundled channel list during import", async () => {
vi.doMock("../channels/plugins/bundled.js", () => ({
listBundledChannelPlugins: () => undefined,
@@ -23,7 +18,6 @@ describe("bundled channel config runtime", () => {
});
it("falls back to static channel schemas when bundled plugin access hits a TDZ-style ReferenceError", async () => {
vi.resetModules();
vi.doMock("../channels/plugins/bundled.js", () => {
return {
listBundledChannelPlugins() {

View File

@@ -1,11 +1,8 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
describe("session store module imports", () => {
beforeEach(() => {
vi.resetModules();
});
it("does not load archive runtime on module import", async () => {
vi.resetModules();
const archiveRuntimeLoads = vi.fn();
vi.doMock("../gateway/session-archive.runtime.js", async (importOriginal) => {
archiveRuntimeLoads();

View File

@@ -1,5 +1,6 @@
import process from "node:process";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../test/helpers/import-fresh.js";
const applyCliProfileEnvMock = vi.hoisted(() => vi.fn());
const attachChildProcessBridgeMock = vi.hoisted(() => vi.fn());
@@ -67,13 +68,19 @@ vi.mock("./version.js", () => ({
VERSION: "9.9.9-test",
}));
async function importEntry(scope: string) {
return await importFreshModule<typeof import("./entry.js")>(
import.meta.url,
`./entry.js?scope=${scope}`,
);
}
describe("entry root version fast path", () => {
let originalArgv: string[];
let originalGatewayToken: string | undefined;
let exitSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
originalArgv = [...process.argv];
originalGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
@@ -97,7 +104,7 @@ describe("entry root version fast path", () => {
it("prints commit-tagged version output when commit metadata is available", async () => {
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await import("./entry.js");
await importEntry("commit-tagged");
await vi.waitFor(() => {
expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test (abc1234)");
expect(exitSpy).toHaveBeenCalledWith(0);
@@ -110,7 +117,7 @@ describe("entry root version fast path", () => {
resolveCommitHashMock.mockReturnValueOnce(null);
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await import("./entry.js");
await importEntry("plain-version");
await vi.waitFor(() => {
expect(logSpy).toHaveBeenCalledWith("OpenClaw 9.9.9-test");
expect(exitSpy).toHaveBeenCalledWith(0);
@@ -123,7 +130,7 @@ describe("entry root version fast path", () => {
resolveCliContainerTargetMock.mockReturnValue("demo");
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
await import("./entry.js");
await importEntry("container-target");
await vi.waitFor(() => {
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
});
@@ -138,7 +145,7 @@ describe("entry root version fast path", () => {
process.env.OPENCLAW_GATEWAY_TOKEN = "demo-token";
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
await import("./entry.js");
await importEntry("gateway-override");
await vi.waitFor(() => {
expect(runCliMock).toHaveBeenCalledWith(["node", "openclaw", "--version"]);
});

View File

@@ -26,7 +26,6 @@ let enabledServer: Awaited<ReturnType<typeof startServer>>;
let enabledPort: number;
beforeAll(async () => {
vi.resetModules();
({ clearMemoryEmbeddingProviders, registerMemoryEmbeddingProvider } =
await import("../plugins/memory-embedding-providers.js"));
createEmbeddingProviderMock = vi.fn(

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { withEnv } from "../test-utils/env.js";
const loggerMocks = vi.hoisted(() => ({
@@ -18,10 +18,13 @@ let logAcceptedEnvOption: EnvModule["logAcceptedEnvOption"];
let normalizeEnv: EnvModule["normalizeEnv"];
let normalizeZaiEnv: EnvModule["normalizeZaiEnv"];
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
({ isTruthyEnvValue, logAcceptedEnvOption, normalizeEnv, normalizeZaiEnv } =
await import("./env.js"));
});
beforeEach(() => {
loggerMocks.info.mockClear();
});

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { GatewayClient } from "../gateway/client.js";
import type { PluginApprovalRequest, PluginApprovalResolved } from "./plugin-approvals.js";
@@ -50,8 +50,7 @@ afterEach(() => {
vi.useRealTimers();
});
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ createExecApprovalChannelRuntime } = await import("./exec-approval-channel-runtime.js"));
});

View File

@@ -6,6 +6,7 @@ import {
withRealpathSymlinkRebindRace,
} from "../test-utils/symlink-rebind-race.js";
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
import * as pinnedPathHelperModule from "./fs-pinned-path-helper.js";
import {
appendFileWithinRoot,
copyFileWithinRoot,
@@ -349,65 +350,36 @@ describe("fs-safe", () => {
it.runIf(process.platform !== "win32")(
"falls back to legacy remove when the pinned helper cannot spawn",
async () => {
vi.resetModules();
vi.doMock("./fs-pinned-path-helper.js", async () => {
const actual = await vi.importActual<typeof import("./fs-pinned-path-helper.js")>(
"./fs-pinned-path-helper.js",
);
const error = new Error("spawn missing python ENOENT") as NodeJS.ErrnoException;
error.code = "ENOENT";
error.syscall = "spawn python3";
return {
...actual,
runPinnedPathHelper: vi.fn(async () => {
throw error;
}),
};
});
const { removePathWithinRoot: removePathWithinRootWithFallback } =
await import("./fs-safe.js");
const error = new Error("spawn missing python ENOENT") as NodeJS.ErrnoException;
error.code = "ENOENT";
error.syscall = "spawn python3";
vi.spyOn(pinnedPathHelperModule, "runPinnedPathHelper").mockRejectedValue(error);
const root = await tempDirs.make("openclaw-fs-safe-root-");
const targetPath = path.join(root, "nested", "out.txt");
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.writeFile(targetPath, "hello");
await removePathWithinRootWithFallback({
await removePathWithinRoot({
rootDir: root,
relativePath: "nested/out.txt",
});
await expect(fs.stat(targetPath)).rejects.toMatchObject({ code: "ENOENT" });
vi.doUnmock("./fs-pinned-path-helper.js");
vi.resetModules();
},
);
it.runIf(process.platform !== "win32")(
"falls back to legacy mkdir when the pinned helper cannot spawn",
async () => {
vi.resetModules();
vi.doMock("./fs-pinned-path-helper.js", async () => {
const actual = await vi.importActual<typeof import("./fs-pinned-path-helper.js")>(
"./fs-pinned-path-helper.js",
);
const error = new Error("spawn missing python ENOENT") as NodeJS.ErrnoException;
error.code = "ENOENT";
error.syscall = "spawn python3";
return {
...actual,
runPinnedPathHelper: vi.fn(async () => {
throw error;
}),
};
});
const { mkdirPathWithinRoot: mkdirPathWithinRootWithFallback } = await import("./fs-safe.js");
const error = new Error("spawn missing python ENOENT") as NodeJS.ErrnoException;
error.code = "ENOENT";
error.syscall = "spawn python3";
vi.spyOn(pinnedPathHelperModule, "runPinnedPathHelper").mockRejectedValue(error);
const root = await tempDirs.make("openclaw-fs-safe-root-");
await mkdirPathWithinRootWithFallback({
await mkdirPathWithinRoot({
rootDir: root,
relativePath: "nested/deeper",
});
@@ -415,8 +387,6 @@ describe("fs-safe", () => {
await expect(fs.stat(path.join(root, "nested", "deeper"))).resolves.toMatchObject({
isDirectory: expect.any(Function),
});
vi.doUnmock("./fs-pinned-path-helper.js");
vi.resetModules();
},
);

View File

@@ -209,7 +209,6 @@ function expectSuccessfulWhatsAppInternalHookPayload(
describe("deliverOutboundPayloads", () => {
beforeAll(async () => {
vi.resetModules();
({ deliverOutboundPayloads, normalizeOutboundPayloads } = await import("./deliver.js"));
});

View File

@@ -45,7 +45,6 @@ describe("deliverSessionMaintenanceWarning", () => {
let prevNodeEnv: string | undefined;
beforeAll(async () => {
vi.resetModules();
vi.doMock("../agents/agent-scope.js", () => ({
resolveSessionAgentId: mocks.resolveSessionAgentId,
}));

View File

@@ -1,6 +1,8 @@
import os from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../test/helpers/import-fresh.js";
import { withEnvAsync } from "../test-utils/env.js";
import { VERSION as runtimeVersion } from "../version.js";
vi.unmock("../version.js");
@@ -16,8 +18,10 @@ async function withPresenceModule<T>(
...env,
},
async () => {
vi.resetModules();
const module = await import("./system-presence.js");
const module = await importFreshModule<typeof import("./system-presence.js")>(
import.meta.url,
`./system-presence.js?scope=${JSON.stringify(env)}`,
);
return await run(module);
},
);
@@ -46,7 +50,7 @@ describe("system-presence version fallback", () => {
OPENCLAW_SERVICE_VERSION: "2.4.6-service",
npm_package_version: "1.0.0-package",
},
async () => (await import("../version.js")).VERSION,
runtimeVersion,
);
});
@@ -68,7 +72,7 @@ describe("system-presence version fallback", () => {
OPENCLAW_SERVICE_VERSION: "2.4.6-service",
npm_package_version: "1.0.0-package",
},
async () => (await import("../version.js")).VERSION,
runtimeVersion,
);
});
@@ -79,7 +83,7 @@ describe("system-presence version fallback", () => {
OPENCLAW_SERVICE_VERSION: "\t",
npm_package_version: "1.0.0-package",
},
async () => (await import("../version.js")).VERSION,
runtimeVersion,
);
});
@@ -90,7 +94,7 @@ describe("system-presence version fallback", () => {
OPENCLAW_SERVICE_VERSION: "\t",
npm_package_version: "1.0.0-package",
},
async () => (await import("../version.js")).VERSION,
runtimeVersion,
);
});
@@ -100,8 +104,10 @@ describe("system-presence version fallback", () => {
vi.spyOn(os, "networkInterfaces").mockImplementation(() => {
throw new Error("uv_interface_addresses failed");
});
vi.resetModules();
const module = await import("./system-presence.js");
const module = await importFreshModule<typeof import("./system-presence.js")>(
import.meta.url,
"./system-presence.js?scope=hostname-fallback",
);
const selfEntry = module.listSystemPresence().find((entry) => entry.reason === "self");
expect(selfEntry?.host).toBe("test-host");
expect(selfEntry?.ip).toBe("test-host");

View File

@@ -1,5 +1,6 @@
import fs from "node:fs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../test/helpers/import-fresh.js";
import { onDiagnosticEvent, resetDiagnosticEventsForTest } from "../infra/diagnostic-events.js";
import {
diagnosticSessionStates,
@@ -72,11 +73,13 @@ describe("logger import side effects", () => {
it("does not mkdir at import time", async () => {
vi.useRealTimers();
vi.resetModules();
const mkdirSpy = vi.spyOn(fs, "mkdirSync");
await import("./logger.js");
await importFreshModule<typeof import("./logger.js")>(
import.meta.url,
"./logger.js?scope=diagnostic-mkdir",
);
expect(mkdirSpy).not.toHaveBeenCalled();
});

View File

@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { importFreshModule } from "../../test/helpers/import-fresh.js";
type LoggerModule = typeof import("./logger.js");
@@ -12,7 +13,6 @@ async function importBrowserSafeLogger(params?: {
module: LoggerModule;
resolvePreferredOpenClawTmpDir: ReturnType<typeof vi.fn>;
}> {
vi.resetModules();
const resolvePreferredOpenClawTmpDir =
params?.resolvePreferredOpenClawTmpDir ??
vi.fn(() => {
@@ -34,13 +34,15 @@ async function importBrowserSafeLogger(params?: {
value: undefined,
});
const module = await import("./logger.js");
const module = await importFreshModule<LoggerModule>(
import.meta.url,
"./logger.js?scope=browser-safe",
);
return { module, resolvePreferredOpenClawTmpDir };
}
describe("logging/logger browser-safe import", () => {
afterEach(() => {
vi.resetModules();
vi.doUnmock("../infra/tmp-openclaw-dir.js");
Object.defineProperty(process, "getBuiltinModule", {
configurable: true,

View File

@@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const runAudioTranscriptionMock = vi.hoisted(() => vi.fn());
@@ -9,12 +9,14 @@ vi.mock("./audio-transcription-runner.js", () => ({
let transcribeFirstAudio: typeof import("./audio-preflight.js").transcribeFirstAudio;
describe("transcribeFirstAudio", () => {
beforeEach(async () => {
vi.resetModules();
runAudioTranscriptionMock.mockReset();
beforeAll(async () => {
({ transcribeFirstAudio } = await import("./audio-preflight.js"));
});
beforeEach(() => {
runAudioTranscriptionMock.mockReset();
});
it("runs audio preflight in auto mode when audio config is absent", async () => {
runAudioTranscriptionMock.mockResolvedValueOnce({
transcript: "voice note transcript",

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { withAudioFixture, withVideoFixture } from "./runner.test-utils.js";
import type { AudioTranscriptionRequest, VideoDescriptionRequest } from "./types.js";
@@ -22,6 +22,7 @@ vi.mock("../infra/net/proxy-fetch.js", () => ({
}));
let buildProviderRegistry: typeof import("./runner.js").buildProviderRegistry;
let clearMediaUnderstandingBinaryCacheForTests: typeof import("./runner.js").clearMediaUnderstandingBinaryCacheForTests;
let runCapability: typeof import("./runner.js").runCapability;
async function runAudioCapabilityWithFetchCapture(params: {
@@ -75,11 +76,15 @@ async function runAudioCapabilityWithFetchCapture(params: {
}
describe("runCapability proxy fetch passthrough", () => {
beforeEach(async () => {
beforeAll(async () => {
({ buildProviderRegistry, clearMediaUnderstandingBinaryCacheForTests, runCapability } =
await import("./runner.js"));
});
beforeEach(() => {
vi.useRealTimers();
vi.resetModules();
vi.clearAllMocks();
({ buildProviderRegistry, runCapability } = await import("./runner.js"));
clearMediaUnderstandingBinaryCacheForTests();
});
afterEach(() => vi.unstubAllEnvs());

View File

@@ -173,7 +173,6 @@ describe("fetchRemoteMedia", () => {
const telegramFileUrl = `https://api.telegram.org/file/bot${telegramToken}/photos/1.jpg`;
beforeAll(async () => {
vi.resetModules();
({ fetchRemoteMedia } = await import("./fetch.js"));
});

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import { getImageMetadata } from "./image-ops.js";
describe("image-ops temp dir", () => {
let createdTempDir = "";
@@ -18,11 +19,9 @@ describe("image-ops temp dir", () => {
afterEach(() => {
delete process.env.OPENCLAW_IMAGE_BACKEND;
vi.restoreAllMocks();
vi.resetModules();
});
it("creates sips temp dirs under the secured OpenClaw tmp root", async () => {
const { getImageMetadata } = await import("./image-ops.js");
const secureRoot = resolvePreferredOpenClawTmpDir();
await getImageMetadata(Buffer.from("image"));

View File

@@ -129,10 +129,14 @@ describe("plugin activation boundary", () => {
const { isChannelConfigured, resolveEnvApiKey } = await importConfigHelpers();
expect(isChannelConfigured({}, "whatsapp", {})).toBe(false);
// Anthropic Vertex auth depends on ambient ADC state on the current machine.
expect([null, { apiKey: "gcp-vertex-credentials", source: "gcloud adc" }]).toContainEqual(
resolveEnvApiKey("anthropic-vertex", {}),
);
expect(
resolveEnvApiKey("anthropic-vertex", {
ANTHROPIC_VERTEX_USE_GCP_METADATA: "true",
}),
).toEqual({
apiKey: "gcp-vertex-credentials",
source: "gcloud adc",
});
expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled();
});

View File

@@ -17,7 +17,6 @@ async function expectGlobalRunnerState(expected: { hasRunner: boolean; registry?
afterEach(async () => {
const mod = await importHookRunnerGlobalModule();
mod.resetGlobalHookRunner();
vi.resetModules();
});
describe("hook-runner-global", () => {

View File

@@ -1,5 +1,5 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
expectAugmentedCodexCatalog,
expectCodexBuiltInSuppression,
@@ -234,7 +234,7 @@ async function expectResolvedAsyncValues(
}
describe("provider-runtime", () => {
beforeEach(async () => {
beforeAll(async () => {
vi.resetModules();
vi.doMock("./providers.js", () => ({
resolveCatalogHookProviderPluginIds: (params: unknown) =>
@@ -285,6 +285,9 @@ describe("provider-runtime", () => {
validateProviderReplayTurnsWithPlugin,
wrapProviderStreamFn,
} = await import("./provider-runtime.js"));
});
beforeEach(() => {
resetProviderRuntimeHookCacheForTest();
resolvePluginProvidersMock.mockReset();
resolvePluginProvidersMock.mockReturnValue([]);

View File

@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import type { PluginRuntimeGatewayRequestScope } from "./gateway-request-scope.js";
const TEST_SCOPE: PluginRuntimeGatewayRequestScope = {
@@ -6,10 +6,6 @@ const TEST_SCOPE: PluginRuntimeGatewayRequestScope = {
isWebchatConnect: (() => false) as PluginRuntimeGatewayRequestScope["isWebchatConnect"],
};
afterEach(() => {
vi.resetModules();
});
describe("gateway request scope", () => {
async function importGatewayRequestScopeModule() {
return await import("./gateway-request-scope.js");

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
type MockRegistryToolEntry = {
pluginId: string;
@@ -193,8 +193,12 @@ function expectConflictingCoreNameResolution(params: {
}
describe("resolvePluginTools optional tools", () => {
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
({ resolvePluginTools } = await import("./tools.js"));
({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js"));
});
beforeEach(() => {
loadOpenClawPluginsMock.mockClear();
resolveRuntimePluginRegistryMock.mockReset();
resolveRuntimePluginRegistryMock.mockImplementation((params) =>
@@ -205,11 +209,7 @@ describe("resolvePluginTools optional tools", () => {
config,
changes: [],
}));
({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js"));
resetPluginRuntimeStateForTest();
({ resolvePluginTools } = await import("./tools.js"));
({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js"));
resetPluginRuntimeStateForTest();
resetPluginRuntimeStateForTest?.();
});
afterEach(() => {

View File

@@ -1,7 +1,7 @@
/**
* Test: before_compaction & after_compaction hook wiring
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { makeZeroUsageSnapshot } from "../agents/usage.js";
const hookMocks = vi.hoisted(() => ({
@@ -17,8 +17,7 @@ describe("compaction hook wiring", () => {
let handleAutoCompactionStart: typeof import("../agents/pi-embedded-subscribe.handlers.compaction.js").handleAutoCompactionStart;
let handleAutoCompactionEnd: typeof import("../agents/pi-embedded-subscribe.handlers.compaction.js").handleAutoCompactionEnd;
beforeEach(async () => {
vi.resetModules();
beforeAll(async () => {
hookMocks.runner.hasHooks.mockClear();
hookMocks.runner.hasHooks.mockReturnValue(false);
hookMocks.runner.runBeforeCompaction.mockClear();
@@ -36,6 +35,16 @@ describe("compaction hook wiring", () => {
await import("../agents/pi-embedded-subscribe.handlers.compaction.js"));
});
beforeEach(() => {
hookMocks.runner.hasHooks.mockClear();
hookMocks.runner.hasHooks.mockReturnValue(false);
hookMocks.runner.runBeforeCompaction.mockClear();
hookMocks.runner.runBeforeCompaction.mockResolvedValue(undefined);
hookMocks.runner.runAfterCompaction.mockClear();
hookMocks.runner.runAfterCompaction.mockResolvedValue(undefined);
hookMocks.emitAgentEvent.mockClear();
});
function createCompactionEndCtx(params: {
runId: string;
messages?: unknown[];

View File

@@ -261,7 +261,6 @@ function expectInactiveWebFetchProviderSecretRef(params: {
describe("runtime web tools resolution", () => {
beforeAll(async () => {
vi.resetModules();
bundledWebSearchProviders = await import("../plugins/web-search-providers.js");
runtimeWebSearchProviders = await import("../plugins/web-search-providers.runtime.js");
bundledWebFetchProviders = await import("../plugins/web-fetch-providers.js");

View File

@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import type { OpenClawConfig } from "../config/config.js";
import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
@@ -242,15 +242,18 @@ function buildAuthStoreForTarget(entry: SecretRegistryEntry, envId: string): Aut
}
describe("secrets runtime target coverage", () => {
beforeAll(async () => {
({ clearSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot } = await import("./runtime.js"));
});
afterEach(() => {
clearSecretsRuntimeSnapshot();
resolveBundledPluginWebSearchProvidersMock.mockReset();
resolvePluginWebSearchProvidersMock.mockReset();
});
beforeEach(async () => {
vi.resetModules();
({ clearSecretsRuntimeSnapshot, prepareSecretsRuntimeSnapshot } = await import("./runtime.js"));
beforeEach(() => {
clearSecretsRuntimeSnapshot();
});
it("handles every openclaw.json registry target when configured as active", async () => {

View File

@@ -85,7 +85,6 @@ describe("light background detection", () => {
afterEach(() => {
process.env = { ...originalEnv };
vi.resetModules();
});
async function importThemeWithEnv(env: Record<string, string | undefined>) {

View File

@@ -1,21 +1,15 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createStorageMock } from "../../test-helpers/storage.ts";
import * as translate from "../lib/translate.ts";
import { pt_BR } from "../locales/pt-BR.ts";
import { zh_CN } from "../locales/zh-CN.ts";
import { zh_TW } from "../locales/zh-TW.ts";
type TranslateModule = typeof import("../lib/translate.ts");
describe("i18n", () => {
let translate: TranslateModule;
beforeEach(async () => {
vi.resetModules();
vi.stubGlobal("localStorage", createStorageMock());
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
translate = await import("../lib/translate.ts");
localStorage.clear();
// Reset to English
await translate.i18n.setLocale("en");
});

View File

@@ -1,6 +1,6 @@
/* @vitest-environment jsdom */
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ChatHost } from "./app-chat.ts";
const { setLastActiveSessionKeyMock } = vi.hoisted(() => ({
@@ -15,8 +15,10 @@ let handleSendChat: typeof import("./app-chat.ts").handleSendChat;
let refreshChatAvatar: typeof import("./app-chat.ts").refreshChatAvatar;
let clearPendingQueueItemsForRun: typeof import("./app-chat.ts").clearPendingQueueItemsForRun;
async function loadChatHelpers(): Promise<void> {
vi.resetModules();
async function loadChatHelpers(params?: { reload?: boolean }): Promise<void> {
if (params?.reload) {
vi.resetModules();
}
({ handleSendChat, refreshChatAvatar, clearPendingQueueItemsForRun } =
await import("./app-chat.ts"));
}
@@ -47,7 +49,7 @@ function makeHost(overrides?: Partial<ChatHost>): ChatHost {
}
describe("refreshChatAvatar", () => {
beforeEach(async () => {
beforeAll(async () => {
await loadChatHelpers();
});
@@ -91,11 +93,14 @@ describe("refreshChatAvatar", () => {
});
describe("handleSendChat", () => {
beforeEach(async () => {
setLastActiveSessionKeyMock.mockReset();
beforeAll(async () => {
await loadChatHelpers();
});
beforeEach(() => {
setLastActiveSessionKeyMock.mockReset();
});
afterEach(() => {
vi.unstubAllGlobals();
vi.doUnmock("./chat/slash-command-executor.ts");
@@ -173,7 +178,7 @@ describe("handleSendChat", () => {
})),
};
});
await loadChatHelpers();
await loadChatHelpers({ reload: true });
const host = makeHost({
client: { request: vi.fn() } as unknown as ChatHost["client"],

View File

@@ -1,24 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createStorageMock } from "../test-helpers/storage.ts";
type NavigationModule = typeof import("./navigation.ts");
import { describe, expect, it } from "vitest";
import { TAB_GROUPS, tabFromPath } from "./navigation.ts";
describe("TAB_GROUPS", () => {
let navigation: NavigationModule;
beforeEach(async () => {
vi.resetModules();
vi.stubGlobal("localStorage", createStorageMock());
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
navigation = await import("./navigation.ts");
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("does not expose unfinished settings slices in the sidebar", () => {
const settings = navigation.TAB_GROUPS.find((group) => group.label === "settings");
const settings = TAB_GROUPS.find((group) => group.label === "settings");
expect(settings?.tabs).toEqual([
"config",
"communications",
@@ -32,11 +17,11 @@ describe("TAB_GROUPS", () => {
});
it("routes every published settings slice", () => {
expect(navigation.tabFromPath("/communications")).toBe("communications");
expect(navigation.tabFromPath("/appearance")).toBe("appearance");
expect(navigation.tabFromPath("/automation")).toBe("automation");
expect(navigation.tabFromPath("/infrastructure")).toBe("infrastructure");
expect(navigation.tabFromPath("/ai-agents")).toBe("aiAgents");
expect(navigation.tabFromPath("/config")).toBe("config");
expect(tabFromPath("/communications")).toBe("communications");
expect(tabFromPath("/appearance")).toBe("appearance");
expect(tabFromPath("/automation")).toBe("automation");
expect(tabFromPath("/infrastructure")).toBe("infrastructure");
expect(tabFromPath("/ai-agents")).toBe("aiAgents");
expect(tabFromPath("/config")).toBe("config");
});
});

View File

@@ -1,5 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createStorageMock } from "../test-helpers/storage.ts";
import { loadSettings, saveSettings } from "./storage.ts";
function setTestLocation(params: { protocol: string; host: string; pathname: string }) {
vi.stubGlobal("location", {
@@ -38,7 +39,6 @@ function expectedGatewayUrl(basePath: string): string {
describe("loadSettings default gateway URL derivation", () => {
beforeEach(() => {
vi.resetModules();
vi.stubGlobal("localStorage", createStorageMock());
vi.stubGlobal("sessionStorage", createStorageMock());
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
@@ -61,7 +61,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
setControlUiBasePath(" /openclaw/ ");
const { loadSettings } = await import("./storage.ts");
expect(loadSettings().gatewayUrl).toBe(expectedGatewayUrl("/openclaw"));
});
@@ -72,12 +71,10 @@ describe("loadSettings default gateway URL derivation", () => {
pathname: "/apps/openclaw/chat",
});
const { loadSettings } = await import("./storage.ts");
expect(loadSettings().gatewayUrl).toBe(expectedGatewayUrl("/apps/openclaw"));
});
it("skips node sessionStorage accessors that warn without a storage file", async () => {
vi.resetModules();
vi.unstubAllGlobals();
vi.stubGlobal("localStorage", createStorageMock());
vi.stubGlobal("navigator", { language: "en-US" } as Navigator);
@@ -89,8 +86,6 @@ describe("loadSettings default gateway URL derivation", () => {
setControlUiBasePath(undefined);
const warningSpy = vi.spyOn(process, "emitWarning").mockImplementation(() => undefined);
const { loadSettings } = await import("./storage.ts");
expect(loadSettings()).toMatchObject({
gatewayUrl: expectedGatewayUrl(""),
token: "",
@@ -118,7 +113,6 @@ describe("loadSettings default gateway URL derivation", () => {
}),
);
const { loadSettings } = await import("./storage.ts");
expect(loadSettings()).toMatchObject({
gatewayUrl: "wss://gateway.example:8443/openclaw",
token: "",
@@ -155,7 +149,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
const gwUrl = expectedGatewayUrl("");
const { loadSettings, saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "session-token",
@@ -188,7 +181,6 @@ describe("loadSettings default gateway URL derivation", () => {
const gwUrl = expectedGatewayUrl("");
const otherUrl = "wss://other-gateway.example:8443";
const { loadSettings, saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "gateway-a-token",
@@ -237,7 +229,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
const gwUrl = expectedGatewayUrl("");
const { loadSettings, saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "memory-only-token",
@@ -290,7 +281,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
const gwUrl = expectedGatewayUrl("");
const { loadSettings, saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "stale-token",
@@ -336,7 +326,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
const gwUrl = expectedGatewayUrl("");
const { saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "",
@@ -370,8 +359,6 @@ describe("loadSettings default gateway URL derivation", () => {
});
const gwUrl = expectedGatewayUrl("");
const { loadSettings, saveSettings } = await import("./storage.ts");
saveSettings({
gatewayUrl: gwUrl,
token: "",
@@ -403,7 +390,6 @@ describe("loadSettings default gateway URL derivation", () => {
pathname: "/",
});
const { saveSettings } = await import("./storage.ts");
const gwUrl = expectedGatewayUrl("");
const scopedKey = `openclaw.control.settings.v1:wss://gateway.example:8443`;