test: cover session websocket cwd proxying

This commit is contained in:
Federico Jaramillo Martinez
2026-06-10 23:15:59 +02:00
parent b99143f757
commit 680ed88b0b
+74 -3
View File
@@ -1,5 +1,6 @@
import Fastify, { type FastifyInstance } from "fastify";
import fastifyWebsocket from "@fastify/websocket";
import { WebSocket, WebSocketServer } from "ws";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { registerSessionProxyRoutes } from "./sessionProxyRoutes";
@@ -9,12 +10,13 @@ let daemon: FakeSessionDaemon;
beforeEach(async () => {
app = Fastify({ logger: false });
await app.register(fastifyWebsocket);
daemon = new FakeSessionDaemon();
daemon = await FakeSessionDaemon.create();
registerSessionProxyRoutes(app, daemon, "/api/machines/local");
});
afterEach(async () => {
await app.close();
await daemon.close();
});
describe("machine-scoped session proxy routes", () => {
@@ -33,17 +35,86 @@ describe("machine-scoped session proxy routes", () => {
expect(response.json()).toEqual({ ok: true });
expect(daemon.requests).toEqual([{ method: "POST", path: "/auth/api-key", body: { providerId: "p", key: "k" } }]);
});
it("preserves cwd query context when forwarding session event websockets", async () => {
await app.listen({ host: "127.0.0.1", port: 0 });
const socket = new WebSocket(`${serverUrl(app)}/api/machines/local/sessions/session-1/events?cwd=${encodeURIComponent("/repo")}`);
try {
await waitForOpen(socket);
expect(daemon.websocketPaths).toEqual(["/sessions/session-1/events?cwd=%2Frepo"]);
} finally {
socket.close();
}
});
});
class FakeSessionDaemon {
readonly requests: { method: string; path: string; body: unknown }[] = [];
readonly websocketPaths: string[] = [];
private readonly sockets = new Set<WebSocket>();
private constructor(private readonly upstream: WebSocketServer) {
this.upstream.on("connection", (socket) => {
this.sockets.add(socket);
socket.on("close", () => { this.sockets.delete(socket); });
});
}
static async create(): Promise<FakeSessionDaemon> {
const upstream = new WebSocketServer({ host: "127.0.0.1", port: 0 });
await waitForListening(upstream);
return new FakeSessionDaemon(upstream);
}
request(method: string, path: string, body?: unknown): Promise<{ statusCode: number; headers: Record<string, string>; body: string }> {
this.requests.push({ method, path, body });
return Promise.resolve({ statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ ok: true }) });
}
connectWebSocket(): never {
throw new Error("not implemented");
connectWebSocket(path: string): WebSocket {
this.websocketPaths.push(path);
return new WebSocket(`${webSocketServerUrl(this.upstream)}${path}`);
}
async close(): Promise<void> {
for (const socket of this.sockets) socket.terminate();
await closeWebSocketServer(this.upstream);
}
}
function serverUrl(instance: FastifyInstance): string {
const address = instance.server.address();
if (address === null || typeof address === "string") throw new Error("Expected TCP server address");
return `ws://127.0.0.1:${String(address.port)}`;
}
function webSocketServerUrl(server: WebSocketServer): string {
const address = server.address();
if (address === null || typeof address === "string") throw new Error("Expected TCP server address");
return `ws://127.0.0.1:${String(address.port)}`;
}
function waitForListening(server: WebSocketServer): Promise<void> {
return new Promise((resolve) => {
server.once("listening", () => { resolve(); });
});
}
function closeWebSocketServer(server: WebSocketServer): Promise<void> {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error !== undefined) reject(error);
else resolve();
});
});
}
function waitForOpen(socket: WebSocket): Promise<void> {
if (socket.readyState === WebSocket.OPEN) return Promise.resolve();
return new Promise((resolve, reject) => {
socket.once("open", () => { resolve(); });
socket.once("error", reject);
socket.once("close", () => { reject(new Error("WebSocket closed before opening")); });
});
}