2026-08-19 14:12:06 -05:00
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
|
import type { SSHHost } from "@/types/index";
|
|
|
|
|
|
|
|
|
|
const isElectron = vi.hoisted(() => vi.fn());
|
|
|
|
|
const remoteApi = vi.hoisted(() => ({ get: vi.fn() }));
|
|
|
|
|
|
|
|
|
|
vi.mock("@/lib/electron", () => ({ isElectron }));
|
|
|
|
|
vi.mock("@/main-axios", () => ({ getRemoteStatsApi: () => remoteApi }));
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
getConnectedRemoteApi,
|
2026-09-20 14:56:39 -05:00
|
|
|
hydrateLocalSharedHostAuth,
|
2026-08-19 14:12:06 -05:00
|
|
|
markRemoteSharedHosts,
|
|
|
|
|
resolveRemoteHostId,
|
|
|
|
|
} from "@/lib/remote-server-api";
|
|
|
|
|
|
|
|
|
|
describe("remote server API", () => {
|
|
|
|
|
const invoke = vi.fn();
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
isElectron.mockReset();
|
|
|
|
|
remoteApi.get.mockReset();
|
|
|
|
|
invoke.mockReset();
|
|
|
|
|
Object.defineProperty(window, "electronAPI", {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: { invoke },
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("is available only for a configured Electron sync server", async () => {
|
|
|
|
|
isElectron.mockReturnValue(false);
|
|
|
|
|
await expect(getConnectedRemoteApi()).resolves.toBeNull();
|
|
|
|
|
|
|
|
|
|
isElectron.mockReturnValue(true);
|
|
|
|
|
invoke.mockResolvedValueOnce({ serverUrl: "https://termix.example" });
|
|
|
|
|
await expect(getConnectedRemoteApi()).resolves.toBe(remoteApi);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("maps a local host to the remote numeric id by syncId", async () => {
|
|
|
|
|
isElectron.mockReturnValue(true);
|
|
|
|
|
invoke.mockResolvedValue({ serverUrl: "https://termix.example" });
|
|
|
|
|
remoteApi.get.mockResolvedValue({
|
|
|
|
|
data: { rows: [{ id: 41, syncId: "host-a" }] },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await expect(resolveRemoteHostId("host-a")).resolves.toBe(41);
|
|
|
|
|
await expect(resolveRemoteHostId("missing")).resolves.toBeNull();
|
|
|
|
|
expect(remoteApi.get).toHaveBeenCalledWith("/sync/hosts");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("keeps only shared remote hosts and gives them collision-free ids", () => {
|
|
|
|
|
const rows = markRemoteSharedHosts([
|
|
|
|
|
{ id: 4, isShared: false },
|
2026-09-20 14:56:39 -05:00
|
|
|
{
|
|
|
|
|
id: 9,
|
|
|
|
|
isShared: true,
|
|
|
|
|
syncId: "shared-host",
|
|
|
|
|
connectionOrigin: "local",
|
|
|
|
|
},
|
2026-08-19 14:12:06 -05:00
|
|
|
] as SSHHost[]);
|
|
|
|
|
|
|
|
|
|
expect(rows).toEqual([
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
id: -9,
|
|
|
|
|
syncId: "shared-host",
|
2026-09-20 14:56:39 -05:00
|
|
|
connectionOrigin: "local",
|
2026-08-19 14:12:06 -05:00
|
|
|
}),
|
|
|
|
|
]);
|
|
|
|
|
});
|
2026-09-20 14:56:39 -05:00
|
|
|
|
|
|
|
|
it("hydrates a remote shared host for a local connection without persisting remote ids", async () => {
|
|
|
|
|
isElectron.mockReturnValue(true);
|
|
|
|
|
invoke.mockResolvedValue({ serverUrl: "https://termix.example" });
|
|
|
|
|
remoteApi.get.mockResolvedValue({
|
|
|
|
|
data: {
|
|
|
|
|
username: "recipient",
|
|
|
|
|
authType: "key",
|
|
|
|
|
key: "PRIVATE KEY",
|
|
|
|
|
keyPassword: "passphrase",
|
|
|
|
|
keyType: "ed25519",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await hydrateLocalSharedHostAuth({
|
|
|
|
|
id: -9,
|
|
|
|
|
isShared: true,
|
|
|
|
|
syncId: "shared-host",
|
|
|
|
|
credentialId: 77,
|
|
|
|
|
username: "owner",
|
|
|
|
|
authType: "key",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(remoteApi.get).toHaveBeenCalledWith(
|
|
|
|
|
"/host/db/host/9/local-connection-auth",
|
|
|
|
|
);
|
|
|
|
|
expect(result).toEqual(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
id: -9,
|
|
|
|
|
syncId: null,
|
|
|
|
|
credentialId: undefined,
|
|
|
|
|
username: "recipient",
|
|
|
|
|
key: "PRIVATE KEY",
|
|
|
|
|
keyPassword: "passphrase",
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("leaves local and owned hosts untouched", async () => {
|
|
|
|
|
const host = { id: 9, isShared: false, username: "root" };
|
|
|
|
|
await expect(hydrateLocalSharedHostAuth(host)).resolves.toBe(host);
|
|
|
|
|
expect(remoteApi.get).not.toHaveBeenCalled();
|
|
|
|
|
});
|
2026-08-19 14:12:06 -05:00
|
|
|
});
|