mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
146 lines
4.2 KiB
TypeScript
146 lines
4.2 KiB
TypeScript
/**
|
|
* 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<string, unknown>(),
|
|
writes: [] as Array<[string, number, string, unknown]>,
|
|
hooks: new Map<string, unknown>(),
|
|
manifests: [] as PluginManifest[],
|
|
visible: null as Record<string, Record<string, unknown>> | null,
|
|
viewerCalls: [] as Array<[Record<string, unknown>, string | undefined]>,
|
|
}));
|
|
|
|
vi.mock("../../database/routes/host-plugin-settings.js", () => ({
|
|
hostSettingsPlugins: () => h.manifests,
|
|
withHostPluginSettings: async (
|
|
host: Record<string, unknown>,
|
|
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([]);
|
|
});
|
|
});
|