fix: proxmox guest sync, startup snippet sync, drop dead cloudflare ssh transport

This commit is contained in:
LukeGus committed 2026-09-28 21:59:37 -05:00
1 parent 44062e30d1
commit ffa872ebd3
19 files changed
+270 -292

No files matched your search

@@ -1,74 +0,0 @@
import { EventEmitter } from "node:events";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { WebSocket } from "ws";
import {
createWebSocketDuplex,
waitForWebSocketOpen,
} from "../../hosts/cloudflare-websocket.js";
function setupSocket() {
const socket = new EventEmitter() as EventEmitter & {
terminate: ReturnType<typeof vi.fn>;
send: ReturnType<typeof vi.fn>;
};
socket.terminate = vi.fn();
socket.send = vi.fn();
return socket as unknown as WebSocket;
}
describe("waitForWebSocketOpen", () => {
afterEach(() => vi.useRealTimers());
it("clears listeners and leaves an opened socket alive", async () => {
vi.useFakeTimers();
const socket = setupSocket();
const result = waitForWebSocketOpen(socket, 30_000);
socket.emit("open");
await expect(result).resolves.toBeUndefined();
expect(socket.terminate).not.toHaveBeenCalled();
expect(socket.listenerCount("open")).toBe(0);
expect(socket.listenerCount("error")).toBe(0);
expect(vi.getTimerCount()).toBe(0);
});
it("terminates a socket that does not open before the deadline", async () => {
vi.useFakeTimers();
const socket = setupSocket();
const result = waitForWebSocketOpen(socket, 30_000);
const rejection = expect(result).rejects.toThrow(
"Cloudflare tunnel timeout",
);
await vi.advanceTimersByTimeAsync(30_000);
await rejection;
expect(socket.terminate).toHaveBeenCalledOnce();
expect(socket.listenerCount("open")).toBe(0);
expect(socket.listenerCount("error")).toBe(0);
});
it("terminates the socket when the SSH duplex is destroyed", () => {
const socket = setupSocket();
const duplex = createWebSocketDuplex(socket);
duplex.destroy();
expect(socket.terminate).toHaveBeenCalledOnce();
expect(socket.listenerCount("message")).toBe(0);
expect(socket.listenerCount("close")).toBe(0);
expect(socket.listenerCount("error")).toBe(0);
});
it("handles established socket errors without an unhandled event", () => {
const socket = setupSocket();
const duplex = createWebSocketDuplex(socket);
socket.emit("error", new Error("connection lost"));
expect(duplex.destroyed).toBe(true);
expect(socket.terminate).toHaveBeenCalledOnce();
expect(socket.listenerCount("error")).toBe(0);
});
});
@@ -0,0 +1,38 @@
import { describe, expect, it, vi } from "vitest";
import { HostSessionStatus } from "../../hosts/host-session-status.js";
describe("HostSessionStatus", () => {
it("reports only the first connection and last disconnection per host", () => {
const status = new HostSessionStatus();
const listener = vi.fn();
status.subscribe(listener);
const closeFirst = status.register(7);
const closeSecond = status.register(7);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenLastCalledWith(7, true);
closeFirst();
expect(listener).toHaveBeenCalledTimes(1);
closeSecond();
expect(listener).toHaveBeenLastCalledWith(7, false);
expect(listener).toHaveBeenCalledTimes(2);
});
it("makes connection cleanup idempotent", () => {
const status = new HostSessionStatus();
const listener = vi.fn();
status.subscribe(listener);
const close = status.register(9);
close();
close();
expect(listener.mock.calls).toEqual([
[9, true],
[9, false],
]);
});
});
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { getJumpHostSocks5Config } from "../../hosts/jump-host-proxy.js";
describe("getJumpHostSocks5Config", () => {
it("uses the first jump host proxy settings", () => {
expect(
getJumpHostSocks5Config({
useSocks5: true,
socks5Host: "proxy.internal",
socks5Port: 1080,
socks5Username: "user",
socks5Password: "secret",
}),
).toEqual({
useSocks5: true,
socks5Host: "proxy.internal",
socks5Port: 1080,
socks5Username: "user",
socks5Password: "secret",
socks5ProxyChain: [],
});
});
it("does not use destination proxy settings for the first jump host", () => {
expect(getJumpHostSocks5Config({ useSocks5: false })).toBeNull();
});
it("accepts a serialized proxy chain from the first jump host", () => {
const chain = [
{
id: "proxy-1",
name: "Proxy 1",
host: "proxy.internal",
port: 1080,
type: "socks5" as const,
},
];
expect(
getJumpHostSocks5Config({
useSocks5: true,
socks5ProxyChain: JSON.stringify(chain),
}),
).toEqual({
useSocks5: true,
socks5Host: undefined,
socks5Port: undefined,
socks5Username: undefined,
socks5Password: undefined,
socks5ProxyChain: chain,
});
});
});
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { resolveSshKeepalive } from "../../hosts/ssh-keepalive.js";
describe("resolveSshKeepalive", () => {
it("preserves zero to disable SSH keepalives", () => {
expect(resolveSshKeepalive(0, 0, 30000, 5)).toEqual({
keepaliveInterval: 0,
keepaliveCountMax: 0,
});
});
it("uses defaults when keepalive settings are absent", () => {
expect(resolveSshKeepalive(undefined, undefined, 60000, 5)).toEqual({
keepaliveInterval: 60000,
keepaliveCountMax: 5,
});
});
it("enforces the existing minimums for positive settings", () => {
expect(resolveSshKeepalive(1, 0.5, 30000, 5)).toEqual({
keepaliveInterval: 5000,
keepaliveCountMax: 1,
});
});
});