Files
wehub-resource-sync 70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:11 +08:00

122 lines
3.9 KiB
TypeScript

import {
evictAllDurableObjects,
evictDurableObject,
runInDurableObject,
} from "cloudflare:test";
import { env } from "cloudflare:workers";
import { it } from "vitest";
import { Counter } from "../src/";
function getResponseWebSocket(response: Response) {
const socket = response.webSocket;
if (socket === null || socket === undefined) {
throw new TypeError("Expected WebSocket response");
}
return socket;
}
function waitForMessage(socket: WebSocket) {
return new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Timed out waiting for WebSocket message"));
}, 10_000);
socket.addEventListener("message", (event) => {
clearTimeout(timeout);
resolve(
typeof event.data === "string"
? event.data
: new TextDecoder().decode(event.data as ArrayBuffer)
);
});
socket.addEventListener("error", () => {
clearTimeout(timeout);
reject(new Error("WebSocket error while waiting for message"));
});
});
}
function waitForClose(socket: WebSocket) {
return new Promise<CloseEvent>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Timed out waiting for WebSocket close"));
}, 10_000);
socket.addEventListener("close", (event) => {
clearTimeout(timeout);
resolve(event);
});
socket.addEventListener("error", () => {
clearTimeout(timeout);
reject(new Error("WebSocket error while waiting for close"));
});
});
}
it("resets in-memory state but preserves storage on targeted eviction", async ({
expect,
}) => {
const id = env.COUNTER.idFromName(`evict-${crypto.randomUUID()}`);
const stub = env.COUNTER.get(id);
// Persist `count = 2` through the `fetch()` handler
expect(await (await stub.fetch("https://example.com/")).text()).toBe("1");
expect(await (await stub.fetch("https://example.com/")).text()).toBe("2");
// Corrupt in-memory state without persisting it to storage
await runInDurableObject(stub, (instance: Counter) => {
instance.count = 999;
});
await evictDurableObject(stub, { webSockets: "hibernate" });
// After eviction the instance is torn down: the in-memory `999` is discarded
// and `count` is reloaded from storage (`2`), so the next increment yields `3`
expect(await (await stub.fetch("https://example.com/")).text()).toBe("3");
});
it("resets all running instances with bulk eviction", async ({ expect }) => {
const id = env.COUNTER.idFromName(`evict-all-${crypto.randomUUID()}`);
const stub = env.COUNTER.get(id);
expect(await (await stub.fetch("https://example.com/")).text()).toBe("1");
await runInDurableObject(stub, (instance: Counter) => {
instance.count = 999;
});
await evictAllDurableObjects();
expect(await (await stub.fetch("https://example.com/")).text()).toBe("2");
});
it("hibernates WebSockets across eviction", async ({ expect }) => {
const id = env.COUNTER.idFromName(`evict-ws-${crypto.randomUUID()}`);
const stub = env.COUNTER.get(id);
const response = await stub.fetch("https://example.com/websocket-order", {
headers: { Upgrade: "websocket" },
});
const socket = getResponseWebSocket(response);
socket.accept();
await evictDurableObject(stub);
// The WebSocket should be hibernated rather than closed, so messages still
// round-trip after eviction (waking the Durable Object back up)
const messagePromise = waitForMessage(socket);
socket.send("after-eviction");
expect(await messagePromise).toBe("after-eviction");
socket.close(1000, "done");
});
it("closes WebSockets when requested during eviction", async ({ expect }) => {
const id = env.COUNTER.idFromName(`evict-ws-close-${crypto.randomUUID()}`);
const stub = env.COUNTER.get(id);
const response = await stub.fetch("https://example.com/websocket-order", {
headers: { Upgrade: "websocket" },
});
const socket = getResponseWebSocket(response);
socket.accept();
const closePromise = waitForClose(socket);
await evictDurableObject(stub, { webSockets: "close" });
expect(await closePromise).toBeDefined();
});