Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions tests/connection-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { describe, it, beforeEach, expect } from "vitest";
import { Client, ConnectConfig } from "ssh2";
import {
connections,
StoredConnection,
cleanupAllConnections,
MAX_OUTPUT_BYTES,
} from "../src/connections.js";

/**
* Helper to create a mock StoredConnection for testing purposes.
* Uses a real Client instance but never actually connects it.
*/
function makeMockConnection(
host: string,
ready: boolean = true
): StoredConnection {
const client = new Client();
const connectConfig: ConnectConfig = {
host,
port: 22,
username: "testuser",
};
return { client, connectConfig, ready, host };
}

describe("Connection Manager - Global connections map", () => {
beforeEach(() => {
// Ensure a clean state before each test by clearing any leftover entries
connections.clear();
});

it("should start with an empty connections map", () => {
expect(connections.size).toBe(0);
});

it("should store a connection and retrieve it by ID", () => {
const conn = makeMockConnection("server1.example.com");
connections.set("conn-1", conn);

expect(connections.has("conn-1")).toBe(true);
const retrieved = connections.get("conn-1");
expect(retrieved).toBeDefined();
expect(retrieved!.host).toBe("server1.example.com");
expect(retrieved!.connectConfig.username).toBe("testuser");
expect(retrieved!.connectConfig.port).toBe(22);
});

it("should return undefined for a non-existent connection ID", () => {
const result = connections.get("does-not-exist");
expect(result).toBeUndefined();
expect(connections.has("does-not-exist")).toBe(false);
});

it("should remove a connection from the map using delete", () => {
const conn = makeMockConnection("server2.example.com");
connections.set("conn-2", conn);
expect(connections.size).toBe(1);

connections.delete("conn-2");
expect(connections.size).toBe(0);
expect(connections.get("conn-2")).toBeUndefined();
});

it("should track the ready state of a connection", () => {
const conn = makeMockConnection("server3.example.com", true);
connections.set("conn-3", conn);

expect(connections.get("conn-3")!.ready).toBe(true);

// Simulate the connection going dead (as the close/end handlers do)
conn.ready = false;
expect(connections.get("conn-3")!.ready).toBe(false);

// Simulate reconnection becoming ready again
conn.ready = true;
expect(connections.get("conn-3")!.ready).toBe(true);
});

it("should allow multiple connections to coexist in the map", () => {
const conn1 = makeMockConnection("host-a.example.com");
const conn2 = makeMockConnection("host-b.example.com");
const conn3 = makeMockConnection("host-c.example.com");

connections.set("a", conn1);
connections.set("b", conn2);
connections.set("c", conn3);

expect(connections.size).toBe(3);
expect(connections.get("a")!.host).toBe("host-a.example.com");
expect(connections.get("b")!.host).toBe("host-b.example.com");
expect(connections.get("c")!.host).toBe("host-c.example.com");
});

it("should replace an existing connection when set with the same ID", () => {
const original = makeMockConnection("original-host.example.com");
connections.set("shared-id", original);
expect(connections.get("shared-id")!.host).toBe(
"original-host.example.com"
);

const replacement = makeMockConnection("replacement-host.example.com");
connections.set("shared-id", replacement);

expect(connections.size).toBe(1);
expect(connections.get("shared-id")!.host).toBe(
"replacement-host.example.com"
);
});

it("should iterate over all connections", () => {
connections.set("x", makeMockConnection("host-x.example.com"));
connections.set("y", makeMockConnection("host-y.example.com"));

const ids: string[] = [];
const hosts: string[] = [];
for (const [id, stored] of connections) {
ids.push(id);
hosts.push(stored.host);
}

expect(ids).toEqual(["x", "y"]);
expect(hosts).toEqual(["host-x.example.com", "host-y.example.com"]);
});
});

describe("Connection Manager - cleanupAllConnections", () => {
beforeEach(() => {
connections.clear();
});

it("should remove all connections from the map", () => {
connections.set("c1", makeMockConnection("host1.example.com"));
connections.set("c2", makeMockConnection("host2.example.com"));
connections.set("c3", makeMockConnection("host3.example.com"));
expect(connections.size).toBe(3);

cleanupAllConnections();

expect(connections.size).toBe(0);
expect(connections.get("c1")).toBeUndefined();
expect(connections.get("c2")).toBeUndefined();
expect(connections.get("c3")).toBeUndefined();
});

it("should handle cleanup when the map is already empty", () => {
expect(connections.size).toBe(0);
// Should not throw
cleanupAllConnections();
expect(connections.size).toBe(0);
});
});

describe("Connection Manager - MAX_OUTPUT_BYTES constant", () => {
it("should be set to 1MB (1024 * 1024 bytes)", () => {
expect(MAX_OUTPUT_BYTES).toBe(1024 * 1024);
expect(MAX_OUTPUT_BYTES).toBe(1_048_576);
});
});

describe("Connection Manager - StoredConnection interface shape", () => {
it("should have all required fields with correct types", () => {
const conn = makeMockConnection("test-host.example.com", false);

// Verify the shape of StoredConnection
expect(conn).toHaveProperty("client");
expect(conn).toHaveProperty("connectConfig");
expect(conn).toHaveProperty("ready");
expect(conn).toHaveProperty("host");

// Verify types
expect(conn.client).toBeInstanceOf(Client);
expect(typeof conn.ready).toBe("boolean");
expect(typeof conn.host).toBe("string");
expect(typeof conn.connectConfig).toBe("object");
});
});