Files
tech-log-frontend/tests/unit/realtime/websocket-connection.test.ts
T

1177 lines
32 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { ClockPort } from "../../../src/application/ports/clock-port.ts";
import {
realtimeTransportRecoveryCommitted,
type RealtimeRecoveryCheckpoint,
} from "../../../src/application/ports/realtime/event-authority.ts";
import type {
RealtimeResult,
} from "../../../src/application/ports/realtime/shared.ts";
import {
realtimeFailure,
realtimeSuccess,
} from "../../../src/adapters/realtime/result.ts";
import {
createWebSocketConnection,
type WebSocketConnectionDependencies,
type WebSocketFacade,
type WebSocketInboundEventOutcome,
type WebSocketRecoveryRequest,
} from "../../../src/adapters/realtime/websocket/websocket-connection.ts";
import {
REALTIME_WEBSOCKET_PROTOCOL,
type WebSocketAdvertisedLimits,
} from "../../../src/adapters/realtime/websocket/websocket-protocol.ts";
import type {
StreamRegistrationId,
} from "../../../src/contracts/realtime-streams.ts";
const LIMITS: WebSocketAdvertisedLimits = Object.freeze({
maxFrameBytes: 64 * 1_024,
maxSubscriptions: 32,
maxInboundQueueCount: 256,
maxInboundQueueBytes: 4 * 1_024 * 1_024,
maxOutboundQueueCount: 128,
maxOutboundQueueBytes: 256 * 1_024,
maxBufferedAmountBytes: 256 * 1_024,
maxEventsPerSecond: 256,
});
const RECOVERY_CHECKPOINT = Object.freeze({
recoveryMode: "SESSION_REBUILD" as const,
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "0",
resumeCursor: null,
}) as RealtimeRecoveryCheckpoint;
const RECOVERY_OUTCOME = realtimeTransportRecoveryCommitted(
"presence.v1" as StreamRegistrationId,
RECOVERY_CHECKPOINT,
);
type PendingSleep = {
readonly milliseconds: number;
readonly resolve: () => void;
readonly reject: (reason?: unknown) => void;
readonly signal?: AbortSignal;
readonly abort: () => void;
settled: boolean;
};
class ManualClock implements ClockPort {
current = 1_000;
readonly sleeps: PendingSleep[] = [];
now(): number {
return this.current;
}
sleep(
milliseconds: number,
signal?: AbortSignal,
): Promise<void> {
return new Promise((resolve, reject) => {
const pending: PendingSleep = {
milliseconds,
resolve: () => {
if (pending.settled) return;
pending.settled = true;
signal?.removeEventListener("abort", pending.abort);
this.current += milliseconds;
resolve();
},
reject,
signal,
abort: () => {
if (pending.settled) return;
pending.settled = true;
reject(new DOMException("aborted", "AbortError"));
},
settled: false,
};
if (signal?.aborted) {
pending.abort();
return;
}
signal?.addEventListener("abort", pending.abort, { once: true });
this.sleeps.push(pending);
});
}
resolveDuration(milliseconds: number): void {
const pending = this.sleeps.find(
(sleep) =>
!sleep.settled && sleep.milliseconds === milliseconds,
);
if (!pending) {
throw new Error(`No pending ${milliseconds}ms sleep.`);
}
pending.resolve();
}
}
class FakeSocket implements WebSocketFacade {
protocol: string = REALTIME_WEBSOCKET_PROTOCOL;
readyState = 0;
bufferedAmount = 0;
readonly sent: string[] = [];
readonly closes: Array<Readonly<{ code?: number; reason?: string }>> = [];
readonly listeners = new Map<
string,
Set<(event: unknown) => void>
>();
send(data: string): void {
if (this.readyState !== 1) throw new Error("not open");
this.sent.push(data);
}
close(code?: number, reason?: string): void {
this.closes.push({ code, reason });
this.readyState = 3;
}
addEventListener(
type: "open" | "message" | "close" | "error",
listener: (event: unknown) => void,
): void {
const listeners = this.listeners.get(type) ?? new Set();
listeners.add(listener);
this.listeners.set(type, listeners);
}
removeEventListener(
type: "open" | "message" | "close" | "error",
listener: (event: unknown) => void,
): void {
this.listeners.get(type)?.delete(listener);
}
open(): void {
this.readyState = 1;
this.emit("open", {});
}
message(value: unknown): void {
this.emit("message", { data: value });
}
nativeClose(code: number, reason = "private provider text"): void {
this.readyState = 3;
this.emit("close", { code, reason });
}
private emit(type: string, event: unknown): void {
for (const listener of [...(this.listeners.get(type) ?? [])]) {
listener(event);
}
}
}
function welcome(
overrides: Partial<{
heartbeatMs: number;
heartbeatAckTimeoutMs: number;
limits: WebSocketAdvertisedLimits;
}> = {},
): string {
return JSON.stringify({
type: "WELCOME",
protocol: REALTIME_WEBSOCKET_PROTOCOL,
connectionId: "connection.0001",
heartbeatMs: 5_000,
heartbeatAckTimeoutMs: 1_000,
limits: LIMITS,
...overrides,
});
}
function serverFrame(value: Record<string, unknown>): string {
return JSON.stringify({
protocol: REALTIME_WEBSOCKET_PROTOCOL,
...value,
});
}
function setup(
overrides: Partial<WebSocketConnectionDependencies> = {},
) {
const socket = new FakeSocket();
const clock = new ManualClock();
const recoveries: WebSocketRecoveryRequest[] = [];
const observations: unknown[] = [];
const createSocket = vi.fn(() => socket);
const onEvent = vi.fn(async () =>
realtimeSuccess(
Object.freeze({ kind: "CONTINUE" as const }),
),
);
const connection = createWebSocketConnection({
origin: "https://app.example.test",
endpointPath: "/_realtime/v1",
clock,
createNonce: () => "nonce.0001",
createSocket,
onEvent,
onRecoveryRequired: (request) => recoveries.push(request),
observe: (observation) => observations.push(observation),
...overrides,
});
return {
connection,
socket,
clock,
recoveries,
observations,
createSocket,
onEvent,
};
}
async function openConnection(
runtime: ReturnType<typeof setup>,
): Promise<void> {
const opened = runtime.connection.connect();
runtime.socket.open();
runtime.socket.message(welcome());
await expect(opened).resolves.toMatchObject({
ok: true,
value: { protocol: REALTIME_WEBSOCKET_PROTOCOL },
});
}
async function flush(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
}
describe("WebSocket connection generation", () => {
it("uses only a fixed same-origin wss endpoint and exact subprotocol", async () => {
const runtime = setup();
await openConnection(runtime);
expect(runtime.createSocket).toHaveBeenCalledWith(
"wss://app.example.test/_realtime/v1",
REALTIME_WEBSOCKET_PROTOCOL,
);
expect(runtime.connection.inspect()).toMatchObject({
status: "OPEN",
subscriptionCount: 0,
});
const closed = runtime.connection.waitClosed();
runtime.connection.close();
runtime.connection.close();
await expect(closed).resolves.toEqual({
category: "NORMAL",
error: {
kind: "CLOSED",
operation: "CLOSE",
retryable: false,
},
recovery: null,
});
expect(runtime.socket.closes).toEqual([
{ code: 1_000, reason: undefined },
]);
await expect(runtime.connection.connect()).resolves.toMatchObject({
ok: false,
error: { kind: "CLOSED", operation: "CONNECT" },
});
});
it("rejects configuration that weakens hard timing ceilings", () => {
expect(() =>
setup({ ceilings: { connectTimeoutMs: 30_001 } }),
).toThrow("WebSocket ceiling is invalid");
expect(() =>
setup({ ceilings: { minHeartbeatMs: 4_999 } }),
).toThrow("WebSocket ceiling is invalid");
expect(() =>
setup({ ceilings: { maxApplyMs: 30_001 } }),
).toThrow("WebSocket ceiling is invalid");
});
it("serializes subscribe and unsubscribe through one outbound queue", async () => {
const runtime = setup({
ceilings: { maxOutboundQueueCount: 2 },
});
await openConnection(runtime);
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.queued",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toMatchObject({ ok: true });
expect(
runtime.connection.unsubscribe("subscription.queued"),
).toMatchObject({ ok: true });
expect(runtime.connection.inspect()).toMatchObject({
status: "OPEN",
outboundQueueCount: 2,
});
expect(runtime.socket.sent).toEqual([]);
await flush();
expect(
runtime.socket.sent.map(
(frame) =>
(JSON.parse(frame) as Readonly<{ type: string }>).type,
),
).toEqual(["SUBSCRIBE", "UNSUBSCRIBE"]);
expect(runtime.connection.inspect()).toMatchObject({
status: "OPEN",
outboundQueueCount: 0,
outboundQueueBytes: 0,
});
runtime.connection.close();
});
it("fails closed when the negotiated outbound message queue overflows", async () => {
const runtime = setup({
ceilings: { maxOutboundQueueCount: 2 },
});
await openConnection(runtime);
const closed = runtime.connection.waitClosed();
for (const suffix of ["one", "two"]) {
expect(
runtime.connection.subscribe({
subscriptionId: `subscription.${suffix}`,
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toMatchObject({ ok: true });
}
expect(runtime.connection.inspect().outboundQueueCount).toBe(2);
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.overflow",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toEqual({
ok: false,
error: {
kind: "QUEUE_OVERFLOW",
operation: "SUBSCRIBE",
retryable: false,
},
});
await expect(closed).resolves.toEqual({
category: "OVERLOADED",
error: {
kind: "QUEUE_OVERFLOW",
operation: "SUBSCRIBE",
retryable: false,
},
recovery: null,
});
expect(runtime.recoveries).toEqual([
{
subscriptionId: null,
reason: "QUEUE_OVERFLOW",
},
]);
expect(runtime.connection.inspect()).toMatchObject({
status: "CLOSED",
subscriptionCount: 0,
outboundQueueCount: 0,
outboundQueueBytes: 0,
});
await flush();
expect(runtime.socket.sent).toEqual([]);
});
it("rejects a wrong negotiated subprotocol before accepting WELCOME", async () => {
const runtime = setup();
runtime.socket.protocol = "";
const opened = runtime.connection.connect();
runtime.socket.open();
await expect(opened).resolves.toEqual({
ok: false,
error: {
kind: "PROTOCOL_MISMATCH",
operation: "CONNECT",
retryable: false,
},
});
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(runtime.recoveries).toEqual([
{
subscriptionId: null,
reason: "PROTOCOL_MISMATCH",
},
]);
});
it("requires a checkpoint and rejects silent resume advancement", async () => {
const runtime = setup();
await openConnection(runtime);
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.no-checkpoint",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: true,
}),
).toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: true,
checkpoint: {
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "10",
cursor: "cursor.0010",
},
}),
).toEqual({
ok: true,
value: { acceptedLocally: true },
});
await flush();
expect(JSON.parse(runtime.socket.sent.at(-1) ?? "")).toMatchObject({
type: "SUBSCRIBE",
cursor: "cursor.0010",
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0011",
nextExpectedSequence: "12",
}),
);
await flush();
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(runtime.recoveries).toContainEqual({
subscriptionId: "subscription.0001",
reason: "SEQUENCE_GAP",
});
expect(runtime.onEvent).not.toHaveBeenCalled();
});
it("holds queued events behind the validated SUBSCRIBED barrier gate", async () => {
let release:
| ((result: RealtimeResult<void>) => void)
| undefined;
const barrier = new Promise<RealtimeResult<void>>((resolve) => {
release = resolve;
});
const onSubscribed = vi.fn(() => barrier);
const runtime = setup({ onSubscribed });
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: true,
checkpoint: {
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "10",
cursor: "cursor.0010",
},
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0010",
nextExpectedSequence: "11",
}),
);
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "orders.v1",
},
}),
);
await vi.waitFor(() =>
expect(onSubscribed).toHaveBeenCalledOnce(),
);
expect(onSubscribed).toHaveBeenCalledWith(
{
subscriptionId: "subscription.0001",
streamId: "orders.v1",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0010",
nextExpectedSequence: "11",
},
expect.any(AbortSignal),
);
expect(runtime.onEvent).not.toHaveBeenCalled();
release?.(realtimeSuccess(undefined));
await vi.waitFor(() =>
expect(runtime.onEvent).toHaveBeenCalledOnce(),
);
});
it("propagates an exact SUBSCRIBED barrier failure before event effects", async () => {
const barrierFailure = realtimeFailure(
"SCOPE_FENCED",
"RECOVER",
false,
);
const runtime = setup({
onSubscribed: () => barrierFailure,
});
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: true,
checkpoint: {
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "10",
cursor: "cursor.0010",
},
});
const closed = runtime.connection.waitClosed();
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0010",
nextExpectedSequence: "11",
}),
);
const receipt = await closed;
expect(receipt.category).toBe("CURSOR_RESET");
expect(receipt.error).toBe(barrierFailure.error);
expect(receipt.recovery).toBeNull();
expect(runtime.onEvent).not.toHaveBeenCalled();
expect(runtime.recoveries).toContainEqual({
subscriptionId: "subscription.0001",
reason: "SCOPE_CHANGED",
});
});
it("serializes active subscription events and closes on bounded queue overflow", async () => {
let releaseFirst: (() => void) | undefined;
const firstEffect = new Promise<
RealtimeResult<WebSocketInboundEventOutcome>
>((resolve) => {
releaseFirst = () =>
resolve(
realtimeSuccess(
Object.freeze({ kind: "CONTINUE" }),
),
);
});
const onEvent = vi
.fn<
(
frame: unknown,
signal: AbortSignal,
) => Promise<
RealtimeResult<WebSocketInboundEventOutcome>
>
>()
.mockImplementationOnce(() => firstEffect)
.mockResolvedValue(
realtimeSuccess(Object.freeze({ kind: "CONTINUE" })),
);
const runtime = setup({
onEvent,
ceilings: { maxInboundQueueCount: 1 },
});
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: true,
checkpoint: {
streamEpoch: "stream-epoch.0001",
lastAppliedSequence: "10",
cursor: "cursor.0010",
},
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: "cursor.0010",
nextExpectedSequence: "11",
}),
);
await flush();
const event = serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "orders.v1",
},
});
runtime.socket.message(event);
runtime.socket.message(event);
runtime.socket.message(event);
await flush();
expect(onEvent).toHaveBeenCalledTimes(1);
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(runtime.recoveries).toContainEqual({
subscriptionId: null,
reason: "QUEUE_OVERFLOW",
});
releaseFirst?.();
});
it("rejects an event routed through the wrong logical subscription", async () => {
const runtime = setup();
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "orders.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "another-stream.v1",
},
}),
);
await flush();
expect(runtime.onEvent).not.toHaveBeenCalled();
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(runtime.recoveries).toContainEqual({
subscriptionId: null,
reason: "PROTOCOL_MISMATCH",
});
});
it("keeps an unsubscribe tombstone until ACK, ignores late frames, and then permits ID reuse", async () => {
const runtime = setup();
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
expect(
runtime.connection.unsubscribe("subscription.0001"),
).toMatchObject({ ok: true });
expect(runtime.connection.inspect().subscriptionCount).toBe(0);
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "replacement.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toMatchObject({
ok: false,
error: { kind: "PROTOCOL_MISMATCH" },
});
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "presence.v1",
},
}),
);
runtime.socket.message(
serverFrame({
type: "RESET_REQUIRED",
subscriptionId: "subscription.0001",
reason: "SERVER_RESET",
}),
);
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "late-stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
expect(runtime.connection.inspect().status).toBe("OPEN");
expect(runtime.onEvent).not.toHaveBeenCalled();
expect(runtime.recoveries).toEqual([]);
runtime.socket.message(
serverFrame({
type: "UNSUBSCRIBED",
subscriptionId: "subscription.0001",
}),
);
await flush();
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "replacement.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toEqual({
ok: true,
value: { acceptedLocally: true },
});
runtime.connection.close();
});
it("reclaims subscription quota after 32 subscribe-unsubscribe acknowledgements", async () => {
const runtime = setup();
await openConnection(runtime);
for (let index = 0; index < 32; index += 1) {
const subscriptionId = `subscription.${String(index).padStart(4, "0")}`;
expect(
runtime.connection.subscribe({
subscriptionId,
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toMatchObject({ ok: true });
expect(
runtime.connection.unsubscribe(subscriptionId),
).toMatchObject({ ok: true });
runtime.socket.message(
serverFrame({
type: "UNSUBSCRIBED",
subscriptionId,
}),
);
await flush();
}
expect(runtime.connection.inspect()).toMatchObject({
status: "OPEN",
subscriptionCount: 0,
});
expect(
runtime.connection.subscribe({
subscriptionId: "subscription.new",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
}),
).toEqual({
ok: true,
value: { acceptedLocally: true },
});
runtime.connection.close();
});
it("bounds the UNSUBSCRIBED acknowledgement and cancels its deadline on ACK", async () => {
const missing = setup();
await openConnection(missing);
missing.connection.subscribe({
subscriptionId: "subscription.missing-ack",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
missing.connection.unsubscribe("subscription.missing-ack");
const closed = missing.connection.waitClosed();
missing.clock.resolveDuration(30_000);
await expect(closed).resolves.toMatchObject({
category: "NETWORK_LOST",
error: {
kind: "IDLE_TIMEOUT",
operation: "SUBSCRIBE",
retryable: true,
},
});
const acknowledged = setup();
await openConnection(acknowledged);
acknowledged.connection.subscribe({
subscriptionId: "subscription.acknowledged",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
acknowledged.connection.unsubscribe(
"subscription.acknowledged",
);
const acknowledgementDeadline = acknowledged.clock.sleeps.find(
(sleep) => sleep.milliseconds === 30_000 && !sleep.settled,
);
expect(acknowledgementDeadline).toBeDefined();
acknowledged.socket.message(
serverFrame({
type: "UNSUBSCRIBED",
subscriptionId: "subscription.acknowledged",
}),
);
await flush();
expect(acknowledgementDeadline?.settled).toBe(true);
expect(acknowledged.connection.inspect().status).toBe("OPEN");
acknowledged.connection.close();
});
it.each([
{
name: "unknown",
prepare: (_runtime: ReturnType<typeof setup>) => undefined,
subscriptionId: "subscription.unknown",
},
{
name: "non-unsubscribing",
prepare: async (runtime: ReturnType<typeof setup>) => {
runtime.connection.subscribe({
subscriptionId: "subscription.active",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.active",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
},
subscriptionId: "subscription.active",
},
])("fails closed for an $name UNSUBSCRIBED acknowledgement", async ({
prepare,
subscriptionId,
}) => {
const runtime = setup();
await openConnection(runtime);
await prepare(runtime);
const closed = runtime.connection.waitClosed();
runtime.socket.message(
serverFrame({
type: "UNSUBSCRIBED",
subscriptionId,
}),
);
await expect(closed).resolves.toMatchObject({
category: "PROTOCOL_MISMATCH",
error: {
kind: "PROTOCOL_MISMATCH",
retryable: false,
},
});
expect(runtime.recoveries).toContainEqual({
subscriptionId: null,
reason: "PROTOCOL_MISMATCH",
});
});
it.each([
{ category: "OVERLOADED" as const, retryable: false },
{ category: "RESTART" as const, retryable: true },
{ category: "NETWORK_LOST" as const, retryable: true },
])(
"classifies $category without inventing a server retry hint",
async ({ category, retryable }) => {
const runtime = setup();
await openConnection(runtime);
const closed = runtime.connection.waitClosed();
runtime.socket.message(
serverFrame({
type: "CLOSE",
category,
}),
);
await expect(closed).resolves.toMatchObject({
category,
error: {
kind: "PROVIDER_UNAVAILABLE",
operation: "RECEIVE",
retryable,
},
});
},
);
it("propagates the canonical consumer failure through the terminal channel", async () => {
const runtime = setup({
onEvent: vi.fn(async () =>
realtimeFailure("FORBIDDEN", "RECEIVE"),
),
});
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
const closed = runtime.connection.waitClosed();
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "presence.v1",
},
}),
);
await flush();
await expect(closed).resolves.toEqual({
category: "FORBIDDEN",
error: {
kind: "FORBIDDEN",
operation: "RECEIVE",
retryable: false,
},
recovery: null,
});
expect(runtime.recoveries).toEqual([]);
});
it("closes the old generation after authority recovery commits", async () => {
const runtime = setup({
onEvent: vi.fn(async () =>
realtimeSuccess(RECOVERY_OUTCOME),
),
});
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
const closed = runtime.connection.waitClosed();
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "presence.v1",
},
}),
);
await flush();
await expect(closed).resolves.toEqual({
category: "CURSOR_RESET",
error: {
kind: "CURSOR_EXPIRED",
operation: "RECEIVE",
retryable: false,
},
recovery: RECOVERY_OUTCOME,
});
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(runtime.recoveries).toEqual([]);
expect(runtime.observations).toContainEqual({
kind: "CLOSED",
failureKind: "CURSOR_EXPIRED",
closeCategory: "CURSOR_RESET",
});
});
it("uses bounded application heartbeat and never exposes native close reason", async () => {
const runtime = setup();
await openConnection(runtime);
runtime.clock.resolveDuration(5_000);
await flush();
const heartbeat = JSON.parse(runtime.socket.sent.at(-1) ?? "");
expect(heartbeat).toMatchObject({
type: "HEARTBEAT",
nonce: "nonce.0001",
});
runtime.socket.message(
serverFrame({
type: "HEARTBEAT_ACK",
nonce: "nonce.0001",
}),
);
await flush();
runtime.clock.resolveDuration(1_000);
await flush();
expect(runtime.connection.inspect().status).toBe("OPEN");
runtime.socket.nativeClose(4_001, "token=do-not-observe");
expect(runtime.connection.inspect().status).toBe("CLOSED");
expect(JSON.stringify(runtime.observations)).not.toContain("token=");
expect(runtime.observations).toContainEqual({
kind: "CLOSED",
failureKind: "AUTH_REQUIRED",
closeCategory: "AUTH_REQUIRED",
});
});
it("handles heartbeat acknowledgements outside the application-effect queue", async () => {
let releaseEffect: (() => void) | undefined;
const runtime = setup({
onEvent: () =>
new Promise((resolve) => {
releaseEffect = () =>
resolve(
realtimeSuccess(
Object.freeze({ kind: "CONTINUE" as const }),
),
);
}),
});
await openConnection(runtime);
runtime.connection.subscribe({
subscriptionId: "subscription.0001",
streamId: "presence.v1",
scopeBinding: "scope.0001",
stateBearing: false,
});
runtime.socket.message(
serverFrame({
type: "SUBSCRIBED",
subscriptionId: "subscription.0001",
streamEpoch: "stream-epoch.0001",
acceptedCursor: null,
nextExpectedSequence: "0",
}),
);
await flush();
runtime.socket.message(
serverFrame({
type: "EVENT",
subscriptionId: "subscription.0001",
envelope: {
protocol: "REALTIME_EVENT_V1",
streamId: "presence.v1",
},
}),
);
await flush();
runtime.clock.resolveDuration(5_000);
await flush();
runtime.socket.message(
serverFrame({
type: "HEARTBEAT_ACK",
nonce: "nonce.0001",
}),
);
runtime.clock.resolveDuration(1_000);
await flush();
expect(runtime.connection.inspect()).toMatchObject({
status: "OPEN",
awaitingHeartbeatAck: false,
inboundQueueCount: 0,
});
releaseEffect?.();
await flush();
runtime.connection.close();
});
it("fails closed when heartbeat eligibility throws", async () => {
const runtime = setup({
heartbeatEligible() {
throw new Error("broken visibility provider");
},
});
await openConnection(runtime);
const closed = runtime.connection.waitClosed();
runtime.clock.resolveDuration(5_000);
await flush();
await expect(closed).resolves.toMatchObject({
category: "NETWORK_LOST",
error: {
kind: "PROVIDER_UNAVAILABLE",
operation: "RECEIVE",
},
});
});
});