import { afterEach, describe, expect, it } from "vitest";
import type { HostConfig } from "../types.js";
import { isLocalHost, narrowToDefaultHost } from "./host-utils.js";
describe("isLocalHost", () => {
describe("local host detection", () => {
it("detects localhost hostname", () => {
const host: HostConfig = {
name: "local",
host: "localhost",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("detects 127.0.0.1 IPv4", () => {
const host: HostConfig = {
name: "local",
host: "127.0.0.1",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("detects ::1 IPv6 loopback", () => {
const host: HostConfig = {
name: "local",
host: "::1",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("detects 0.0.0.0 (all interfaces)", () => {
const host: HostConfig = {
name: "local",
host: "0.0.0.0",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("detects Unix socket path without SSH user", () => {
const host: HostConfig = {
name: "local",
host: "localhost",
protocol: "ssh",
dockerSocketPath: "/var/run/docker.sock",
};
expect(isLocalHost(host)).toBe(true);
});
it("detects protocol 'ssh' with localhost and no SSH user", () => {
const host: HostConfig = {
name: "local",
host: "localhost",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
});
describe("remote host detection", () => {
it("detects remote IP as not local", () => {
const host: HostConfig = {
name: "remote",
host: "192.168.1.100",
protocol: "ssh",
sshUser: "admin",
};
expect(isLocalHost(host)).toBe(false);
});
it("detects remote hostname as not local", () => {
const host: HostConfig = {
name: "remote",
host: "server.example.com",
protocol: "ssh",
sshUser: "admin",
};
expect(isLocalHost(host)).toBe(false);
});
it("detects localhost with SSH user as remote (requires SSH)", () => {
const host: HostConfig = {
name: "remote",
host: "localhost",
protocol: "ssh",
sshUser: "admin",
};
expect(isLocalHost(host)).toBe(false);
});
it("detects 127.0.0.1 with SSH user as remote (requires SSH)", () => {
const host: HostConfig = {
name: "remote",
host: "127.0.0.1",
protocol: "ssh",
sshUser: "admin",
};
expect(isLocalHost(host)).toBe(false);
});
it("detects http/https protocol as not local (Docker API only)", () => {
const host: HostConfig = {
name: "remote",
host: "localhost",
protocol: "http",
};
expect(isLocalHost(host)).toBe(false);
});
});
describe("edge cases", () => {
it("handles uppercase localhost", () => {
const host: HostConfig = {
name: "local",
host: "LOCALHOST",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("handles localhost with port", () => {
const host: HostConfig = {
name: "local",
host: "localhost",
port: 2375,
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("handles 127.0.0.2 (also loopback range)", () => {
const host: HostConfig = {
name: "local",
host: "127.0.0.2",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
it("handles 127.255.255.255 (end of loopback range)", () => {
const host: HostConfig = {
name: "local",
host: "127.255.255.255",
protocol: "ssh",
};
expect(isLocalHost(host)).toBe(true);
});
});
});
describe("narrowToDefaultHost", () => {
const originalEnv = process.env.SYNAPSE_DEFAULT_HOST;
afterEach(() => {
if (originalEnv === undefined) {
delete process.env.SYNAPSE_DEFAULT_HOST;
} else {
process.env.SYNAPSE_DEFAULT_HOST = originalEnv;
}
});
it("returns single-host arrays unchanged", () => {
const hosts: HostConfig[] = [{ name: "only", host: "server.example.com" }];
expect(narrowToDefaultHost(hosts)).toEqual(hosts);
});
it("returns empty arrays unchanged", () => {
expect(narrowToDefaultHost([])).toEqual([]);
});
it("narrows to SYNAPSE_DEFAULT_HOST when set", () => {
process.env.SYNAPSE_DEFAULT_HOST = "host2";
const hosts: HostConfig[] = [
{ name: "host1", host: "server1.example.com" },
{ name: "host2", host: "server2.example.com" },
{ name: "host3", host: "server3.example.com" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("host2");
});
it("falls back to local socket host when no env var set", () => {
delete process.env.SYNAPSE_DEFAULT_HOST;
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "local", host: "/var/run/docker.sock" },
{ name: "remote2", host: "server2.example.com" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("local");
});
it("falls back to host with dockerSocketPath", () => {
delete process.env.SYNAPSE_DEFAULT_HOST;
const hosts: HostConfig[] = [
{ name: "remote", host: "server1.example.com" },
{ name: "socket-host", host: "localhost", dockerSocketPath: "/var/run/docker.sock" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("socket-host");
});
it("returns all hosts when no default or local socket found", () => {
delete process.env.SYNAPSE_DEFAULT_HOST;
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "remote2", host: "server2.example.com" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(2);
});
it("ignores empty/whitespace SYNAPSE_DEFAULT_HOST", () => {
process.env.SYNAPSE_DEFAULT_HOST = " ";
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "local", host: "/var/run/docker.sock" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("local");
});
it("ignores 'undefined' string SYNAPSE_DEFAULT_HOST", () => {
process.env.SYNAPSE_DEFAULT_HOST = "undefined";
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "local", host: "/var/run/docker.sock" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("local");
});
it("ignores 'null' string SYNAPSE_DEFAULT_HOST", () => {
process.env.SYNAPSE_DEFAULT_HOST = "null";
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "local", host: "/var/run/docker.sock" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("local");
});
it("returns all hosts when SYNAPSE_DEFAULT_HOST does not match any host", () => {
process.env.SYNAPSE_DEFAULT_HOST = "nonexistent";
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "remote2", host: "server2.example.com" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(2);
});
it("prefers SYNAPSE_DEFAULT_HOST over local socket fallback", () => {
process.env.SYNAPSE_DEFAULT_HOST = "remote1";
const hosts: HostConfig[] = [
{ name: "remote1", host: "server1.example.com" },
{ name: "local", host: "/var/run/docker.sock" },
];
const result = narrowToDefaultHost(hosts);
expect(result).toHaveLength(1);
expect(result[0].name).toBe("remote1");
});
});