test: harden channel suite isolation

This commit is contained in:
Peter Steinberger
2026-03-23 12:57:02 +00:00
parent 949d6be1d1
commit b393effba6
44 changed files with 431 additions and 213 deletions

View File

@@ -1,9 +1,13 @@
import { describe, expect, it } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { withFetchPreconnect } from "../../../test/helpers/extensions/fetch-mock.js";
import { fetchDiscord } from "./api.js";
import { jsonResponse } from "./test-http-helpers.js";
describe("fetchDiscord", () => {
beforeEach(() => {
vi.useRealTimers();
});
it("formats rate limit payloads without raw JSON", async () => {
const fetcher = withFetchPreconnect(async () =>
jsonResponse(

View File

@@ -1,10 +1,10 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { PluginRuntime } from "../../../src/plugins/runtime/types.js";
import { createStartAccountContext } from "../../../test/helpers/extensions/start-account-context.js";
import type { ResolvedDiscordAccount } from "./accounts.js";
import { discordPlugin } from "./channel.js";
import type { OpenClawConfig } from "./runtime-api.js";
import { setDiscordRuntime } from "./runtime.js";
let discordPlugin: typeof import("./channel.js").discordPlugin;
let setDiscordRuntime: typeof import("./runtime.js").setDiscordRuntime;
const probeDiscordMock = vi.hoisted(() => vi.fn());
const monitorDiscordProviderMock = vi.hoisted(() => vi.fn());
@@ -75,6 +75,13 @@ afterEach(() => {
auditDiscordChannelPermissionsMock.mockReset();
});
beforeEach(async () => {
vi.useRealTimers();
vi.resetModules();
({ discordPlugin } = await import("./channel.js"));
({ setDiscordRuntime } = await import("./runtime.js"));
});
describe("discordPlugin outbound", () => {
it("forwards mediaLocalRoots to sendMessageDiscord", async () => {
const sendMessageDiscord = vi.fn(async () => ({ messageId: "m1" }));

View File

@@ -1,16 +1,25 @@
import { MessageFlags } from "discord-api-types/v10";
import { beforeEach, describe, expect, it } from "vitest";
import {
clearDiscordComponentEntries,
registerDiscordComponentEntries,
resolveDiscordComponentEntry,
resolveDiscordModalEntry,
} from "./components-registry.js";
import {
buildDiscordComponentMessage,
buildDiscordComponentMessageFlags,
readDiscordComponentSpec,
} from "./components.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
let clearDiscordComponentEntries: typeof import("./components-registry.js").clearDiscordComponentEntries;
let registerDiscordComponentEntries: typeof import("./components-registry.js").registerDiscordComponentEntries;
let resolveDiscordComponentEntry: typeof import("./components-registry.js").resolveDiscordComponentEntry;
let resolveDiscordModalEntry: typeof import("./components-registry.js").resolveDiscordModalEntry;
let buildDiscordComponentMessage: typeof import("./components.js").buildDiscordComponentMessage;
let buildDiscordComponentMessageFlags: typeof import("./components.js").buildDiscordComponentMessageFlags;
let readDiscordComponentSpec: typeof import("./components.js").readDiscordComponentSpec;
beforeEach(async () => {
vi.resetModules();
({
clearDiscordComponentEntries,
registerDiscordComponentEntries,
resolveDiscordComponentEntry,
resolveDiscordModalEntry,
} = await import("./components-registry.js"));
({ buildDiscordComponentMessage, buildDiscordComponentMessageFlags, readDiscordComponentSpec } =
await import("./components.js"));
});
describe("discord components", () => {
it("builds v2 containers with modal trigger", () => {

View File

@@ -61,6 +61,11 @@ function createAutoThreadMentionContext() {
return { guildInfo, channelConfig };
}
beforeEach(() => {
vi.useRealTimers();
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
});
describe("registerDiscordListener", () => {
class FakeListener {}

View File

@@ -1,5 +1,5 @@
import type { Client } from "@buape/carbon";
import { ChannelType, MessageType } from "@buape/carbon";
import { MessageType } from "@buape/carbon";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
dispatchMock,
@@ -9,13 +9,19 @@ import {
updateLastRouteMock,
upsertPairingRequestMock,
} from "./monitor.tool-result.test-harness.js";
import { createDiscordMessageHandler } from "./monitor/message-handler.js";
import { __resetDiscordChannelInfoCacheForTest } from "./monitor/message-utils.js";
import { createNoopThreadBindingManager } from "./monitor/thread-bindings.js";
type Config = ReturnType<typeof import("../../../src/config/config.js").loadConfig>;
let ChannelType: typeof import("@buape/carbon").ChannelType;
let createDiscordMessageHandler: typeof import("./monitor/message-handler.js").createDiscordMessageHandler;
let __resetDiscordChannelInfoCacheForTest: typeof import("./monitor/message-utils.js").__resetDiscordChannelInfoCacheForTest;
let createNoopThreadBindingManager: typeof import("./monitor/thread-bindings.js").createNoopThreadBindingManager;
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ ChannelType } = await import("@buape/carbon"));
({ createDiscordMessageHandler } = await import("./monitor/message-handler.js"));
({ __resetDiscordChannelInfoCacheForTest } = await import("./monitor/message-utils.js"));
({ createNoopThreadBindingManager } = await import("./monitor/thread-bindings.js"));
__resetDiscordChannelInfoCacheForTest();
sendMock.mockClear().mockResolvedValue(undefined);
updateLastRouteMock.mockClear();

View File

@@ -1,14 +1,28 @@
import { describe, expect, it } from "vitest";
import { buildDiscordComponentCustomId, buildDiscordModalCustomId } from "../components.js";
import {
createDiscordComponentButton,
createDiscordComponentChannelSelect,
createDiscordComponentMentionableSelect,
createDiscordComponentModal,
createDiscordComponentRoleSelect,
createDiscordComponentStringSelect,
createDiscordComponentUserSelect,
} from "./agent-components.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
let buildDiscordComponentCustomId: typeof import("../components.js").buildDiscordComponentCustomId;
let buildDiscordModalCustomId: typeof import("../components.js").buildDiscordModalCustomId;
let createDiscordComponentButton: typeof import("./agent-components.js").createDiscordComponentButton;
let createDiscordComponentChannelSelect: typeof import("./agent-components.js").createDiscordComponentChannelSelect;
let createDiscordComponentMentionableSelect: typeof import("./agent-components.js").createDiscordComponentMentionableSelect;
let createDiscordComponentModal: typeof import("./agent-components.js").createDiscordComponentModal;
let createDiscordComponentRoleSelect: typeof import("./agent-components.js").createDiscordComponentRoleSelect;
let createDiscordComponentStringSelect: typeof import("./agent-components.js").createDiscordComponentStringSelect;
let createDiscordComponentUserSelect: typeof import("./agent-components.js").createDiscordComponentUserSelect;
beforeEach(async () => {
vi.resetModules();
({ buildDiscordComponentCustomId, buildDiscordModalCustomId } = await import("../components.js"));
({
createDiscordComponentButton,
createDiscordComponentChannelSelect,
createDiscordComponentMentionableSelect,
createDiscordComponentModal,
createDiscordComponentRoleSelect,
createDiscordComponentStringSelect,
createDiscordComponentUserSelect,
} = await import("./agent-components.js"));
});
type WildcardComponent = {
customId: string;

View File

@@ -1,5 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import { DiscordMessageListener } from "./listeners.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
let DiscordMessageListener: typeof import("./listeners.js").DiscordMessageListener;
beforeEach(async () => {
vi.resetModules();
({ DiscordMessageListener } = await import("./listeners.js"));
});
function createLogger() {
return {

View File

@@ -10,11 +10,6 @@ import {
__testing as sessionBindingTesting,
registerSessionBindingAdapter,
} from "../../../../src/infra/outbound/session-binding-service.js";
import {
preflightDiscordMessage,
resolvePreflightMentionRequirement,
shouldIgnoreBoundThreadWebhookMessage,
} from "./message-handler.preflight.js";
import {
createDiscordMessage,
createDiscordPreflightArgs,
@@ -25,10 +20,22 @@ import {
type DiscordConfig,
type DiscordMessageEvent,
} from "./message-handler.preflight.test-helpers.js";
import {
__testing as threadBindingTesting,
createThreadBindingManager,
} from "./thread-bindings.js";
let preflightDiscordMessage: typeof import("./message-handler.preflight.js").preflightDiscordMessage;
let resolvePreflightMentionRequirement: typeof import("./message-handler.preflight.js").resolvePreflightMentionRequirement;
let shouldIgnoreBoundThreadWebhookMessage: typeof import("./message-handler.preflight.js").shouldIgnoreBoundThreadWebhookMessage;
let threadBindingTesting: typeof import("./thread-bindings.js").__testing;
let createThreadBindingManager: typeof import("./thread-bindings.js").createThreadBindingManager;
beforeEach(async () => {
vi.resetModules();
({
preflightDiscordMessage,
resolvePreflightMentionRequirement,
shouldIgnoreBoundThreadWebhookMessage,
} = await import("./message-handler.preflight.js"));
({ __testing: threadBindingTesting, createThreadBindingManager } =
await import("./thread-bindings.js"));
});
function createThreadBinding(
overrides?: Partial<

View File

@@ -1,13 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import {
createDiscordMessageHandler,
preflightDiscordMessageMock,
processDiscordMessageMock,
} from "./message-handler.module-test-helpers.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
createDiscordHandlerParams,
createDiscordPreflightContext,
} from "./message-handler.test-helpers.js";
let createDiscordMessageHandler: typeof import("./message-handler.module-test-helpers.js").createDiscordMessageHandler;
let preflightDiscordMessageMock: typeof import("./message-handler.module-test-helpers.js").preflightDiscordMessageMock;
let processDiscordMessageMock: typeof import("./message-handler.module-test-helpers.js").processDiscordMessageMock;
const eventualReplyDeliveredMock = vi.hoisted(() => vi.fn());
type SetStatusFn = (patch: Record<string, unknown>) => void;
@@ -86,6 +84,12 @@ async function createLifecycleStopScenario(params: {
}
describe("createDiscordMessageHandler queue behavior", () => {
beforeEach(async () => {
vi.resetModules();
({ createDiscordMessageHandler, preflightDiscordMessageMock, processDiscordMessageMock } =
await import("./message-handler.module-test-helpers.js"));
});
it("resets busy counters when the handler is created", () => {
preflightDiscordMessageMock.mockReset();
processDiscordMessageMock.mockReset();

View File

@@ -1,10 +1,12 @@
import { describe, expect, it, vi } from "vitest";
import { listNativeCommandSpecs } from "../../../../src/auto-reply/commands-registry.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig, loadConfig } from "../../../../src/config/config.js";
import { createDiscordNativeCommand } from "./native-command.js";
import { createNoopThreadBindingManager } from "./thread-bindings.js";
let listNativeCommandSpecs: typeof import("../../../../src/auto-reply/commands-registry.js").listNativeCommandSpecs;
let createDiscordNativeCommand: typeof import("./native-command.js").createDiscordNativeCommand;
let createNoopThreadBindingManager: typeof import("./thread-bindings.js").createNoopThreadBindingManager;
function createNativeCommand(name: string): ReturnType<typeof createDiscordNativeCommand> {
function createNativeCommand(
name: string,
): ReturnType<typeof import("./native-command.js").createDiscordNativeCommand> {
const command = listNativeCommandSpecs({ provider: "discord" }).find(
(entry) => entry.name === name,
);
@@ -24,17 +26,19 @@ function createNativeCommand(name: string): ReturnType<typeof createDiscordNativ
});
}
type CommandOption = NonNullable<ReturnType<typeof createDiscordNativeCommand>["options"]>[number];
type CommandOption = NonNullable<
ReturnType<typeof import("./native-command.js").createDiscordNativeCommand>["options"]
>[number];
function findOption(
command: ReturnType<typeof createDiscordNativeCommand>,
command: ReturnType<typeof import("./native-command.js").createDiscordNativeCommand>,
name: string,
): CommandOption | undefined {
return command.options?.find((entry) => entry.name === name);
}
function requireOption(
command: ReturnType<typeof createDiscordNativeCommand>,
command: ReturnType<typeof import("./native-command.js").createDiscordNativeCommand>,
name: string,
): CommandOption {
const option = findOption(command, name);
@@ -60,6 +64,13 @@ function readChoices(option: CommandOption | undefined): unknown[] | undefined {
}
describe("createDiscordNativeCommand option wiring", () => {
beforeEach(async () => {
vi.resetModules();
({ listNativeCommandSpecs } = await import("../../../../src/auto-reply/commands-registry.js"));
({ createDiscordNativeCommand } = await import("./native-command.js"));
({ createNoopThreadBindingManager } = await import("./thread-bindings.js"));
});
it("uses autocomplete for /acp action so inline action values are accepted", async () => {
const command = createNativeCommand("acp");
const action = requireOption(command, "action");

View File

@@ -1,7 +1,13 @@
import { describe, expect, it } from "vitest";
import { __testing } from "./provider.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
let __testing: typeof import("./provider.js").__testing;
describe("resolveThreadBindingsEnabled", () => {
beforeEach(async () => {
vi.resetModules();
({ __testing } = await import("./provider.js"));
});
it("defaults to enabled when unset", () => {
expect(
__testing.resolveThreadBindingsEnabled({

View File

@@ -34,11 +34,14 @@ vi.mock("../send.js", async (importOriginal) => {
};
});
const { maybeSendBindingMessage, resolveChannelIdForBinding } =
await import("./thread-bindings.discord-api.js");
let maybeSendBindingMessage: typeof import("./thread-bindings.discord-api.js").maybeSendBindingMessage;
let resolveChannelIdForBinding: typeof import("./thread-bindings.discord-api.js").resolveChannelIdForBinding;
describe("resolveChannelIdForBinding", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ maybeSendBindingMessage, resolveChannelIdForBinding } =
await import("./thread-bindings.discord-api.js"));
hoisted.restGet.mockClear();
hoisted.createDiscordRestClient.mockClear();
hoisted.sendMessageDiscord.mockClear().mockResolvedValue({});
@@ -124,7 +127,10 @@ describe("resolveChannelIdForBinding", () => {
});
describe("maybeSendBindingMessage", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ maybeSendBindingMessage, resolveChannelIdForBinding } =
await import("./thread-bindings.discord-api.js"));
hoisted.sendMessageDiscord.mockClear().mockResolvedValue({});
hoisted.sendWebhookMessageDiscord.mockClear().mockResolvedValue({});
});

View File

@@ -15,7 +15,7 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
};
});
const { closeDiscordThreadSessions } = await import("./thread-session-close.js");
let closeDiscordThreadSessions: typeof import("./thread-session-close.js").closeDiscordThreadSessions;
function setupStore(store: Record<string, { updatedAt: number }>) {
hoisted.updateSessionStore.mockImplementation(
@@ -30,7 +30,9 @@ const MATCHED_KEY = `agent:main:discord:channel:${THREAD_ID}`;
const UNMATCHED_KEY = `agent:main:discord:channel:${OTHER_ID}`;
describe("closeDiscordThreadSessions", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ closeDiscordThreadSessions } = await import("./thread-session-close.js"));
hoisted.updateSessionStore.mockClear();
hoisted.resolveStorePath.mockClear();
hoisted.resolveStorePath.mockReturnValue("/tmp/openclaw-sessions.json");

View File

@@ -1,5 +1,4 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { normalizeDiscordOutboundTarget } from "./normalize.js";
const hoisted = vi.hoisted(() => {
const sendMessageDiscordMock = vi.fn();
@@ -37,7 +36,14 @@ vi.mock("./monitor/thread-bindings.js", async (importOriginal) => {
};
});
const { discordOutbound } = await import("./outbound-adapter.js");
let normalizeDiscordOutboundTarget: typeof import("./normalize.js").normalizeDiscordOutboundTarget;
let discordOutbound: typeof import("./outbound-adapter.js").discordOutbound;
beforeEach(async () => {
vi.resetModules();
({ normalizeDiscordOutboundTarget } = await import("./normalize.js"));
({ discordOutbound } = await import("./outbound-adapter.js"));
});
const DEFAULT_DISCORD_SEND_RESULT = {
channel: "discord",

View File

@@ -1,10 +1,8 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { MsgContext } from "../../../../src/auto-reply/templating.js";
import { expectChannelInboundContextContract as expectInboundContextContract } from "../../../../src/channels/plugins/contracts/suites.js";
import {
createBaseSignalEventHandlerDeps,
createSignalReceiveEvent,
} from "./event-handler.test-harness.js";
let expectInboundContextContract: typeof import("../../../../src/channels/plugins/contracts/suites.js").expectChannelInboundContextContract;
let createBaseSignalEventHandlerDeps: typeof import("./event-handler.test-harness.js").createBaseSignalEventHandlerDeps;
let createSignalReceiveEvent: typeof import("./event-handler.test-harness.js").createSignalReceiveEvent;
const { sendTypingMock, sendReadReceiptMock, dispatchInboundMessageMock, capture } = vi.hoisted(
() => {
@@ -52,7 +50,12 @@ let createSignalEventHandler: typeof import("./event-handler.js").createSignalEv
describe("signal createSignalEventHandler inbound context", () => {
beforeEach(async () => {
vi.useRealTimers();
vi.resetModules();
({ expectChannelInboundContextContract: expectInboundContextContract } =
await import("../../../../src/channels/plugins/contracts/suites.js"));
({ createBaseSignalEventHandlerDeps, createSignalReceiveEvent } =
await import("./event-handler.test-harness.js"));
({ createSignalEventHandler } = await import("./event-handler.js"));
capture.ctx = undefined;
sendTypingMock.mockReset().mockResolvedValue(true);

View File

@@ -10,7 +10,7 @@ import {
stopSlackMonitor,
} from "./monitor.test-helpers.js";
const { monitorSlackProvider } = await import("./monitor.js");
let monitorSlackProvider: typeof import("./monitor.js").monitorSlackProvider;
const slackTestState = getSlackTestState();
@@ -69,6 +69,12 @@ async function runMissingThreadScenario(params: {
beforeEach(() => {
resetInboundDedupe();
});
beforeEach(async () => {
vi.resetModules();
({ monitorSlackProvider } = await import("./monitor.js"));
resetInboundDedupe();
resetSlackTestState({
messages: { responsePrefix: "PFX" },
channels: {

View File

@@ -50,7 +50,7 @@ vi.mock("./message-handler/dispatch.js", () => ({
dispatchPreparedSlackMessageMock(prepared),
}));
import { createSlackMessageHandler } from "./message-handler.js";
let createSlackMessageHandler: typeof import("./message-handler.js").createSlackMessageHandler;
function createMarkMessageSeen() {
const seen = new Set<string>();
@@ -117,7 +117,9 @@ async function createInFlightMessageScenario(ts: string) {
}
describe("createSlackMessageHandler app_mention race handling", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ createSlackMessageHandler } = await import("./message-handler.js"));
prepareSlackMessageMock.mockReset();
dispatchPreparedSlackMessageMock.mockReset();
});

View File

@@ -5,7 +5,7 @@ vi.mock("../send.js", () => ({
sendMessageSlack: (...args: unknown[]) => sendMock(...args),
}));
import { deliverReplies } from "./replies.js";
let deliverReplies: typeof import("./replies.js").deliverReplies;
function baseParams(overrides?: Record<string, unknown>) {
return {
@@ -20,7 +20,9 @@ function baseParams(overrides?: Record<string, unknown>) {
}
describe("deliverReplies identity passthrough", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
({ deliverReplies } = await import("./replies.js"));
sendMock.mockReset();
});
it("passes identity to sendMessageSlack for text replies", async () => {

View File

@@ -194,7 +194,9 @@ async function loadRegisterSlackMonitorSlashCommands(): Promise<RegisterFn> {
const { dispatchMock } = getSlackSlashMocks();
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
registerSlackMonitorSlashCommandsPromise = undefined;
resetSlackSlashMocks();
});

View File

@@ -1,6 +1,14 @@
import { API_CONSTANTS } from "grammy";
import { describe, expect, it } from "vitest";
import { DEFAULT_TELEGRAM_UPDATE_TYPES, resolveTelegramAllowedUpdates } from "./allowed-updates.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
let API_CONSTANTS: typeof import("grammy").API_CONSTANTS;
let DEFAULT_TELEGRAM_UPDATE_TYPES: typeof import("./allowed-updates.js").DEFAULT_TELEGRAM_UPDATE_TYPES;
let resolveTelegramAllowedUpdates: typeof import("./allowed-updates.js").resolveTelegramAllowedUpdates;
beforeEach(async () => {
vi.resetModules();
({ API_CONSTANTS } = await import("grammy"));
({ DEFAULT_TELEGRAM_UPDATE_TYPES, resolveTelegramAllowedUpdates } =
await import("./allowed-updates.js"));
});
describe("resolveTelegramAllowedUpdates", () => {
it("includes the default update types plus reaction and channel post support", () => {

View File

@@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
let collectTelegramUnmentionedGroupIds: typeof import("./audit.js").collectTelegramUnmentionedGroupIds;
let auditTelegramGroupMembership: typeof import("./audit.js").auditTelegramGroupMembership;
@@ -31,12 +31,10 @@ async function auditSingleGroup() {
}
describe("telegram audit", () => {
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
({ collectTelegramUnmentionedGroupIds, auditTelegramGroupMembership } =
await import("./audit.js"));
});
beforeEach(() => {
fetchWithTimeoutMock.mockReset();
});

View File

@@ -1,9 +1,20 @@
import { afterEach, describe, expect, it } from "vitest";
import {
clearRuntimeConfigSnapshot,
setRuntimeConfigSnapshot,
} from "../../../src/config/config.js";
import { buildTelegramMessageContextForTest } from "./bot-message-context.test-harness.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest;
let clearRuntimeConfigSnapshot: typeof import("../../../src/config/config.js").clearRuntimeConfigSnapshot;
let setRuntimeConfigSnapshot: typeof import("../../../src/config/config.js").setRuntimeConfigSnapshot;
beforeEach(async () => {
vi.resetModules();
({ buildTelegramMessageContextForTest } = await import("./bot-message-context.test-harness.js"));
({ clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } =
await import("../../../src/config/config.js"));
clearRuntimeConfigSnapshot();
});
afterEach(() => {
clearRuntimeConfigSnapshot();
});
describe("buildTelegramMessageContext dm thread sessions", () => {
const buildContext = async (message: Record<string, unknown>) =>
@@ -110,10 +121,6 @@ describe("buildTelegramMessageContext group sessions without forum", () => {
});
describe("buildTelegramMessageContext direct peer routing", () => {
afterEach(() => {
clearRuntimeConfigSnapshot();
});
it("isolates dm sessions by sender id when chat id differs", async () => {
const runtimeCfg = {
agents: { defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" } },

View File

@@ -1,9 +1,5 @@
import { vi } from "vitest";
import {
buildTelegramMessageContext,
type BuildTelegramMessageContextParams,
type TelegramMediaRef,
} from "./bot-message-context.js";
import type { BuildTelegramMessageContextParams, TelegramMediaRef } from "./bot-message-context.js";
export const baseTelegramMessageContextConfig = {
agents: { defaults: { model: "anthropic/claude-opus-4-5", workspace: "/tmp/openclaw" } },
@@ -24,7 +20,10 @@ type BuildTelegramMessageContextForTestParams = {
export async function buildTelegramMessageContextForTest(
params: BuildTelegramMessageContextForTestParams,
): Promise<Awaited<ReturnType<typeof buildTelegramMessageContext>>> {
): Promise<
Awaited<ReturnType<typeof import("./bot-message-context.js").buildTelegramMessageContext>>
> {
const { buildTelegramMessageContext } = await import("./bot-message-context.js");
return await buildTelegramMessageContext({
primaryCtx: {
message: {

View File

@@ -33,7 +33,6 @@ vi.mock("./bot/delivery.replies.js", () => ({
let registerTelegramNativeCommands: typeof import("./bot-native-commands.js").registerTelegramNativeCommands;
import {
createCommandBot,
createNativeCommandTestParams as createNativeCommandTestParamsBase,
createPrivateCommandContext,
deliverReplies as registeredDeliverReplies,
@@ -80,6 +79,12 @@ function createNativeCommandTestParams(
});
}
function resolveDeliverRepliesCalls() {
return deliveryMocks.deliverReplies.mock.calls.length > 0
? deliveryMocks.deliverReplies.mock.calls
: registeredDeliverReplies.mock.calls;
}
describe("registerTelegramNativeCommands", () => {
beforeEach(async () => {
vi.resetModules();
@@ -270,7 +275,8 @@ describe("registerTelegramNativeCommands", () => {
expect(handler).toBeTruthy();
await handler?.(createPrivateCommandContext());
expect(registeredDeliverReplies).toHaveBeenCalledWith(
const firstDeliverRepliesCall = resolveDeliverRepliesCalls().at(0) as [unknown] | undefined;
expect(firstDeliverRepliesCall?.[0]).toEqual(
expect.objectContaining({
mediaLocalRoots: expect.arrayContaining([
expect.stringMatching(/[\\/]\.openclaw[\\/]workspace-work$/),
@@ -324,7 +330,8 @@ describe("registerTelegramNativeCommands", () => {
expect(handler).toBeTruthy();
await handler?.(createPrivateCommandContext());
expect(registeredDeliverReplies).toHaveBeenCalledWith(
const firstDeliverRepliesCall = resolveDeliverRepliesCalls().at(0) as [unknown] | undefined;
expect(firstDeliverRepliesCall?.[0]).toEqual(
expect.objectContaining({
silent: true,
replies: [expect.objectContaining({ isError: true })],

View File

@@ -11,18 +11,9 @@ const {
telegramBotRuntimeForTest,
} = harness;
const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } =
await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
const createTelegramBot = (opts: Parameters<typeof createTelegramBotBase>[0]) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
let createTelegramBot: (
opts: Parameters<typeof import("./bot.js").createTelegramBot>[0],
) => ReturnType<typeof import("./bot.js").createTelegramBot>;
const loadConfig = getLoadConfigMock();
@@ -69,6 +60,20 @@ function resolveFlushTimer(setTimeoutSpy: ReturnType<typeof vi.spyOn>) {
}
describe("createTelegramBot channel_post media", () => {
beforeEach(async () => {
vi.resetModules();
const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } =
await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
createTelegramBot = (opts) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
});
it("buffers channel_post media groups and processes them together", async () => {
setOpenChannelPostConfig();

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { GetReplyOptions, MsgContext } from "openclaw/plugin-sdk/reply-runtime";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { escapeRegExp, formatEnvelopeTimestamp } from "../../../test/helpers/envelope-timestamp.js";
import { withEnvAsync } from "../../../test/helpers/extensions/env.js";
import { useFrozenTime, useRealTime } from "../../../test/helpers/extensions/frozen-time.js";
@@ -33,22 +33,11 @@ const {
throttlerSpy,
useSpy,
} = harness;
import { resolveTelegramFetch } from "./fetch.js";
// Import after the harness registers `vi.mock(...)` for grammY and Telegram internals.
const {
createTelegramBot: createTelegramBotBase,
getTelegramSequentialKey,
setTelegramBotRuntimeForTest,
} = await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
const createTelegramBot = (opts: Parameters<typeof createTelegramBotBase>[0]) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
let resolveTelegramFetch: typeof import("./fetch.js").resolveTelegramFetch;
let createTelegramBot: (
opts: Parameters<typeof import("./bot.js").createTelegramBot>[0],
) => ReturnType<typeof import("./bot.js").createTelegramBot>;
let getTelegramSequentialKey: typeof import("./bot.js").getTelegramSequentialKey;
const loadConfig = getLoadConfigMock();
const loadWebMedia = getLoadWebMediaMock();
@@ -92,6 +81,24 @@ describe("createTelegramBot", () => {
afterAll(() => {
process.env.TZ = ORIGINAL_TZ;
});
beforeEach(async () => {
vi.resetModules();
({ resolveTelegramFetch } = await import("./fetch.js"));
const {
createTelegramBot: createTelegramBotBase,
getTelegramSequentialKey: importedGetTelegramSequentialKey,
setTelegramBotRuntimeForTest,
} = await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
getTelegramSequentialKey = importedGetTelegramSequentialKey;
createTelegramBot = (opts) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
});
// groupPolicy tests

View File

@@ -27,22 +27,13 @@ const {
wasSentByBot,
} = await import("./bot.create-telegram-bot.test-harness.js");
// Import after the harness registers `vi.mock(...)` for grammY and Telegram internals.
const { listNativeCommandSpecs, listNativeCommandSpecsForConfig } =
await import("../../../src/auto-reply/commands-registry.js");
const { loadSessionStore } = await import("../../../src/config/sessions.js");
const { normalizeTelegramCommandName } =
await import("../../../src/config/telegram-custom-commands.js");
const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } =
await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
const createTelegramBot = (opts: Parameters<typeof createTelegramBotBase>[0]) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
let listNativeCommandSpecs: typeof import("../../../src/auto-reply/commands-registry.js").listNativeCommandSpecs;
let listNativeCommandSpecsForConfig: typeof import("../../../src/auto-reply/commands-registry.js").listNativeCommandSpecsForConfig;
let loadSessionStore: typeof import("../../../src/config/sessions.js").loadSessionStore;
let normalizeTelegramCommandName: typeof import("../../../src/config/telegram-custom-commands.js").normalizeTelegramCommandName;
let createTelegramBot: (
opts: Parameters<typeof import("./bot.js").createTelegramBot>[0],
) => ReturnType<typeof import("./bot.js").createTelegramBot>;
const loadConfig = getLoadConfigMock();
const readChannelAllowFromStore = getReadChannelAllowFromStoreMock();
@@ -77,6 +68,24 @@ describe("createTelegramBot", () => {
},
});
});
beforeEach(async () => {
vi.resetModules();
({ listNativeCommandSpecs, listNativeCommandSpecsForConfig } =
await import("../../../src/auto-reply/commands-registry.js"));
({ loadSessionStore } = await import("../../../src/config/sessions.js"));
({ normalizeTelegramCommandName } =
await import("../../../src/config/telegram-custom-commands.js"));
const { createTelegramBot: createTelegramBotBase, setTelegramBotRuntimeForTest } =
await import("./bot.js");
setTelegramBotRuntimeForTest(
telegramBotRuntimeForTest as unknown as Parameters<typeof setTelegramBotRuntimeForTest>[0],
);
createTelegramBot = (opts) =>
createTelegramBotBase({
...opts,
telegramDeps: telegramBotDepsForTest,
});
});
it("merges custom commands with native commands", async () => {
const config = {

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
createWhatsAppPollFixture,
expectWhatsAppPollSent,
@@ -21,9 +21,14 @@ vi.mock("./runtime.js", () => ({
}),
}));
import { whatsappPlugin } from "./channel.js";
let whatsappPlugin: typeof import("./channel.js").whatsappPlugin;
describe("whatsappPlugin outbound sendPoll", () => {
beforeEach(async () => {
vi.resetModules();
({ whatsappPlugin } = await import("./channel.js"));
});
it("threads cfg into runtime sendPollWhatsApp call", async () => {
const { cfg, poll, to, accountId } = createWhatsAppPollFixture();

View File

@@ -96,7 +96,8 @@ vi.mock("./session.js", () => {
};
});
import { monitorWebInbox, resetWebInboundDedupe } from "./inbound.js";
let monitorWebInbox: typeof import("./inbound.js").monitorWebInbox;
let resetWebInboundDedupe: typeof import("./inbound.js").resetWebInboundDedupe;
let createWaSocket: typeof import("./session.js").createWaSocket;
async function waitForMessage(onMessage: ReturnType<typeof vi.fn>) {
@@ -115,12 +116,18 @@ describe("web inbound media saves with extension", () => {
}
beforeEach(() => {
vi.useRealTimers();
vi.resetModules();
saveMediaBufferSpy.mockClear();
});
beforeEach(async () => {
({ monitorWebInbox, resetWebInboundDedupe } = await import("./inbound.js"));
({ createWaSocket } = await import("./session.js"));
resetWebInboundDedupe();
});
beforeAll(async () => {
({ createWaSocket } = await import("./session.js"));
await fs.rm(HOME, { recursive: true, force: true });
});

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
const { normalizeMessageContent, downloadMediaMessage } = vi.hoisted(() => ({
normalizeMessageContent: vi.fn((msg: unknown) => msg),
@@ -10,23 +10,31 @@ vi.mock("@whiskeysockets/baileys", () => ({
downloadMediaMessage,
}));
import { downloadInboundMedia } from "./media.js";
let downloadInboundMedia: typeof import("./media.js").downloadInboundMedia;
const mockSock = {
updateMediaMessage: vi.fn(),
logger: { child: () => ({}) },
} as never;
};
async function expectMimetype(message: Record<string, unknown>, expected: string) {
const result = await downloadInboundMedia({ message } as never, mockSock);
const result = await downloadInboundMedia({ message } as never, mockSock as never);
expect(result).toBeDefined();
expect(result?.mimetype).toBe(expected);
}
describe("downloadInboundMedia", () => {
beforeEach(async () => {
vi.resetModules();
({ downloadInboundMedia } = await import("./media.js"));
normalizeMessageContent.mockClear();
downloadMediaMessage.mockClear();
mockSock.updateMediaMessage.mockClear();
});
it("returns undefined for messages without media", async () => {
const msg = { message: { conversation: "hello" } } as never;
const result = await downloadInboundMedia(msg, mockSock);
const result = await downloadInboundMedia(msg, mockSock as never);
expect(result).toBeUndefined();
});
@@ -59,7 +67,7 @@ describe("downloadInboundMedia", () => {
documentMessage: { mimetype: "application/pdf", fileName: "report.pdf" },
},
} as never;
const result = await downloadInboundMedia(msg, mockSock);
const result = await downloadInboundMedia(msg, mockSock as never);
expect(result).toBeDefined();
expect(result?.mimetype).toBe("application/pdf");
expect(result?.fileName).toBe("report.pdf");

View File

@@ -19,15 +19,17 @@ vi.mock("./session.js", () => {
};
});
import { loginWeb } from "./login.js";
import type { waitForWaConnection } from "./session.js";
const { createWaSocket } = await import("./session.js");
let loginWeb: typeof import("./login.js").loginWeb;
let createWaSocket: typeof import("./session.js").createWaSocket;
describe("web login", () => {
beforeEach(() => {
beforeEach(async () => {
vi.resetModules();
vi.useFakeTimers();
vi.clearAllMocks();
({ loginWeb } = await import("./login.js"));
({ createWaSocket } = await import("./session.js"));
});
afterEach(() => {

View File

@@ -2,19 +2,18 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import sharp from "sharp";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { resolveStateDir } from "../../../src/config/paths.js";
import { resolvePreferredOpenClawTmpDir } from "../../../src/infra/tmp-openclaw-dir.js";
import { optimizeImageToPng } from "../../../src/media/image-ops.js";
import { mockPinnedHostnameResolution } from "../../../src/test-helpers/ssrf.js";
import { captureEnv } from "../../../test/helpers/extensions/env.js";
import { sendVoiceMessageDiscord } from "../../discord/src/send.js";
import {
LocalMediaAccessError,
loadWebMedia,
loadWebMediaRaw,
optimizeImageToJpeg,
} from "./media.js";
let LocalMediaAccessError: typeof import("./media.js").LocalMediaAccessError;
let loadWebMedia: typeof import("./media.js").loadWebMedia;
let loadWebMediaRaw: typeof import("./media.js").loadWebMediaRaw;
let optimizeImageToJpeg: typeof import("./media.js").optimizeImageToJpeg;
const convertHeicToJpegMock = vi.fn();
@@ -78,6 +77,8 @@ function cloneStatWithDev<T extends { dev: number | bigint }>(stat: T, dev: numb
}
beforeAll(async () => {
({ LocalMediaAccessError, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg } =
await import("./media.js"));
fixtureRoot = await fs.mkdtemp(
path.join(resolvePreferredOpenClawTmpDir(), "openclaw-media-test-"),
);
@@ -136,6 +137,12 @@ afterEach(() => {
vi.clearAllMocks();
});
beforeEach(async () => {
vi.resetModules();
({ LocalMediaAccessError, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg } =
await import("./media.js"));
});
describe("web media loading", () => {
beforeAll(() => {
// Ensure state dir is stable and not influenced by other tests that stub OPENCLAW_STATE_DIR.

View File

@@ -158,7 +158,7 @@ describe("web monitor inbox", () => {
() => {
expect(sock.sendMessage).toHaveBeenCalledTimes(1);
},
{ timeout: 2_000, interval: 5 },
{ timeout: 5_000, interval: 5 },
);
expect(onMessage).not.toHaveBeenCalled();
expectPairingPromptSent(sock, "999@s.whatsapp.net", "+999");
@@ -246,7 +246,7 @@ describe("web monitor inbox", () => {
},
]);
},
{ timeout: 2_000, interval: 5 },
{ timeout: 5_000, interval: 5 },
);
// Verify it WAS NOT passed to onMessage

View File

@@ -1,11 +1,5 @@
import crypto from "node:crypto";
import fsSync from "node:fs";
import os from "node:os";
import path from "node:path";
import "./monitor-inbox.test-harness.js";
import { describe, expect, it, vi } from "vitest";
import { setLoggerOverride } from "../../../src/logging.js";
import { monitorWebInbox } from "./inbound.js";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
DEFAULT_ACCOUNT_ID,
getAuthDir,
@@ -13,10 +7,31 @@ import {
installWebMonitorInboxUnitTestHooks,
mockLoadConfig,
} from "./monitor-inbox.test-harness.js";
let monitorWebInbox: typeof import("./inbound.js").monitorWebInbox;
const inboundLoggerInfoMock = vi.hoisted(() => vi.fn());
vi.mock("openclaw/plugin-sdk/text-runtime", async (importOriginal) => {
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/text-runtime")>();
return {
...actual,
getChildLogger: () => ({
info: inboundLoggerInfoMock,
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
}),
};
});
describe("web monitor inbox", () => {
installWebMonitorInboxUnitTestHooks();
beforeEach(async () => {
vi.resetModules();
inboundLoggerInfoMock.mockReset();
({ monitorWebInbox } = await import("./inbound.js"));
});
async function openMonitor(onMessage = vi.fn()) {
return await monitorWebInbox({
verbose: false,
@@ -120,10 +135,7 @@ describe("web monitor inbox", () => {
expect(sock.ws.close).toHaveBeenCalledTimes(1);
});
it("logs inbound bodies to file", async () => {
const logPath = path.join(os.tmpdir(), `openclaw-log-test-${crypto.randomUUID()}.log`);
setLoggerOverride({ level: "trace", file: logPath });
it("logs inbound bodies through the inbound child logger", async () => {
const { listener } = await runSingleUpsertAndCapture({
type: "notify",
messages: [
@@ -136,15 +148,13 @@ describe("web monitor inbox", () => {
],
});
await vi.waitFor(
() => {
expect(fsSync.existsSync(logPath)).toBe(true);
},
{ timeout: 2_000, interval: 5 },
expect(inboundLoggerInfoMock).toHaveBeenCalledWith(
expect.objectContaining({
body: "ping",
from: "+999",
}),
"inbound message",
);
const content = fsSync.readFileSync(logPath, "utf-8");
expect(content).toMatch(/web-inbound/);
expect(content).toMatch(/ping/);
await listener.close();
});

View File

@@ -149,7 +149,8 @@ export async function waitForMessageCalls(onMessage: ReturnType<typeof vi.fn>, c
() => {
expect(onMessage).toHaveBeenCalledTimes(count);
},
{ timeout: 2_000, interval: 5 },
// Channel-suite workers can be saturated under no-isolate CI runs.
{ timeout: 5_000, interval: 5 },
);
}
@@ -215,6 +216,7 @@ export function installWebMonitorInboxUnitTestHooks(opts?: { authDir?: boolean }
const createAuthDir = opts?.authDir ?? true;
beforeEach(async () => {
vi.useRealTimers();
vi.resetModules();
vi.clearAllMocks();
sessionState.sock = createMockSock();

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../../src/config/config.js";
const hoisted = vi.hoisted(() => ({
@@ -15,9 +15,14 @@ vi.mock("./send.js", () => ({
sendReactionWhatsApp: hoisted.sendReactionWhatsApp,
}));
import { whatsappOutbound } from "./outbound-adapter.js";
let whatsappOutbound: typeof import("./outbound-adapter.js").whatsappOutbound;
describe("whatsappOutbound sendPoll", () => {
beforeEach(async () => {
vi.resetModules();
({ whatsappOutbound } = await import("./outbound-adapter.js"));
});
it("threads cfg through poll send options", async () => {
const cfg = { marker: "resolved-cfg" } as OpenClawConfig;
const poll = {

View File

@@ -4,7 +4,7 @@ import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { WebSocketServer } from "ws";
import {
decorateOpenClawProfile,
@@ -112,6 +112,10 @@ describe("browser chrome profile decoration", () => {
fixtureRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "openclaw-chrome-suite-"));
});
beforeEach(() => {
vi.useRealTimers();
});
afterAll(async () => {
if (fixtureRoot) {
await fsp.rm(fixtureRoot, { recursive: true, force: true });
@@ -206,6 +210,10 @@ describe("browser chrome helpers", () => {
return vi.spyOn(fs, "existsSync").mockImplementation((p) => match(String(p)));
}
beforeEach(() => {
vi.useRealTimers();
});
afterEach(() => {
vi.unstubAllEnvs();
vi.unstubAllGlobals();

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
installPwToolsCoreTestHooks,
setPwToolsCoreCurrentPage,
@@ -6,9 +6,14 @@ import {
} from "./pw-tools-core.test-harness.js";
installPwToolsCoreTestHooks();
const mod = await import("./pw-tools-core.js");
let mod: typeof import("./pw-tools-core.js");
describe("pw-tools-core", () => {
beforeEach(async () => {
vi.resetModules();
mod = await import("./pw-tools-core.js");
});
it("clamps timeoutMs for scrollIntoView", async () => {
const scrollIntoViewIfNeeded = vi.fn(async () => {});
setPwToolsCoreCurrentRefLocator({ scrollIntoViewIfNeeded });

View File

@@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
let page: { evaluate: ReturnType<typeof vi.fn> } | null = null;
@@ -34,11 +34,9 @@ vi.mock("./pw-tools-core.snapshot.js", () => ({
let batchViaPlaywright: typeof import("./pw-tools-core.interactions.js").batchViaPlaywright;
describe("batchViaPlaywright", () => {
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
({ batchViaPlaywright } = await import("./pw-tools-core.interactions.js"));
});
beforeEach(() => {
vi.clearAllMocks();
page = {
evaluate: vi.fn(async () => "ok"),

View File

@@ -1,4 +1,4 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
let page: Record<string, unknown> | null = null;
let locator: Record<string, unknown> | null = null;
@@ -54,11 +54,9 @@ function seedSingleLocatorPage(): { setInputFiles: ReturnType<typeof vi.fn> } {
}
describe("setInputFilesViaPlaywright", () => {
beforeAll(async () => {
beforeEach(async () => {
vi.resetModules();
({ setInputFilesViaPlaywright } = await import("./pw-tools-core.interactions.js"));
});
beforeEach(() => {
vi.clearAllMocks();
page = null;
locator = null;

View File

@@ -1,7 +1,7 @@
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_UPLOAD_DIR } from "./paths.js";
import {
installPwToolsCoreTestHooks,
@@ -9,9 +9,14 @@ import {
} from "./pw-tools-core.test-harness.js";
installPwToolsCoreTestHooks();
const mod = await import("./pw-tools-core.js");
let mod: typeof import("./pw-tools-core.js");
describe("pw-tools-core", () => {
beforeEach(async () => {
vi.resetModules();
mod = await import("./pw-tools-core.js");
});
it("last file-chooser arm wins", async () => {
const firstPath = path.join(DEFAULT_UPLOAD_DIR, `vitest-arm-1-${crypto.randomUUID()}.txt`);
const secondPath = path.join(DEFAULT_UPLOAD_DIR, `vitest-arm-2-${crypto.randomUUID()}.txt`);

View File

@@ -1,7 +1,7 @@
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_UPLOAD_DIR } from "./paths.js";
import {
getPwToolsCoreSessionMocks,
@@ -12,7 +12,7 @@ import {
installPwToolsCoreTestHooks();
const sessionMocks = getPwToolsCoreSessionMocks();
const mod = await import("./pw-tools-core.js");
let mod: typeof import("./pw-tools-core.js");
function createFileChooserPageMocks() {
const fileChooser = { setFiles: vi.fn(async () => {}) };
@@ -26,6 +26,11 @@ function createFileChooserPageMocks() {
}
describe("pw-tools-core", () => {
beforeEach(async () => {
vi.resetModules();
mod = await import("./pw-tools-core.js");
});
it("screenshots an element selector", async () => {
const elementScreenshot = vi.fn(async () => Buffer.from("E"));
const page = {

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { resolveTargetIdAfterNavigate } from "./agent.snapshot.js";
type Tab = { targetId: string; url: string };
@@ -8,6 +8,10 @@ function staticListTabs(tabs: Tab[]): () => Promise<Tab[]> {
}
describe("resolveTargetIdAfterNavigate", () => {
beforeEach(() => {
vi.useRealTimers();
});
it("returns original targetId when old target still exists (no swap)", async () => {
const result = await resolveTargetIdAfterNavigate({
oldTargetId: "old-123",

View File

@@ -401,6 +401,7 @@ export async function cleanupBrowserControlServerTestContext(): Promise<void> {
export function installBrowserControlServerHooks() {
beforeEach(async () => {
vi.useRealTimers();
cdpMocks.createTargetViaCdp.mockImplementation(async () => {
if (state.createTargetId) {
return { targetId: state.createTargetId };