import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; import path from "node:path"; import { expectDefined } from "@openclaw/normalization-core"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { discordPlugin } from "../extensions/discord/api.js"; import { slackPlugin } from "../extensions/slack/api.js"; import { createOperationalRunInstanceRef, prepareAgentRunAdmission, } from "../src/agents/admitted-run-context.js"; import { buildPreparedCliRunContext } from "../src/agents/cli-runner.test-helpers.js"; import { runPlugin, SUCCESS_RESULT } from "../src/agents/cli-runner/execute-plugin.test-support.js"; import { createCliToolTracking } from "../src/agents/cli-runner/execute-tool-tracking.js"; import { buildCliMcpGrantContext } from "../src/agents/cli-runner/mcp-grant-context.js"; import type { RunCliAgentParams } from "../src/agents/cli-runner/types.js"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../src/config/config.js"; import type { OpenClawConfig } from "../src/config/types.openclaw.js"; import { createAgentRuntimeApprovalAuthorityValidator } from "../src/gateway/agent-runtime-identity-token.js"; import { activateMcpLoopbackClientGrantCapture, deactivateMcpLoopbackClientGrantCapture, mintMcpLoopbackClientGrant, resolveMcpLoopbackClientGrant, revokeMcpLoopbackClientGrant, } from "../src/gateway/mcp-grant-store.js"; import { closeMcpLoopbackServer, ensureMcpLoopbackServer } from "../src/gateway/mcp-http.js"; import { beginMcpLoopbackToolCallCapture, clearMcpLoopbackToolCallCapture, getActiveMcpLoopbackRuntime, } from "../src/gateway/mcp-http.loopback-runtime.js"; import { mintMessageActionTurnCapability, revokeMessageActionTurnCapability, } from "../src/gateway/message-action-turn-capability.js"; import { createRequestGatewayMethodRegistry } from "../src/gateway/server-methods.js"; import type { GatewayRequestContext } from "../src/gateway/server-methods/types.js"; import { createPluginRegistry } from "../src/plugins/registry.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../src/plugins/runtime.js"; import { bindGatewayContextResolver, clearGatewayContextResolver, } from "../src/plugins/runtime/gateway-request-scope.js"; import type { PluginRuntime } from "../src/plugins/runtime/types.js"; import { createPluginRecord } from "../src/plugins/status.test-fixtures.js"; import { trackAsyncWork } from "../src/shared/async-work-scope.js"; import type { Deferred } from "../src/shared/deferred.js"; import { closeOpenClawStateDatabaseForTest } from "../src/state/openclaw-state-db.js"; import { createDeferred, withTestTimeout } from "./helpers/promise.js"; // Device inventory is unrelated to the real grant, message tool, and provider boundary. vi.mock("../src/agents/node-exec-availability.js", () => ({ loadNodeExecAvailability: async () => ({ cacheKey: "no-nodes", isAvailable: () => false }), })); const channels = { discord: { current: "100000000000000003", sender: "100000000000000009" }, slack: { current: "C0123456789", sender: "U0123456789" }, }; const discordSibling = "100000000000000004"; const discordDm = "100000000000000005"; const discordGuild = "100000000000000001"; const discordManagementRole = "100000000000000006"; const discordMessage = "100000000000000020"; const discordChannelPath = `/api/v10/channels/${channels.discord.current}`; const react = { action: "react", channel: "discord", messageId: discordMessage, emoji: "✅" }; type McpResponse = { result?: { tools?: Array<{ name: string }>; content?: Array<{ type: string; text?: string }>; isError?: boolean; }; error?: unknown; }; function expectSuccess(response: McpResponse) { expect(response.error).toBeUndefined(); expect(response.result?.isError, JSON.stringify(response)).not.toBe(true); const text = response.result?.content?.find((item) => item.type === "text")?.text; expect(JSON.parse(text ?? "null")).toMatchObject({ ok: true }); } function expectDenied(response: McpResponse, reason: RegExp) { expect(response.error).toBeUndefined(); expect(response.result?.isError).toBe(true); expect(response.result?.content?.map((item) => item.text).join("\n")).toMatch(reason); } describe("CLI message authority integration", () => { const requests: Array<{ method: string; path: string; fields: Record; body: string; }> = []; const cleanupTurns: Array<() => void> = []; const providerWork = new Set>(); const providerErrors: unknown[] = []; let provider: Server; let providerOrigin: string; let mcpOrigin: string; let cfg: OpenClawConfig; let workspaceDir: string; let sequence = 0; let realFetch: typeof fetch; let gatewaySend = false; let heldRequest: { method: "GET" | "POST"; entered: Deferred; release: Deferred } | undefined; async function handleProviderRequest(req: IncomingMessage, res: ServerResponse) { const url = new URL(req.url ?? "/", "http://fixture.invalid"); const chunks: Buffer[] = []; for await (const chunk of req) { chunks.push(Buffer.from(chunk)); } const rawBody = Buffer.concat(chunks).toString(); const fields = Object.fromEntries(new URLSearchParams(rawBody)); requests.push({ method: req.method ?? "", path: url.pathname, fields, body: rawBody }); const gate = heldRequest; if ( gate && req.method === gate.method && url.pathname === `${discordChannelPath}${gate.method === "POST" ? "/messages" : ""}` ) { gate.entered.resolve(); await gate.release.promise; if (heldRequest === gate) { heldRequest = undefined; } } let body: unknown; if (req.method === "GET" && /^\/api\/v10\/channels\/\d+$/.test(url.pathname)) { const channelId = url.pathname.split("/").at(-1); body = channelId === discordDm ? { id: discordDm, type: 1, recipients: [{ id: channels.discord.sender }] } : { id: channelId, type: 0, guild_id: discordGuild, name: "allowed" }; } else if (req.method === "GET" && /\/channels\/\d+\/messages$/.test(url.pathname)) { body = []; } else if (req.method === "POST" && url.pathname === "/api/v10/users/@me/channels") { expect(JSON.parse(rawBody)).toEqual({ recipient_id: channels.discord.sender }); body = { id: discordDm, type: 1, recipients: [{ id: channels.discord.sender }] }; } else if (req.method === "POST" && url.pathname === `${discordChannelPath}/messages`) { body = { id: discordMessage, channel_id: channels.discord.current, content: "accepted message", }; } else if (req.method === "PUT" && /\/reactions\/[^/]+\/@me$/.test(url.pathname)) { res.writeHead(204).end(); return; } else if ( (req.method === "PUT" && url.pathname === `${discordChannelPath}/pins/${discordMessage}`) || (req.method === "DELETE" && url.pathname === `${discordChannelPath}/messages/${discordMessage}`) ) { res.writeHead(204).end(); return; } else if (req.method === "GET" && url.pathname === `/api/v10/guilds/${discordGuild}`) { body = { id: discordGuild, owner_id: "100000000000000008", roles: [ { id: discordGuild, permissions: "0" }, { id: discordManagementRole, permissions: "16" }, // Discord MANAGE_CHANNELS. ], }; } else if ( req.method === "GET" && url.pathname === `/api/v10/guilds/${discordGuild}/members/${channels.discord.sender}` ) { body = { user: { id: channels.discord.sender }, roles: [discordManagementRole] }; } else if (req.method === "PATCH" && url.pathname === discordChannelPath) { body = { id: channels.discord.current, type: 0, guild_id: discordGuild, name: "allowed", topic: "A permitted channel topic", }; } else if (url.pathname === "/api/conversations.info") { body = { ok: true, channel: { id: fields.channel, is_channel: true, name: "allowed" } }; } else if (url.pathname === "/api/conversations.history") { body = { ok: true, messages: [], has_more: false }; } else { throw new Error(`Unexpected provider request: ${req.method} ${url.pathname}`); } res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(body)); } beforeAll(async () => { const isolatedHome = process.env.OPENCLAW_TEST_HOME; if (!isolatedHome) { throw new Error("CLI message integration requires the shared isolated test HOME"); } workspaceDir = path.join(isolatedHome, "workspace"); provider = createServer((req, res) => { const work = handleProviderRequest(req, res).catch((error: unknown) => { providerErrors.push(error); res.destroy(); }); providerWork.add(work); void work.finally(() => providerWork.delete(work)); }); await new Promise((resolve) => { provider.listen(0, "127.0.0.1", resolve); }); const address = provider.address(); if (!address || typeof address === "string") { throw new Error("Expected provider TCP address"); } providerOrigin = `http://127.0.0.1:${address.port}`; realFetch = globalThis.fetch.bind(globalThis); cfg = { agents: { defaults: { workspace: workspaceDir } }, tools: { allow: ["message"] }, channels: { discord: { enabled: true, token: "synthetic-message-provider-token", groupPolicy: "allowlist", guilds: { [discordGuild]: { channels: { "*": { enabled: true } } } }, }, slack: { enabled: true, botToken: "synthetic-message-provider-token", groupPolicy: "open", dm: { groupEnabled: true }, }, }, }; setRuntimeConfigSnapshot(cfg, cfg); await ensureMcpLoopbackServer(0); const runtime = expectDefined(getActiveMcpLoopbackRuntime(), "task-owned MCP runtime"); mcpOrigin = `http://127.0.0.1:${runtime.port}`; }); beforeEach(() => { gatewaySend = false; requests.length = 0; providerErrors.length = 0; vi.stubGlobal("fetch", (input: RequestInfo | URL, init?: RequestInit) => { const url = new URL(input instanceof Request ? input.url : String(input)); if (url.origin === "https://discord.com" && url.pathname.startsWith("/api/v10/")) { return realFetch(new URL(`${url.pathname}${url.search}`, providerOrigin), init); } if (url.origin !== providerOrigin && url.origin !== mcpOrigin) { throw new Error(`Unexpected fixture network destination: ${url.origin}`); } return realFetch(input, init); }); vi.stubEnv("SLACK_API_URL", `${providerOrigin}/api/`); for (const key of [ "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy", ]) { vi.stubEnv(key, undefined); } const owner = createPluginRegistry({ logger: { info() {}, warn() {}, error() {}, debug() {} }, runtime: {} as PluginRuntime, activateGlobalSideEffects: false, }); // Installer provenance has its own coverage; the official adapters and gates are real. for (const plugin of [discordPlugin, slackPlugin]) { const record = createPluginRecord({ id: plugin.id, origin: "global", trustedOfficialInstall: true, }); owner.registry.plugins.push(record); owner.createApi(record, { config: cfg, registrationMode: "full" }).registerChannel({ plugin: { ...plugin, status: undefined, ...(plugin.actions ? { actions: { ...plugin.actions, // Only the accepted-send case selects the supported Gateway-owned dispatch branch. resolveExecutionMode: (params) => gatewaySend && params.action === "send" ? "gateway" : (plugin.actions?.resolveExecutionMode?.(params) ?? "local"), }, } : {}), }, }); } setActivePluginRegistry(owner.registry); setRuntimeConfigSnapshot(cfg, cfg); }); afterEach(async () => { heldRequest?.release.resolve(); heldRequest = undefined; for (const cleanup of cleanupTurns.splice(0)) { cleanup(); } await Promise.all(providerWork); if (providerErrors.length) { throw new AggregateError(providerErrors, "Provider fixture failed"); } }); afterAll(async () => { await closeMcpLoopbackServer(); provider?.closeAllConnections(); if (provider?.listening) { await new Promise((resolve, reject) => { provider.close((error) => (error ? reject(error) : resolve())); }); } closeOpenClawStateDatabaseForTest(); resetPluginRuntimeStateForTest(); clearRuntimeConfigSnapshot(); vi.unstubAllGlobals(); vi.unstubAllEnvs(); }); function holdProviderRequest(method: "GET" | "POST") { const gate = { method, entered: createDeferred(), release: createDeferred() }; heldRequest = gate; return { entered: () => withTestTimeout(gate.entered.promise, 10_000, "Expected provider request was not reached"), release: () => gate.release.resolve(), }; } async function createTurn( channel: keyof typeof channels, options: { splitSession?: boolean; bindCapability?: boolean; gatewaySend?: boolean; discordDm?: boolean; } = {}, ) { gatewaySend = options.gatewaySend === true; const runtime = expectDefined(getActiveMcpLoopbackRuntime(), "active MCP runtime"); const source = new AbortController(); const runId = `cli-message-${channel}-${++sequence}`; const direct = channel === "discord" && options.discordDm === true; const policySessionKey = direct ? `agent:main:discord:default:direct:${channels.discord.sender}` : `agent:main:${channel}:channel:${channels[channel].current}`; const splitSession = options.splitSession || direct; const run = { sessionId: `session-${runId}`, sessionKey: splitSession ? "agent:main:main" : policySessionKey, sessionFile: path.join(workspaceDir, `${runId}.jsonl`), runId, workspaceDir, ...(splitSession ? { runtimePolicySessionKey: policySessionKey } : {}), provider: "claude-cli" as const, model: "test-model", prompt: "Inspect the current conversation.", timeoutMs: 60_000, messageProvider: channel, messageChannel: channel, // Discord CLI ingress receives the user target; the capability keeps the native DM id. currentChannelId: direct ? `user:${channels.discord.sender}` : channels[channel].current, ...(direct ? { chatType: "direct" as const, currentMessageId: discordMessage } : {}), agentAccountId: "default", senderId: channels[channel].sender, senderIsOwner: false, cliToolAvailability: { native: [], openClaw: ["message"] }, } satisfies RunCliAgentParams; const admission = prepareAgentRunAdmission({ cfg, facts: { runId, agentId: "main", ingress: { kind: "system", boundary: "cli-message-test", state: "present" }, }, operationalRunInstance: createOperationalRunInstanceRef(runId), }); const admitted = await admission.admit("gateway", runId); const directToolContext = direct ? expectDefined( discordPlugin.threading?.buildToolContext?.({ cfg, accountId: "default", context: { Channel: "discord", From: `discord:${run.senderId}`, To: run.currentChannelId, ChatType: "direct", NativeChannelId: discordDm, CurrentMessageId: discordMessage, }, hasRepliedRef: undefined, }), "Discord DM threading context", ) : undefined; const capability = mintMessageActionTurnCapability({ agentId: "main", runId, sessionKey: policySessionKey, sourceReplySessionKey: run.sessionKey, sessionId: run.sessionId, requesterAccountId: "default", requesterSenderId: run.senderId, toolContext: { currentChannelProvider: channel, ...(directToolContext ?? { currentChannelId: run.currentChannelId, currentChatType: "channel" as const, }), }, }); const grant = mintMcpLoopbackClientGrant({ context: buildCliMcpGrantContext({ run, config: cfg, requireExplicitMessageTarget: false, agentId: "main", modelProvider: "anthropic", modelId: "test-model", toolsAllow: ["message"], }), runtimeOwnerToken: runtime.ownerToken, admittedRunContext: admitted, messageActionTurnCapability: options.bindCapability === false ? undefined : capability, abortSignal: source.signal, }); const capture = { token: grant.token, runtimeOwnerToken: runtime.ownerToken, captureKey: `capture-${runId}`, }; const gatewayContext = { trackExecution: trackAsyncWork, getRuntimeConfig: () => cfg, dedupe: new Map(), getGatewayMethodRegistry: () => createRequestGatewayMethodRegistry(), validateAgentRuntimeApprovalAuthority: createAgentRuntimeApprovalAuthorityValidator(), } as GatewayRequestContext; bindGatewayContextResolver(admitted, () => gatewayContext); cleanupTurns.push(() => { source.abort(); revokeMcpLoopbackClientGrant(grant.token); revokeMessageActionTurnCapability(capability); clearMcpLoopbackToolCallCapture(capture.captureKey); clearGatewayContextResolver(admitted); admission.close(); }); expect(activateMcpLoopbackClientGrantCapture(capture)).not.toBe(false); beginMcpLoopbackToolCallCapture({ captureKey: capture.captureKey, onToolCallResult() {} }); const rpc = async ( method: string, params?: Record, headers?: Record, ): Promise => { const response = await realFetch(`${mcpOrigin}/mcp`, { method: "POST", headers: { ...headers, authorization: `Bearer ${grant.token}`, "content-type": "application/json", "x-openclaw-cli-capture-key": capture.captureKey, }, body: JSON.stringify({ jsonrpc: "2.0", id: ++sequence, method, ...(params ? { params } : {}), }), signal: AbortSignal.timeout(30_000), }); const payload = (await response.json()) as McpResponse; expect(response.status, JSON.stringify(payload)).toBe(200); return payload; }; // Warm discovery so later calls also exercise the grant-owned tool cache. expect((await rpc("tools/list")).result?.tools?.some((tool) => tool.name === "message")).toBe( true, ); return { capability, source, capture, runParams: { ...run, agentId: "main", config: cfg, admittedRunContext: admitted, abortSignal: source.signal, messageActionTurnCapability: options.bindCapability === false ? undefined : capability, }, isCurrent: () => resolveMcpLoopbackClientGrant(capture)?.isCurrent() === true, call: (args: Record, headers?: Record) => rpc("tools/call", { name: "message", arguments: args }, headers), }; } it.each(["discord", "slack"] as const)( "reads through an official %s CLI grant", async (channel) => { const turn = await createTurn(channel, { splitSession: channel === "discord" }); const target = channel === "discord" ? discordSibling : channels.slack.current; if (channel === "discord") { expect(turn.runParams.sessionKey).not.toBe(turn.runParams.runtimePolicySessionKey); } expectSuccess( await turn.call({ action: "read", channel, target: `channel:${target}`, limit: 1 }), ); expect(requests).toContainEqual( expect.objectContaining( channel === "discord" ? { method: "GET", path: `/api/v10/channels/${target}/messages` } : { method: "POST", path: "/api/conversations.history", fields: expect.objectContaining({ channel: target }), }, ), ); }, ); it("reacts with bare, prefixed, and default channel targets while rejecting another conversation", async () => { const turn = await createTurn("discord"); for (const target of [ `channel:${channels.discord.current}`, channels.discord.current, undefined, ]) { expectSuccess(await turn.call({ ...react, ...(target ? { target } : {}) })); } expect(requests.filter((request) => request.method === "PUT")).toEqual([ expect.objectContaining({ path: `${discordChannelPath}/messages/${discordMessage}/reactions/%E2%9C%85/@me`, }), expect.objectContaining({ path: `${discordChannelPath}/messages/${discordMessage}/reactions/%E2%9C%85/@me`, }), expect.objectContaining({ path: `${discordChannelPath}/messages/${discordMessage}/reactions/%E2%9C%85/@me`, }), ]); const before = requests.length; expectDenied( await turn.call( { ...react, target: `channel:${discordSibling}` }, { "x-openclaw-current-channel-id": discordSibling }, ), /exact current conversation and account/, ); expect(requests).toHaveLength(before); }); it.each([ { selection: "a prefixed native target", target: `channel:${discordDm}` }, { selection: "a bare native target", target: discordDm }, { selection: "an implicit target", target: undefined }, ])("reacts in a Discord DM with $selection and an implicit account", async ({ target }) => { const turn = await createTurn("discord", { discordDm: true }); expectSuccess(await turn.call({ ...react, ...(target ? { target } : {}) })); expect(requests.filter((request) => request.method === "PUT")).toEqual([ expect.objectContaining({ path: `/api/v10/channels/${discordDm}/messages/${discordMessage}/reactions/%E2%9C%85/@me`, }), ]); }); it("pins the current Discord message without an explicit target", async () => { const turn = await createTurn("discord"); expectSuccess( await turn.call({ action: "pin", channel: "discord", messageId: discordMessage }), ); expect(requests.filter((request) => request.method === "PUT")).toEqual([ expect.objectContaining({ path: `${discordChannelPath}/pins/${discordMessage}` }), ]); }); it("deletes an explicit message in the current Discord channel", async () => { const turn = await createTurn("discord"); expectSuccess( await turn.call({ action: "delete", channel: "discord", target: `channel:${channels.discord.current}`, messageId: discordMessage, }), ); expect(requests.filter((request) => request.method === "DELETE")).toEqual([ expect.objectContaining({ path: `${discordChannelPath}/messages/${discordMessage}` }), ]); }); it("edits the current Discord channel with the admitted sender's permission", async () => { const turn = await createTurn("discord"); expectSuccess( await turn.call({ action: "channel-edit", channel: "discord", target: `channel:${channels.discord.current}`, topic: "A permitted channel topic", }), ); expect(requests).toContainEqual( expect.objectContaining({ method: "GET", path: `/api/v10/guilds/${discordGuild}/members/${channels.discord.sender}`, }), ); const edits = requests.filter((request) => request.method === "PATCH"); expect(edits).toHaveLength(1); expect(edits[0]?.path).toBe(discordChannelPath); expect(JSON.parse(edits[0]?.body ?? "null")).toEqual({ topic: "A permitted channel topic" }); }); it("does not accept a private capability supplied through child arguments or headers", async () => { const turn = await createTurn("discord", { bindCapability: false }); expect(turn.isCurrent()).toBe(true); expectDenied( await turn.call( { action: "read", channel: "discord", target: `channel:${channels.discord.current}`, messageActionTurnCapability: turn.capability, conversationReadOrigin: "direct-operator", }, { "x-openclaw-message-action-turn-capability": turn.capability }, ), /current provider and account context/, ); expect(requests).toEqual([]); }); it("rejects a revoked capability after warming the MCP tool cache", async () => { const turn = await createTurn("discord"); revokeMessageActionTurnCapability(turn.capability); expect(turn.isCurrent()).toBe(true); expectDenied(await turn.call(react), /turn capability.*no longer active/); expect(requests).toEqual([]); }); it("cancels a provider lookup before a reaction can be written", async () => { const turn = await createTurn("discord"); const lookup = holdProviderRequest("GET"); const response = turn.call({ ...react, target: `channel:${channels.discord.current}` }); try { await lookup.entered(); turn.source.abort(); } finally { lookup.release(); } expectDenied(await response, /abort|cancel|no longer active/i); expect(requests).toContainEqual( expect.objectContaining({ method: "GET", path: discordChannelPath }), ); expect(requests.every((request) => request.method === "GET")).toBe(true); }); it("retains an accepted Gateway send result after normal CLI completion", async () => { const turn = await createTurn("discord", { gatewaySend: true, bindCapability: false }); const heldSend = holdProviderRequest("POST"); const context = buildPreparedCliRunContext({ ...turn.runParams, backend: { command: "/bin/sh", args: [] }, }); context.params = turn.runParams; context.preparedBackend.mcpClientGrantCapture = { transportToken: turn.capture.token, adoptProcessToken: vi.fn(), revokeProcessToken: vi.fn(), activate: (captureKey, assertCurrent) => { activateMcpLoopbackClientGrantCapture({ ...turn.capture, captureKey, assertCurrent }); }, deactivate: (captureKey) => { deactivateMcpLoopbackClientGrantCapture({ ...turn.capture, captureKey }); }, }; const tracking = createCliToolTracking(context); const toolCallId = "accepted-gateway-send"; const args = { action: "send", channel: "discord", target: `channel:${channels.discord.current}`, message: "Preserve the accepted message.", }; let pendingSend: ReturnType | undefined; let nativeSignal: AbortSignal | undefined; let drain: Promise | undefined; const recordRunError = vi.fn(); try { await expect( runPlugin( context, async function* (execution) { nativeSignal = execution.abortSignal; tracking.handleCliToolUseStart({ toolCallId, name: "mcp__openclaw__message", kind: "mcp_tool_use", args, }); pendingSend = turn.call(args); void pendingSend.catch(() => undefined); await heldSend.entered(); yield { ...SUCCESS_RESULT, session_id: turn.runParams.sessionId }; }, { sessionId: turn.runParams.sessionId, activeToolCount: () => (pendingSend ? 1 : 0), mcpCapture: { captureKey: turn.capture.captureKey, beginCapture: tracking.beginGatewayCapture, }, }, ), ).resolves.toMatchObject({ reason: "exit", exitCode: 0 }); expect(nativeSignal?.aborted).toBe(true); drain = tracking.finishDeliveryTracking({ useManagedClaudeLiveSession: false, recordRunError, }); heldSend.release(); const response = await expectDefined(pendingSend, "accepted MCP message request"); await drain; expect(response).toMatchObject({ result: { isError: false } }); expect(JSON.stringify(response)).toContain(discordMessage); expect(tracking.resolveCliLoopbackTerminalOutcome(toolCallId)).toEqual({ outcome: "completed", }); expect(tracking.withExecutionEvidence({ text: "completed" }).didSendViaMessagingTool).toBe( true, ); expect(recordRunError).not.toHaveBeenCalled(); expect(requests.filter((request) => request.method === "POST")).toHaveLength(1); } finally { heldSend.release(); await pendingSend?.catch(() => undefined); await drain; tracking.finalizeCapture(() => {}); } expect(turn.isCurrent()).toBe(false); }); });