mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
* feat: AI translations at release, 7 new languages and regional fallback * fix: proper plural forms for count strings and full translations * fix: plural forms for selected, hidden and active counts * fix: synced plugin updates failing on desktop and server-only plugins shown as stopped * fix: restore rounded corners on app icons and favicon * feat: desktop app auto updates with automatic, ask first or never modes * feat: community and custom plugin registries * feat: plugin guide for AI tools in the SDK with a lint check * fix: keep the terminal WebSocket open past nginx's 60s default (#1588) * chore: update changelog * feat: show plugin download counts only and stop CI builds inflating them * feat: hide installed plugins in the browse tab by default * chore: pin bundled plugins to latest releases * chore: update translations * fix: focusing a saved password field and saving no longer erases it * fix: tell desktop users when their 2.8 data needs 2.9 first * fix: point windows update info at the x64 installer * fix: keep desktop client tunnel sockets alive behind proxies * chore: pin bundled plugins to latest releases * fix: cover client tunnel keepalive in the c2s runtime tests * fix: typing after opening a host no longer goes to the host search * fix: accept tab and = separators in SSH config import (#1598) * chore: update changelog * chore: drop plugin versions from core changelog * chore: update translations * chore: lint, format, and bump version to 26.10.1 --------- Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com> Co-authored-by: Iain <Iain1970@protonmail.com>
156 lines
4.0 KiB
TypeScript
156 lines
4.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
importedHostUsername,
|
|
notifyImportedHosts,
|
|
parseSSHConfig,
|
|
} from "../../../database/routes/host-bulk-routes.js";
|
|
import { pluginEvents, TOPICS } from "../../../plugins/events.js";
|
|
|
|
describe("parseSSHConfig", () => {
|
|
it("parses a basic Host block", () => {
|
|
const config = `
|
|
Host myserver
|
|
HostName 192.168.1.10
|
|
User alice
|
|
Port 2222
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
name: "myserver",
|
|
hostname: "192.168.1.10",
|
|
user: "alice",
|
|
port: 2222,
|
|
});
|
|
});
|
|
|
|
it("parses multiple Host blocks", () => {
|
|
const config = `
|
|
Host web
|
|
HostName web.example.com
|
|
User deploy
|
|
|
|
Host db
|
|
HostName db.example.com
|
|
User postgres
|
|
Port 5432
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].name).toBe("web");
|
|
expect(result[1].name).toBe("db");
|
|
expect(result[1].port).toBe(5432);
|
|
});
|
|
|
|
it("ignores comment lines", () => {
|
|
const config = `
|
|
# This is a comment
|
|
Host server
|
|
# Another comment
|
|
HostName 10.0.0.1
|
|
User root
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].hostname).toBe("10.0.0.1");
|
|
});
|
|
|
|
it("skips wildcard Host entries", () => {
|
|
const config = `
|
|
Host *
|
|
ServerAliveInterval 60
|
|
|
|
Host prod
|
|
HostName prod.example.com
|
|
User ubuntu
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].name).toBe("prod");
|
|
});
|
|
|
|
it("captures IdentityFile and ProxyJump", () => {
|
|
const config = `
|
|
Host bastion
|
|
HostName bastion.example.com
|
|
User ec2-user
|
|
IdentityFile ~/.ssh/id_rsa
|
|
ProxyJump jumphost.example.com
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].identityFile).toBe("~/.ssh/id_rsa");
|
|
expect(result[0].proxyJump).toBe("jumphost.example.com");
|
|
});
|
|
|
|
it("skips Host blocks without a HostName", () => {
|
|
const config = `
|
|
Host alias-only
|
|
User foo
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it("accepts tabs and = between keywords and values", () => {
|
|
const config = "Host\tweb\n\tHostName=10.0.0.5\n\tUser = deploy\n";
|
|
expect(parseSSHConfig(config)).toEqual([
|
|
{ name: "web", hostname: "10.0.0.5", user: "deploy" },
|
|
]);
|
|
});
|
|
|
|
it("defaults port to undefined when not specified", () => {
|
|
const config = `
|
|
Host server
|
|
HostName 1.2.3.4
|
|
User root
|
|
`;
|
|
const result = parseSSHConfig(config);
|
|
expect(result[0].port).toBeUndefined();
|
|
});
|
|
|
|
it("returns empty array for empty input", () => {
|
|
expect(parseSSHConfig("")).toHaveLength(0);
|
|
expect(parseSSHConfig(" \n\n ")).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe("importedHostUsername", () => {
|
|
it("lets credential-backed SSH hosts inherit the credential username", () => {
|
|
expect(importedHostUsername("ssh", "credential", "")).toBe("");
|
|
});
|
|
|
|
it("keeps requiring usernames for other SSH authentication modes", () => {
|
|
expect(importedHostUsername("ssh", "password", "")).toBeNull();
|
|
expect(importedHostUsername("ssh", "key", undefined)).toBeNull();
|
|
});
|
|
|
|
it("always returns a non-null database value for non-SSH hosts", () => {
|
|
expect(importedHostUsername("rdp", "password", undefined)).toBe("");
|
|
});
|
|
|
|
it("preserves an explicitly configured host username", () => {
|
|
expect(importedHostUsername("ssh", "credential", "guest-admin")).toBe(
|
|
"guest-admin",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("notifyImportedHosts", () => {
|
|
it("sends a host update for every imported host", () => {
|
|
const seen: unknown[] = [];
|
|
const off = pluginEvents.on(TOPICS.hostUpdated, (payload) => {
|
|
seen.push(payload);
|
|
});
|
|
try {
|
|
notifyImportedHosts("user-1", [4, 7]);
|
|
} finally {
|
|
off();
|
|
}
|
|
expect(seen).toEqual([
|
|
{ hostId: 4, userId: "user-1" },
|
|
{ hostId: 7, userId: "user-1" },
|
|
]);
|
|
});
|
|
});
|