test(whatsapp): simplify inbound-drain harness internals (#131158)

Replace the drain waiter array + release loop with one lazily created
shared idle deferred (same semantics: concurrent waiters share it, it
resolves on the first all-trackers-zero publish), and fold the tracker
update into publishInboundPendingWork.

Collapse expectInboxPairingReplyText into expectPairingPromptSent: the
mocked pairing upsert always issues PAIRCODE, so the regex code
extraction and code-override parameter were dead generality. Net -20
lines of test-support code, no behavior change.
This commit is contained in:
Peter Steinberger
2026-08-27 13:36:57 -07:00
committed by GitHub
parent d95a1dbcf6
commit 97f66133c5
@@ -257,25 +257,6 @@ export type InboxMonitorOptions = Parameters<MonitorWebInbox>[0];
let monitorWebInbox: MonitorWebInbox;
let resetWebInboundDedupe: ResetWebInboundDedupe;
function expectInboxPairingReplyText(
text: string,
params: {
channel: string;
idLine: string;
code?: string;
},
): string {
const code = text.match(/Pairing code:\s*```[\r\n]+([A-Z2-9]{6,})/)?.[1];
expect(code).toBeDefined();
const resolvedCode = params.code ?? code ?? "";
expect(text).toContain("OpenClaw: access not configured.");
expect(text).toContain(params.idLine);
expect(text).toContain("Pairing code:");
expect(text).toContain(`\n\`\`\`\n${resolvedCode}\n\`\`\`\n`);
expect(text).toContain(`pairing approve ${params.channel} ${resolvedCode}`);
return resolvedCode;
}
// Yields two macrotask ticks so already-scheduled inbound continuations run.
// This deliberately does NOT wait for pending inbound work to finish — tests
// observing intermediate states (held handlers, parked debounce batches) rely
@@ -291,23 +272,7 @@ export async function settleInboundWork() {
type InboundWorkTracker = { pending: number };
const inboundWorkTrackers = new Set<InboundWorkTracker>();
let inboundDrainWaiters: Array<() => void> = [];
function releaseInboundDrainWaitersIfIdle() {
if (inboundDrainWaiters.length === 0) {
return;
}
for (const tracker of inboundWorkTrackers) {
if (tracker.pending > 0) {
return;
}
}
const waiters = inboundDrainWaiters;
inboundDrainWaiters = [];
for (const release of waiters) {
release();
}
}
let inboundIdle: { promise: Promise<void>; release: () => void } | undefined;
function hasPendingInboundWork(): boolean {
for (const tracker of inboundWorkTrackers) {
@@ -318,6 +283,14 @@ function hasPendingInboundWork(): boolean {
return false;
}
function publishInboundPendingWork(tracker: InboundWorkTracker, pending: number) {
tracker.pending = pending;
if (inboundIdle && !hasPendingInboundWork()) {
inboundIdle.release();
inboundIdle = undefined;
}
}
// Event-driven drain: resolves when every harness-started listener reports zero
// pending inbound work. Unlike a vi.waitFor deadline, this cannot fail spuriously
// when a saturated no-isolate worker stalls mid-flow (sync module fetches against
@@ -329,9 +302,14 @@ export async function waitForInboundWorkDrained() {
throw new Error("waitForInboundWorkDrained requires a listener started via startInboxMonitor");
}
if (hasPendingInboundWork()) {
await new Promise<void>((resolve) => {
inboundDrainWaiters.push(resolve);
});
if (!inboundIdle) {
let release!: () => void;
const promise = new Promise<void>((resolve) => {
release = resolve;
});
inboundIdle = { promise, release };
}
await inboundIdle.promise;
}
// One extra tick lets drained-callback continuations (delivery bookkeeping) run.
await new Promise((resolve) => {
@@ -377,8 +355,7 @@ export async function startInboxMonitor(
const listener = await monitorWebInbox({
...merged,
onPendingWorkChanged: (pendingWorkCount: number, at?: number) => {
tracker.pending = pendingWorkCount;
releaseInboundDrainWaitersIfIdle();
publishInboundPendingWork(tracker, pendingWorkCount);
callerOnPendingWorkChanged?.(pendingWorkCount, at);
},
});
@@ -411,15 +388,18 @@ export function buildNotifyMessageUpsert(params: {
};
}
// The mocked pairing upsert always issues PAIRCODE, so the reply text can be
// asserted directly instead of re-extracting the code from the message.
function expectPairingPromptSent(sock: MockSock, jid: string, senderE164: string) {
expect(sock.sendMessage).toHaveBeenCalledTimes(1);
const sendCall = sock.sendMessage.mock.calls.at(0);
expect(sendCall?.[0]).toBe(jid);
expectInboxPairingReplyText((sendCall?.[1] as { text?: string } | undefined)?.text ?? "", {
channel: "whatsapp",
idLine: `Your WhatsApp phone number: ${senderE164}`,
code: "PAIRCODE",
});
const text = (sendCall?.[1] as { text?: string } | undefined)?.text ?? "";
expect(text).toContain("OpenClaw: access not configured.");
expect(text).toContain(`Your WhatsApp phone number: ${senderE164}`);
expect(text).toContain("Pairing code:");
expect(text).toContain("\n```\nPAIRCODE\n```\n");
expect(text).toContain("pairing approve whatsapp PAIRCODE");
}
// The pairing reply is sent before the inbound handler completes, so a full
@@ -446,7 +426,7 @@ export function installWebMonitorInboxUnitTestHooks(opts?: { authDir?: boolean }
vi.useRealTimers();
vi.clearAllMocks();
inboundWorkTrackers.clear();
inboundDrainWaiters = [];
inboundIdle = undefined;
channelActivityMocks.recordChannelActivity.mockClear();
pluginRuntimeMocks.reset();
setWhatsAppRuntime({