/** * Host plugin settings over remote sync: non-secret values travel with the * host, and a plugin can translate a local row id to its syncId and back. */ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PluginManifest } from "@termix-ssh/plugin-sdk/manifest"; const h = vi.hoisted(() => ({ stored: new Map(), writes: [] as Array<[string, number, string, unknown]>, hooks: new Map(), manifests: [] as PluginManifest[], visible: null as Record> | null, viewerCalls: [] as Array<[Record, string | undefined]>, })); vi.mock("../../database/routes/host-plugin-settings.js", () => ({ hostSettingsPlugins: () => h.manifests, withHostPluginSettings: async ( host: Record, viewerId?: string, ) => { h.viewerCalls.push([host, viewerId]); return h.visible ? { ...host, pluginSettings: h.visible } : host; }, })); vi.mock("../../plugins/registry.js", () => ({ consume: (key: string) => h.hooks.get(key), })); vi.mock("../../utils/logger.js", () => ({ databaseLogger: { warn: vi.fn(), info: vi.fn(), error: vi.fn() }, })); vi.mock("../../plugins/settings.js", () => ({ declaredFields: (manifest: PluginManifest) => manifest.contributes!.settings!.host!.fields, getAllSettings: async ( manifest: PluginManifest, _scope: string, id: number, ) => Object.fromEntries( manifest.contributes!.settings!.host!.fields.map((field) => [ field.key, h.stored.get(`${manifest.id}:${id}:${field.key}`), ]), ), setSetting: async ( manifest: PluginManifest, _scope: string, id: number, key: string, value: unknown, ) => { h.writes.push([manifest.id, id, key, value]); return null; }, })); const { exportHostPluginSettings, importHostPluginSettings } = await import("../../sync/host-plugin-settings.js"); const VAULT = { id: "vault", contributes: { settings: { host: { fields: [ { key: "profileId", type: "number", labelKey: "k" }, { key: "token", type: "secret", labelKey: "k" }, ], }, }, }, } as unknown as PluginManifest; const TUNNELS = { id: "tunnels", contributes: { settings: { host: { fields: [ { key: "enableTunnel", type: "boolean", labelKey: "k" }, { key: "tunnelConnections", type: "custom", labelKey: "k" }, ], }, }, }, } as unknown as PluginManifest; beforeEach(() => { h.stored.clear(); h.writes.length = 0; h.hooks.clear(); h.manifests = [VAULT]; h.visible = null; h.viewerCalls.length = 0; h.hooks.set("vault.hostSettingsSync", { exportValue: (key: string, value: unknown) => key === "profileId" && value === 4 ? "profile-sync-4" : value, importValue: (key: string, value: unknown) => key === "profileId" && value === "profile-sync-4" ? 11 : value, }); }); describe("host plugin settings over sync", () => { it("exports non-secret values through the plugin's translation", async () => { h.stored.set("vault:1:profileId", 4); h.stored.set("vault:1:token", "secret"); expect(await exportHostPluginSettings(1)).toEqual({ vault: { profileId: "profile-sync-4" }, }); }); it("exports only what a shared host's viewer sees", async () => { h.manifests = [VAULT, TUNNELS]; h.visible = { vault: { token: { set: true } }, tunnels: { enableTunnel: true, tunnelConnections: [{ sourcePort: 22 }] }, }; expect( await exportHostPluginSettings(1, { userId: "viewer", permissionLevel: "connect", }), ).toEqual({ tunnels: { enableTunnel: true, tunnelConnections: [{ sourcePort: 22 }] }, }); expect(h.viewerCalls).toEqual([ [{ id: 1, isShared: true, permissionLevel: "connect" }, "viewer"], ]); }); it("imports them back to local ids", async () => { await importHostPluginSettings(9, { vault: { profileId: "profile-sync-4", token: "ignored" }, }); expect(h.writes).toEqual([["vault", 9, "profileId", 11]]); }); it("ignores a payload without plugin settings", async () => { await importHostPluginSettings(9, undefined); expect(h.writes).toEqual([]); }); });