Files
paseo/packages/server/src/terminal/terminal-session-controller.resize.test.ts
T
Mohamed Boudra 6f2195904b fix(subscriptions): isolate observers and silence unrequested feeds (#4596)
Give each observation a server-issued ID owned by its physical connection. Preserve snapshot-only reads and combined list-and-subscribe, source-bound replies, lifecycle cleanup and legacy wire compatibility.
2026-09-11 08:07:35 +02:00

142 lines
4.6 KiB
TypeScript

import { describe, expect, test } from "vitest";
import {
TerminalStreamOpcode,
encodeTerminalResizePayload,
type TerminalStreamFrame,
} from "@getpaseo/protocol/binary-frames/index";
import { SessionDelivery } from "../server/session/owned-subscriptions/index.js";
import type {
SessionInboundMessage,
SessionOutboundMessage,
TerminalState,
} from "@getpaseo/protocol/messages";
import type pino from "pino";
import { TerminalSessionController } from "./terminal-session-controller.js";
import type { TerminalManager } from "./terminal-manager.js";
import type { TerminalSession } from "./terminal.js";
function createFixture(): {
terminal: TerminalSession;
terminalManager: TerminalManager;
appliedSizes: string[];
} {
const appliedSizes: string[] = [];
let size = { rows: 24, cols: 80 };
const state = (): TerminalState => ({
rows: size.rows,
cols: size.cols,
grid: [],
scrollback: [],
cursor: { row: 0, col: 0 },
});
const terminal = {
id: "terminal-1",
name: "Terminal",
cwd: "/tmp",
workspaceId: "workspace-1",
getSize: () => size,
send: (message: { type: string; rows?: number; cols?: number }) => {
if (message.type !== "resize" || message.rows === undefined || message.cols === undefined) {
return;
}
size = { rows: message.rows, cols: message.cols };
appliedSizes.push(`${message.cols}x${message.rows}`);
},
subscribe: () => () => {},
onExit: () => () => {},
} as unknown as TerminalSession;
const terminalManager = {
getTerminal: () => terminal,
getTerminalState: async () => ({ state: state(), revision: 0 }),
} as unknown as TerminalManager;
return { terminal, terminalManager, appliedSizes };
}
function createController(
terminalManager: TerminalManager,
outbound: SessionOutboundMessage[] = [],
): TerminalSessionController {
return new TerminalSessionController({
terminalManager,
emit: (message) => outbound.push(message),
emitBinary: () => {},
hasBinaryChannel: () => true,
isPathWithinRoot: () => false,
sessionLogger: { warn: () => {}, error: () => {} } as unknown as pino.Logger,
});
}
function resizeFrame(input: {
rows: number;
cols: number;
intent?: "claim" | "update";
}): TerminalStreamFrame {
return {
opcode: TerminalStreamOpcode.Resize,
slot: 0,
payload: encodeTerminalResizePayload(input),
};
}
describe("terminal session controller size ownership", () => {
test("routes JSON, binary, and attach sizes through one connection-owned arbiter", async () => {
const { terminalManager, appliedSizes } = createFixture();
const controllerA = createController(terminalManager);
const controllerB = createController(terminalManager);
const sourceA = {};
const sourceB = {};
const ownership = new SessionDelivery(() => {});
// Legacy binary resize and attach sizing remain source scoped.
const dispatch = (
controller: TerminalSessionController,
source: object,
message: SessionInboundMessage,
) =>
ownership.request(source, message, async () => {
await controller.dispatch(message, ownership);
});
await dispatch(controllerA, sourceA, {
type: "terminal_input",
terminalId: "terminal-1",
message: { type: "resize", rows: 30, cols: 100, intent: "claim" },
});
await dispatch(controllerA, sourceA, {
type: "terminal_input",
terminalId: "terminal-1",
message: { type: "resize", rows: 31, cols: 101, intent: "update" },
});
await dispatch(controllerB, sourceB, {
type: "terminal_input",
terminalId: "terminal-1",
message: { type: "resize", rows: 32, cols: 102, intent: "update" },
});
await dispatch(controllerB, sourceB, {
type: "subscribe_terminal_request",
terminalId: "terminal-1",
requestId: "subscribe-b",
restore: { mode: "live" },
});
controllerB.handleBinaryFrame(resizeFrame({ rows: 33, cols: 103, intent: "claim" }), sourceB);
await dispatch(controllerA, sourceA, {
type: "terminal_input",
terminalId: "terminal-1",
message: { type: "resize", rows: 34, cols: 104, intent: "update" },
});
await dispatch(controllerA, sourceA, {
type: "subscribe_terminal_request",
terminalId: "terminal-1",
requestId: "subscribe-a",
restore: { mode: "live", size: { rows: 35, cols: 105 } },
});
controllerB.handleBinaryFrame(resizeFrame({ rows: 36, cols: 106, intent: "update" }), sourceB);
await ownership.close();
expect(appliedSizes).toEqual(["100x30", "101x31", "103x33", "105x35"]);
});
});