Files
Termix/plugins/remote-desktop/tests/backend/sessions.test.ts
T

127 lines
4.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { RemoteSessions } from "../../src/backend/sessions.js";
import { createLiveSessions } from "../../src/backend/live-sessions.js";
import { GuacamoleTokenService } from "../../src/backend/token-service.js";
const meta = {
termixConnectId: "c1",
hostId: 7,
hostName: "Win box",
ownerUserId: "user-1",
protocol: "rdp" as const,
tabInstanceId: "tab-1",
};
afterEach(() => {
vi.useRealTimers();
});
describe("RemoteSessions", () => {
it("moves parked resources onto the session and releases them on close", () => {
const sessions = new RemoteSessions();
const tunnel = vi.fn();
const tracking = vi.fn();
sessions.park("c1", [tunnel]);
sessions.opened(meta, "g1", [tracking]);
expect(sessions.byConnect("c1")?.guacamoleConnectionId).toBe("g1");
expect(sessions.byGuacamole("g1")?.hostName).toBe("Win box");
expect(tunnel).not.toHaveBeenCalled();
sessions.closed("c1");
expect(tunnel).toHaveBeenCalledOnce();
expect(tracking).toHaveBeenCalledOnce();
expect(sessions.byGuacamole("g1")).toBeNull();
});
it.each(["vnc", "rdp"] as const)(
"keeps an opened %s session's proxy and jump tunnel alive beyond one hour",
(protocol) => {
vi.useFakeTimers();
const sessions = new RemoteSessions();
const proxy = vi.fn();
const tunnel = vi.fn();
sessions.park("c1", [proxy, tunnel]);
sessions.opened({ ...meta, protocol }, "g1");
vi.advanceTimersByTime(2 * 60 * 60 * 1000);
expect(proxy).not.toHaveBeenCalled();
expect(tunnel).not.toHaveBeenCalled();
expect(sessions.byConnect("c1")?.guacamoleConnectionId).toBe("g1");
sessions.closed("c1");
sessions.closed("c1");
expect(proxy).toHaveBeenCalledOnce();
expect(tunnel).toHaveBeenCalledOnce();
},
);
it("releases a tunnel guacd never used", () => {
vi.useFakeTimers();
const sessions = new RemoteSessions(1000);
const tunnel = vi.fn();
sessions.park("c1", [tunnel]);
vi.advanceTimersByTime(1000);
expect(tunnel).toHaveBeenCalledOnce();
});
it("releases everything on clear, and one failing close does not stop the rest", () => {
const sessions = new RemoteSessions();
const failing = vi.fn(() => {
throw new Error("already closed");
});
const pending = vi.fn();
const open = vi.fn();
sessions.park("c2", [failing, pending]);
sessions.park("c1", [open]);
sessions.opened(meta, "g1");
sessions.clear();
expect(pending).toHaveBeenCalledOnce();
expect(open).toHaveBeenCalledOnce();
});
});
describe("sessions.live providers", () => {
const tokens = new GuacamoleTokenService();
it("describes a live session of its own protocol only", () => {
const sessions = new RemoteSessions();
sessions.opened(meta, "g1");
const deps = { sessions, tokens, endSession: vi.fn(() => true) };
const rdp = createLiveSessions("rdp", deps);
const vnc = createLiveSessions("vnc", deps);
expect(rdp.getSession("g1")).toMatchObject({
id: "g1",
userId: "user-1",
hostId: 7,
hostName: "Win box",
isConnected: true,
tabInstanceId: "tab-1",
});
expect(vnc.getSession("g1")).toBeNull();
});
it("mints a join token for a viewer and refuses an unknown session", () => {
const sessions = new RemoteSessions();
sessions.opened(meta, "g1");
const rdp = createLiveSessions("rdp", {
sessions,
tokens,
endSession: vi.fn(() => true),
});
const token = tokens.decryptToken(rdp.createViewerToken("g1", true));
expect(token?.connection).toMatchObject({ join: "g1", readOnly: true });
expect(token?.recording).toBeUndefined();
expect(() => rdp.createViewerToken("nope", false)).toThrow();
});
it("ends the session for its owner", () => {
const sessions = new RemoteSessions();
sessions.opened(meta, "g1");
const endSession = vi.fn(() => true);
const rdp = createLiveSessions("rdp", { sessions, tokens, endSession });
rdp.ownerEndSession("g1", "owner ended");
expect(endSession).toHaveBeenCalledWith("g1");
});
});