mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor(errors): give caller aborts a typed reason at the adapters Squashes 908813551f, 87858de4dc (fix(mcp): preserve cancellation reasons in handlers) and fa151dc3f6 (test(server): race handler-entry gates against the triggering tool call) onto main without #10566's pre-squash commits. * refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters (#10692) * refactor(errors): give owner-initiated aborts a typed reason outside the browser adapters * refactor(verify): keep the abort-reason scan receiver-independent Playwright's route.abort takes a network error code; the one call site now names its default code instead of the guard exempting a variable name. * refactor(browser): give owner-initiated CDP aborts typed reasons (#10706) * refactor(browser): give owner-initiated CDP aborts typed reasons * fix(browser): give the page-side request fetch page-native abort reasons The request callback runs in the page through page.evaluate and cannot reach REA's modules. Define its deadline and cleanup reasons as DOMExceptions inside the callback, and make the test fake evaluate the callback from its source so module imports are unavailable, as in a page. * test(process): expect the reader close's typed owner reason Closing the Darwin run-token reader aborts its compilation with OwnerCancellation.ownerClosed since the owner-abort change in this PR; the boundary test still expected the default DOMException. * fix(server): report HTTP session replacement and shutdown to pending tools as an owner close
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import { createDeferred } from "../../tests/fixtures/binarySession.js";
|
|
import { OwnerCancellation } from "../domain/analysisErrorCore.js";
|
|
import { SharedStartupAttempt } from "./SharedStartupAttempt.js";
|
|
|
|
it("keeps shared startup running for an interested peer and returns the departing caller's reason", async () => {
|
|
const completed = createDeferred<string>();
|
|
const attempt = new SharedStartupAttempt(() => completed.promise);
|
|
const controller = new AbortController();
|
|
const departing = attempt.wait(controller.signal);
|
|
const staying = attempt.wait();
|
|
const reason = { caller: "departing" };
|
|
controller.abort(reason);
|
|
const departed = await departing;
|
|
expect(departed).toEqual({ status: "cancelled", reason });
|
|
expect(departed.status === "cancelled" ? departed.reason : undefined).toBe(
|
|
reason,
|
|
);
|
|
expect(attempt.signal.aborted).toBe(false);
|
|
completed.resolve("ready");
|
|
await expect(staying).resolves.toEqual({
|
|
status: "completed",
|
|
value: "ready",
|
|
});
|
|
});
|
|
|
|
it("retains the final cleanup result when all callers cancel together", async () => {
|
|
const cleanup = createDeferred<{ cleanupIncomplete: boolean }>();
|
|
const attempt = new SharedStartupAttempt(() => cleanup.promise);
|
|
const first = new AbortController();
|
|
const last = new AbortController();
|
|
const waits = [attempt.wait(first.signal), attempt.wait(last.signal)];
|
|
const firstReason = new Error("first caller left");
|
|
first.abort(firstReason);
|
|
last.abort();
|
|
await expect(waits[0]).resolves.toEqual({
|
|
status: "cancelled",
|
|
reason: firstReason,
|
|
});
|
|
expect(attempt.signal.aborted).toBe(true);
|
|
expect(attempt.settled).toBe(false);
|
|
const failure = { cleanupIncomplete: true };
|
|
cleanup.resolve(failure);
|
|
await expect(waits[1]).resolves.toEqual({
|
|
status: "stopped",
|
|
value: failure,
|
|
});
|
|
expect(attempt.settled).toBe(true);
|
|
});
|
|
|
|
it("distinguishes provider closure from a caller's cancellation", async () => {
|
|
const cleanup = createDeferred<string>();
|
|
const attempt = new SharedStartupAttempt(() => cleanup.promise);
|
|
const controller = new AbortController();
|
|
const waiting = attempt.wait(controller.signal);
|
|
attempt.close();
|
|
expect(attempt.signal.reason).toBe(OwnerCancellation.startupOwnerClosed);
|
|
expect(controller.signal.aborted).toBe(false);
|
|
cleanup.resolve("provider stopped");
|
|
await expect(waiting).resolves.toEqual({
|
|
status: "completed",
|
|
value: "provider stopped",
|
|
});
|
|
});
|
|
|
|
it("does not lose an unexpected producer rejection after its final caller cancels", async () => {
|
|
let reject: ((cause: unknown) => void) | undefined;
|
|
const promise = new Promise<never>((_resolve, fail) => {
|
|
reject = fail;
|
|
});
|
|
const attempt = new SharedStartupAttempt(() => promise);
|
|
const controller = new AbortController();
|
|
const waiting = attempt.wait(controller.signal);
|
|
controller.abort();
|
|
const cause = new TypeError("Cleanup rejected");
|
|
reject?.(cause);
|
|
await expect(waiting).rejects.toBe(cause);
|
|
});
|
|
|
|
it.each(["caller", "owner"])(
|
|
"preserves %s interruption when owning cancellation and closure occur together",
|
|
async (first) => {
|
|
const completed = createDeferred<string>();
|
|
const controller = new AbortController();
|
|
const reason = { caller: "operation owner" };
|
|
const attempt = new SharedStartupAttempt(
|
|
() => completed.promise,
|
|
controller.signal,
|
|
);
|
|
const waiting = attempt.wait();
|
|
if (first === "caller") {
|
|
controller.abort(reason);
|
|
attempt.close();
|
|
} else {
|
|
attempt.close();
|
|
expect(attempt.signal.aborted).toBe(true);
|
|
expect(attempt.signal.reason).toBe(OwnerCancellation.startupOwnerClosed);
|
|
controller.abort(reason);
|
|
}
|
|
expect(attempt.signal.reason).toBe(
|
|
first === "caller" ? reason : OwnerCancellation.startupOwnerClosed,
|
|
);
|
|
expect(attempt.settled).toBe(false);
|
|
completed.resolve("finalized");
|
|
await expect(waiting).resolves.toEqual({
|
|
status: "completed",
|
|
value: "finalized",
|
|
});
|
|
},
|
|
);
|