openclaw / src /proxy-capture /runtime.lifecycle.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
34810d2 verified
Raw
History Blame Contribute Delete
29.2 kB
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
import { registerSecretValueForRedaction } from "../logging/secret-redaction-registry.js";
import { resetSecretRedactionRegistryForTest } from "../logging/secret-redaction-registry.test-support.js";
import { createDeferredCore } from "../shared/deferred.js";
import { closeOpenClawStateDatabaseByPath } from "../state/openclaw-state-db-cache.js";
import { resolveDebugProxySettings, type DebugProxySettings } from "./env.js";
import {
captureHttpExchange,
captureWsEvent,
finalizeDebugProxyCapture,
initializeDebugProxyCapture,
prepareHttpCapture,
type DebugProxyCaptureRuntimeDeps,
} from "./runtime.js";
import {
acquireDebugProxyCaptureStore,
closeDebugProxyCaptureStore,
DebugProxyCaptureStore,
persistEventPayload,
} from "./store.sqlite.js";
import type { CaptureEventRecord } from "./types.js";
const roots: string[] = [];
afterEach(() => {
closeDebugProxyCaptureStore();
vi.restoreAllMocks();
vi.unstubAllEnvs();
resetSecretRedactionRegistryForTest();
for (const root of roots.splice(0)) {
fs.rmSync(root, { recursive: true, force: true });
}
});
function stateRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "capture-lifecycle-"));
roots.push(root);
return root;
}
function captureSettings(root: string, sessionId = "lifecycle"): DebugProxySettings {
return {
enabled: true,
required: false,
dbPath: path.join(root, "capture.sqlite"),
blobDir: path.join(root, "blobs"),
certDir: path.join(root, "certs"),
sessionId,
sourceProcess: "fixture",
};
}
function observePendingCapture(response: Response, prefixChunks: number) {
const pending = createDeferredCore();
const settled = createDeferredCore();
const clone = response.clone.bind(response);
vi.spyOn(response, "clone").mockImplementation(() => {
const captured = clone();
const reader = captured.body!.getReader();
const read = reader.read.bind(reader);
let reads = 0;
vi.spyOn(reader, "read").mockImplementation(() => {
reads += 1;
const last = reads === prefixChunks + 1;
if (last) {
pending.resolve();
}
return read().finally(() => {
if (last) {
settled.resolve();
}
});
});
vi.spyOn(captured.body!, "getReader").mockReturnValue(reader);
return captured;
});
return { pending: pending.promise, settled: settled.promise };
}
function pendingResponse(chunks: Buffer[]) {
let controller: ReadableStreamDefaultController<Uint8Array>;
const response = new Response(
new ReadableStream<Uint8Array>({
start(value) {
controller = value;
for (const chunk of chunks) {
controller.enqueue(chunk);
}
},
}),
{ status: 200, headers: { "content-type": "text/plain" } },
);
return { response, controller: controller!, ...observePendingCapture(response, chunks.length) };
}
describe("capture store lifecycle", () => {
it.each(
(["shared", "legacy"] as const).flatMap((storage) =>
(["direct", "last-lease"] as const).map((close) => ({ storage, close })),
),
)("settles bytes once before $storage $close close", async ({ storage, close }) => {
const root = stateRoot();
const settings = captureSettings(root);
const acquire = () =>
storage === "shared"
? acquireDebugProxyCaptureStore({ env: { OPENCLAW_STATE_DIR: root } })
: acquireDebugProxyCaptureStore(settings.dbPath, settings.blobDir);
const lease = acquire();
const sibling = acquire();
const store = lease.store;
const getStore = vi.fn(() => store);
const deps: DebugProxyCaptureRuntimeDeps = {
getStore,
persistEventPayload: (_store, payload) => persistEventPayload(store, payload),
};
registerSecretValueForRedaction("fixture-secret-value");
const bytes = Buffer.from("first fixture-secret-value caf\u00e9");
const stream = pendingResponse([
bytes.subarray(0, 15),
bytes.subarray(15, -1),
bytes.subarray(-1),
]);
const terminals: CaptureEventRecord[] = [];
const record = store.recordEvent.bind(store);
const recording = vi.spyOn(store, "recordEvent").mockImplementation((event) => {
expect(store.isClosed).toBe(false);
record(event);
if (event.kind !== "request") {
terminals.push(event);
}
});
const end = vi.spyOn(store, "endSession");
try {
store.upsertSession({
id: settings.sessionId,
startedAt: Date.now(),
mode: "fixture",
sourceScope: "openclaw",
sourceProcess: "fixture",
});
captureHttpExchange(
{ url: "https://example.test/pending", method: "GET", response: stream.response },
settings,
deps,
);
await stream.pending;
lease.release();
expect(store.isClosed).toBe(false);
expect(terminals).toHaveLength(0);
if (close === "direct") {
store.close();
} else {
sibling.release();
}
expect(store.isClosed).toBe(true);
expect(terminals).toHaveLength(1);
expect(terminals[0]).toMatchObject({
kind: "response",
status: 200,
dataText: "first [REDACTED] caf\u00e9",
});
expect(JSON.parse(terminals[0]!.metaJson!)).toMatchObject({ bodyCapture: "finalized" });
expect(end).toHaveBeenCalledExactlyOnceWith(settings.sessionId);
stream.controller.enqueue(Buffer.from("-later"));
stream.controller.close();
expect(await stream.response.text()).toBe(`${bytes.toString()}-later`);
await stream.settled;
finalizeDebugProxyCapture(settings, deps);
store.close();
sibling.release();
expect(terminals).toHaveLength(1);
expect(end).toHaveBeenCalledTimes(1);
expect(getStore).toHaveBeenCalledTimes(1);
const reopened = acquire();
try {
expect(reopened.store.listSessions()[0]?.endedAt).toBeTypeOf("number");
expect(reopened.store.getSessionEvents(settings.sessionId)).toHaveLength(2);
expect(reopened.store.readBlob(terminals[0]!.dataBlobId!)).toBe(
"first [REDACTED] caf\u00e9",
);
} finally {
reopened.release();
}
} finally {
recording.mockRestore();
lease.release();
sibling.release();
store.close();
closeOpenClawStateDatabaseByPath(store.dbPath);
}
});
it.each(["eof", "error", "finalize"] as const)(
"records one terminal through a pending-read %s race",
async (mode) => {
const root = stateRoot();
const settings = captureSettings(root);
const store = new DebugProxyCaptureStore({ env: { OPENCLAW_STATE_DIR: root } });
const deps: DebugProxyCaptureRuntimeDeps = { getStore: () => store };
const stream = pendingResponse([Buffer.from("prefix")]);
const terminals: CaptureEventRecord[] = [];
const done = createDeferredCore();
const record = store.recordEvent.bind(store);
vi.spyOn(store, "recordEvent").mockImplementation((event) => {
record(event);
if (event.kind !== "request") {
terminals.push(event);
done.resolve();
}
});
registerSecretValueForRedaction("fixture-error-secret");
const reason = new Error("fixture read failure: fixture-error-secret");
try {
captureHttpExchange(
{ url: "https://example.test/race", method: "GET", response: stream.response },
settings,
deps,
);
await stream.pending;
if (mode === "eof") {
stream.controller.close();
} else if (mode === "error") {
stream.controller.error(reason);
} else {
finalizeDebugProxyCapture(settings, deps);
stream.controller.error(reason);
}
await done.promise;
await stream.settled;
expect(terminals).toHaveLength(1);
expect(terminals[0]).toMatchObject({
kind: mode === "error" ? "error" : "response",
status: 200,
dataText: "prefix",
});
if (mode === "error") {
expect(terminals[0]).toMatchObject({ errorText: "fixture read failure: [REDACTED]" });
expect(JSON.parse(terminals[0]!.metaJson!)).toMatchObject({
bodyCapture: "failed",
stage: "response-body",
});
}
finalizeDebugProxyCapture(settings, deps);
expect(terminals).toHaveLength(1);
} finally {
await stream.response.body?.cancel().catch(() => undefined);
store.close();
closeOpenClawStateDatabaseByPath(store.dbPath);
}
},
);
it.each(["blob", "event"] as const)(
"settles sibling reads and closes after one terminal %s persistence fails",
async (surface) => {
const root = stateRoot();
const settings = captureSettings(root);
const store = new DebugProxyCaptureStore({ env: { OPENCLAW_STATE_DIR: root } });
const deps: DebugProxyCaptureRuntimeDeps = { getStore: () => store };
const streams = [
pendingResponse([Buffer.from("one")]),
pendingResponse([Buffer.from("two")]),
];
const failure = new Error("fixture storage failure");
const attempted: string[] = [];
if (surface === "blob") {
const persist = store.persistPayload.bind(store);
vi.spyOn(store, "persistPayload").mockImplementation((data, contentType) => {
if (data.toString() === "one") {
throw failure;
}
return persist(data, contentType);
});
}
const record = store.recordEvent.bind(store);
vi.spyOn(store, "recordEvent").mockImplementation((event) => {
if (event.kind !== "request") {
attempted.push(event.flowId);
if (surface === "event" && event.flowId === "one") {
throw failure;
}
}
record(event);
});
try {
for (const [index, stream] of streams.entries()) {
captureHttpExchange(
{
url: "https://example.test/sibling",
method: "GET",
response: stream.response,
flowId: index === 0 ? "one" : "two",
},
settings,
deps,
);
}
await Promise.all(streams.map((stream) => stream.pending));
let error: unknown;
try {
finalizeDebugProxyCapture(settings, deps);
} catch (caught) {
error = caught;
}
expect(error).toBeInstanceOf(AggregateError);
expect((error as AggregateError).errors[0].errors).toContain(failure);
expect(store.isClosed).toBe(true);
expect(attempted).toEqual(surface === "blob" ? ["two"] : ["one", "two"]);
finalizeDebugProxyCapture(settings, deps);
expect(attempted).toEqual(surface === "blob" ? ["two"] : ["one", "two"]);
} finally {
for (const stream of streams) {
stream.controller.close();
await stream.response.body?.cancel();
await stream.settled;
}
store.close();
closeOpenClawStateDatabaseByPath(store.dbPath);
}
},
);
it("does not revive a retired database while finalizing its retained owner", async () => {
const root = stateRoot();
const settings = captureSettings(root);
const store = new DebugProxyCaptureStore({ env: { OPENCLAW_STATE_DIR: root } });
const getStore = vi.fn(() => store);
const deps: DebugProxyCaptureRuntimeDeps = { getStore };
const stream = pendingResponse([Buffer.from("prefix")]);
try {
captureHttpExchange(
{ url: "https://example.test/retired", method: "GET", response: stream.response },
settings,
deps,
);
await stream.pending;
closeOpenClawStateDatabaseByPath(store.dbPath);
expect(store.isClosed).toBe(true);
expect(() => finalizeDebugProxyCapture(settings, deps)).toThrow(AggregateError);
expect(() => finalizeDebugProxyCapture(settings, deps)).not.toThrow();
expect(getStore).toHaveBeenCalledTimes(1);
expect(store.db.isOpen).toBe(false);
} finally {
stream.controller.close();
await stream.response.body?.cancel();
await stream.settled;
store.close();
}
});
it.each(
(["shared", "legacy"] as const).flatMap((storage) =>
(["none", "blob", "event"] as const).map((failure) => ({ storage, failure })),
),
)(
"ends the $storage session and reports $failure failure before CLI exit finalization",
({ storage, failure }) => {
const root = stateRoot();
const settings = captureSettings(root);
const runtimeUrl = new URL("./runtime.ts", import.meta.url).href;
const storeUrl = new URL("./store.sqlite.ts", import.meta.url).href;
const redactionUrl = new URL("../logging/secret-redaction-registry.ts", import.meta.url).href;
const script = `
import assert from "node:assert/strict";
import { captureHttpExchange, initializeDebugProxyCapture, finalizeDebugProxyCapture } from ${JSON.stringify(runtimeUrl)};
import { getDebugProxyCaptureStore } from ${JSON.stringify(storeUrl)};
import { registerSecretValueForRedaction } from ${JSON.stringify(redactionUrl)};
const settings = ${JSON.stringify(settings)};
const store = ${storage === "shared" ? "getDebugProxyCaptureStore()" : "getDebugProxyCaptureStore(settings.dbPath, settings.blobDir)"};
const failure = ${JSON.stringify(failure)};
registerSecretValueForRedaction("fixture-storage-secret");
const error = new Error("fixture " + failure + " persistence failure: fixture-storage-secret");
let acquired = 0, terminals = 0, ended = 0, failures = 0;
const persist = store.persistPayload.bind(store);
store.persistPayload = (data, contentType) => {
if (failure === "blob" && data?.toString() === "prefix-one") {
failures++;
throw error;
}
return persist(data, contentType);
};
const record = store.recordEvent.bind(store);
store.recordEvent = event => {
assert.equal(store.isClosed, false);
if (failure === "event" && event.kind !== "request" && event.flowId === "one") {
failures++;
throw error;
}
record(event);
if (event.kind !== "request") terminals++;
};
const end = store.endSession.bind(store);
store.endSession = id => { ended++; end(id); };
const deps = { getStore: () => { acquired++; return store; } };
initializeDebugProxyCapture("fixture", settings, deps);
for (const flowId of ["one", "two"]) {
const response = new Response(new ReadableStream({
start(controller) { controller.enqueue(new TextEncoder().encode("prefix-" + flowId)); }
}));
const clone = response.clone.bind(response);
let pending, reads = 0;
const ready = new Promise(resolve => pending = resolve);
response.clone = () => {
const cloned = clone();
const reader = cloned.body.getReader();
const read = reader.read.bind(reader);
reader.read = () => { if (++reads === 2) pending(); return read(); };
cloned.body.getReader = () => reader;
return cloned;
};
captureHttpExchange({ url: "https://example.test/exit", method: "GET", response, flowId }, settings, deps);
await ready;
}
process.once("exit", () => {
assert.equal(store.isClosed, true);
finalizeDebugProxyCapture(settings, deps);
finalizeDebugProxyCapture(settings, deps);
assert.equal(acquired, 1);
assert.equal(terminals, failure === "none" ? 2 : 1);
assert.equal(ended, 1);
assert.equal(failures, failure === "none" ? 0 : 1);
process.stdout.write(JSON.stringify({ acquired, terminals, ended, failures }));
});
process.exit(0);
`;
const child = spawnSync(
process.execPath,
[
"--disable-warning=ExperimentalWarning",
"--import",
"tsx",
"--input-type=module",
"-e",
script,
],
{
cwd: process.cwd(),
env: { ...process.env, OPENCLAW_STATE_DIR: root },
encoding: "utf8",
timeout: 20_000,
},
);
expect(child.stderr).toBe(
failure === "none"
? ""
: `[proxy-capture] Capture persistence failed: fixture ${failure} persistence failure: [REDACTED]\n`,
);
expect(child.status).toBe(0);
expect(JSON.parse(child.stdout)).toEqual({
acquired: 1,
terminals: failure === "none" ? 2 : 1,
ended: 1,
failures: failure === "none" ? 0 : 1,
});
const reopened =
storage === "shared"
? new DebugProxyCaptureStore({ env: { OPENCLAW_STATE_DIR: root } })
: new DebugProxyCaptureStore(settings.dbPath, settings.blobDir);
try {
expect(reopened.listSessions()[0]?.endedAt).toBeTypeOf("number");
const events = reopened.getSessionEvents(settings.sessionId);
expect(events).toHaveLength(failure === "none" ? 4 : 3);
expect(events[0]).toMatchObject({ kind: "response", dataText: "prefix-two" });
} finally {
reopened.close();
closeOpenClawStateDatabaseByPath(reopened.dbPath);
}
},
);
});
describe("capture admission generation", () => {
it.each([
[0, 1],
[1, 0],
] as const)(
"isolates same-session databases when finalizing %s before %s",
async (first, second) => {
const envs = [stateRoot(), stateRoot()].map((root) => ({
OPENCLAW_STATE_DIR: root,
OPENCLAW_DEBUG_PROXY_ENABLED: "1",
}));
const settings = envs.map((env) => resolveDebugProxySettings(env));
expect(settings[0]!.sessionId).toBe(settings[1]!.sessionId);
expect(settings[0]!.dbPath).not.toBe(settings[1]!.dbPath);
const stores = envs.map((env) => new DebugProxyCaptureStore({ env }));
let selected = stores[0]!;
const getStore = vi.fn(() => selected);
const deps = { getStore };
const recordings = stores.map((store) => vi.spyOn(store, "recordEvent"));
const streams = stores.map((_, index) => pendingResponse([Buffer.from(`database-${index}`)]));
const admissions = settings.map((value, index) => {
selected = stores[index]!;
return prepareHttpCapture(value, deps)!;
});
try {
for (const [index, capture] of admissions.entries()) {
capture({
url: "https://example.test/identity",
method: "POST",
flowId: `database-${index}`,
requestBody: `request-${index}`,
response: streams[index]!.response,
});
}
await Promise.all(streams.map((stream) => stream.pending));
expect(
stores.map((store, index) =>
store.getSessionEvents(settings[index]!.sessionId).map((event) => event.flowId),
),
).toEqual([["database-0"], ["database-1"]]);
expect(getStore).toHaveBeenCalledTimes(2);
getStore.mockImplementation(() => {
throw new Error("Existing-owner lookup or finalization acquired a store.");
});
for (const index of [first, second]) {
const fresh = resolveDebugProxySettings(envs[index]);
captureWsEvent(
{
url: "wss://example.test/identity",
direction: "inbound",
kind: "ws-frame",
flowId: `database-${index}`,
payload: "before-close",
},
fresh,
deps,
);
expect(recordings[index]!.mock.lastCall?.[0].kind).toBe("ws-frame");
finalizeDebugProxyCapture(fresh, deps);
expect(stores[index]!.isClosed).toBe(true);
const terminals = recordings[index]!.mock.calls.map(([event]) => event).filter(
(event) => event.kind === "response",
);
expect(terminals).toHaveLength(1);
expect(terminals[0]).toMatchObject({
flowId: `database-${index}`,
dataText: `database-${index}`,
});
const recorded = recordings[index]!.mock.calls.length;
admissions[index]!({
url: "https://example.test/delayed",
method: "GET",
response: new Response("late"),
});
expect(recordings[index]).toHaveBeenCalledTimes(recorded);
if (index === first) {
expect(stores[second]!.isClosed).toBe(false);
expect(
recordings[second]!.mock.calls.some(([event]) => event.kind === "response"),
).toBe(false);
}
finalizeDebugProxyCapture(resolveDebugProxySettings(envs[index]), deps);
}
expect(getStore).toHaveBeenCalledTimes(2);
} finally {
for (const [index, stream] of streams.entries()) {
finalizeDebugProxyCapture(settings[index], deps);
stream.controller.close();
await stream.response.body?.cancel();
await stream.settled;
stores[index]!.close();
closeOpenClawStateDatabaseByPath(stores[index]!.dbPath);
}
}
},
);
it.each(["explicit", "ambient"] as const)(
"preserves fresh %s lazy sessions without reopening retired admission",
(mode) => {
const root = stateRoot();
let settings = captureSettings(root, "first");
vi.stubEnv("OPENCLAW_STATE_DIR", root);
vi.stubEnv("OPENCLAW_DEBUG_PROXY_ENABLED", "1");
vi.stubEnv("OPENCLAW_DEBUG_PROXY_SESSION_ID", settings.sessionId);
const record = vi.fn();
const getStore = vi.fn(() => ({
upsertSession() {},
endSession() {},
recordEvent: record,
close() {},
}));
const deps = {
getStore,
persistEventPayload: () => ({}),
fetchTarget: { fetch: vi.fn() } as unknown as typeof globalThis,
};
const resolved = () => (mode === "explicit" ? settings : undefined);
const frame = {
url: "wss://example.test/stream",
direction: "inbound" as const,
kind: "ws-frame" as const,
flowId: "fixture",
payload: "frame",
};
const delayed = prepareHttpCapture(resolved(), deps)!;
captureWsEvent(frame, resolved(), deps);
finalizeDebugProxyCapture(resolved(), deps);
captureWsEvent(frame, resolved(), deps);
expect(getStore).toHaveBeenCalledTimes(1);
expect(record).toHaveBeenCalledTimes(1);
settings = captureSettings(root, "second");
vi.stubEnv("OPENCLAW_DEBUG_PROXY_SESSION_ID", settings.sessionId);
captureWsEvent(frame, resolved(), deps);
expect(getStore).toHaveBeenCalledTimes(2);
expect(record).toHaveBeenCalledTimes(2);
delayed({
url: "https://example.test/delayed",
method: "GET",
response: new Response("late"),
});
expect(record).toHaveBeenCalledTimes(2);
finalizeDebugProxyCapture(resolved(), deps);
initializeDebugProxyCapture("replacement", resolved(), deps);
captureWsEvent(frame, resolved(), deps);
expect(getStore).toHaveBeenCalledTimes(3);
expect(record).toHaveBeenCalledTimes(3);
finalizeDebugProxyCapture(resolved(), deps);
},
);
it("releases retired settings, store, and runtime closures while delayed admission stays fenced", () => {
const root = stateRoot();
const runtimeUrl = new URL("./runtime.ts", import.meta.url).href;
const script = `
import assert from "node:assert/strict";
import { initializeDebugProxyCapture, finalizeDebugProxyCapture, prepareHttpCapture } from ${JSON.stringify(runtimeUrl)};
let store, acquired = 0, recorded = 0, complete;
const getStore = () => { acquired++; return store; };
const target = { fetch: () => new Promise(resolve => complete = resolve) };
const control = new WeakRef({});
function retire() {
const settings = ${JSON.stringify(captureSettings(root))};
store = { upsertSession() {}, endSession() {}, recordEvent() { recorded++; }, close() {} };
const payload = { retained: Buffer.alloc(1024) };
const persist = () => { assert.ok(payload.retained); return {}; };
const refs = [new WeakRef(settings), new WeakRef(store), new WeakRef(persist)];
const deps = { getStore, persistEventPayload: persist, fetchTarget: target };
initializeDebugProxyCapture("fixture", settings, deps);
const capture = prepareHttpCapture(settings, deps);
const late = target.fetch("https://example.test/delayed");
finalizeDebugProxyCapture(settings, deps);
store = undefined;
return { refs, capture, late };
}
const retired = retire();
for (let i = 0; i < 30; i++) {
await new Promise(setImmediate);
globalThis.gc();
}
assert.equal(control.deref(), undefined, "GC control must be collected");
const released = retired.refs.map(ref => ref.deref() === undefined);
const response = new Response("late");
complete(response);
assert.equal(await retired.late, response);
retired.capture({ url: "https://example.test/delayed", method: "GET", response });
assert.equal(acquired, 1);
assert.equal(recorded, 0);
process.stdout.write(JSON.stringify(released));
`;
const child = spawnSync(
process.execPath,
[
"--disable-warning=ExperimentalWarning",
"--expose-gc",
"--import",
"tsx",
"--input-type=module",
"-e",
script,
],
{
cwd: process.cwd(),
env: { ...process.env, OPENCLAW_STATE_DIR: root },
encoding: "utf8",
timeout: 20_000,
},
);
expect(child.stderr).toBe("");
expect(child.status).toBe(0);
expect(JSON.parse(child.stdout)).toEqual([true, true, true]);
});
it.each(
(["patched", "guarded"] as const).flatMap((route) =>
(["success", "rejection"] as const).map((outcome) => ({ route, outcome })),
),
)(
"does not attach a delayed $route $outcome to a replacement owner",
async ({ route, outcome }) => {
const root = stateRoot();
const settings = captureSettings(root, `generation-${route}-${outcome}`);
vi.stubEnv("OPENCLAW_STATE_DIR", root);
vi.stubEnv("OPENCLAW_DEBUG_PROXY_ENABLED", "1");
vi.stubEnv("OPENCLAW_DEBUG_PROXY_SESSION_ID", settings.sessionId);
const admitted = createDeferredCore();
const transport = createDeferredCore<Response>();
const fetchImpl = vi.fn(async () => {
admitted.resolve();
return await transport.promise;
});
const target = { ...globalThis, fetch: fetchImpl } as typeof globalThis;
const deps = { fetchTarget: target };
initializeDebugProxyCapture("first", settings, deps);
const firstStore = acquireDebugProxyCaptureStore();
const operation =
route === "patched"
? target.fetch("https://example.test/delayed").then((response) => ({
response,
release: async () => {},
}))
: fetchWithSsrFGuard({ url: "https://example.test/delayed", fetchImpl });
const result = operation.then(
(value) => ({ value, error: undefined }),
(error: unknown) => ({ value: undefined, error }),
);
await admitted.promise;
finalizeDebugProxyCapture(settings, deps);
expect(firstStore.store.isClosed).toBe(true);
initializeDebugProxyCapture("replacement", settings, deps);
const replacement = acquireDebugProxyCaptureStore();
const record = vi.spyOn(replacement.store, "recordEvent");
const response = new Response("late");
const error = new Error("fixture late transport failure");
try {
if (outcome === "success") {
transport.resolve(response);
} else {
transport.reject(error);
}
const completed = await result;
if (outcome === "success") {
expect(completed.error).toBeUndefined();
expect(completed.value?.response).toBe(response);
expect(await completed.value!.response.text()).toBe("late");
await completed.value!.release();
} else {
expect(completed.error).toBe(error);
}
expect(record).not.toHaveBeenCalled();
expect(replacement.store.getSessionEvents(settings.sessionId)).toEqual([]);
} finally {
finalizeDebugProxyCapture(settings, deps);
firstStore.release();
replacement.release();
closeOpenClawStateDatabaseByPath(replacement.store.dbPath);
}
},
);
});