Files
NetMesh/domain/vaultImport/csvExport.test.ts

177 lines
5.6 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import test from "node:test";
import { importVaultHostsFromText } from "../vaultImport.ts";
import { exportHostsToCsvWithStats } from "./csvExport.ts";
import type { Host } from "../models.ts";
test("CSV exports include a UTF-8 BOM and preserve Chinese text when imported again", () => {
const host: Host = {
id: "host-1",
label: "中文服务器",
hostname: "10.0.0.1",
username: "root",
port: 22,
};
const { csv } = exportHostsToCsvWithStats([host]);
assert.equal(csv.charCodeAt(0), 0xfeff);
assert.deepEqual([...new TextEncoder().encode(csv).slice(0, 3)], [0xef, 0xbb, 0xbf]);
const imported = importVaultHostsFromText("csv", csv);
assert.equal(imported.hosts[0]?.label, host.label);
assert.equal(imported.hosts[0]?.hostname, host.hostname);
});
test("CSV round-trips local key authentication and its saved passphrase", () => {
const host: Host = {
id: "host-key",
label: "Key host",
hostname: "key.example.com",
username: "ubuntu",
port: 22,
identityFilePaths: ["~/.ssh/id_ed25519"],
authMethod: "key",
};
const { csv } = exportHostsToCsvWithStats([host], {
keyPassphrases: new Map([["~/.ssh/id_ed25519", "+secret"]]),
});
assert.equal(csv.includes(",+secret"), false);
const imported = importVaultHostsFromText("csv", csv);
assert.deepEqual(imported.hosts[0]?.identityFilePaths, ["~/.ssh/id_ed25519"]);
assert.equal(imported.hosts[0]?.authMethod, "key");
assert.equal(imported.hosts[0]?.password, undefined);
assert.deepEqual(imported.keyPassphrases, [{
hostId: imported.hosts[0]?.id,
keyPath: "~/.ssh/id_ed25519",
passphrase: "+secret",
}]);
});
test("CSV round-trips a referenced Keychain file path and saved passphrase", () => {
const host: Host = {
id: "host-reference-key",
label: "Reference key host",
hostname: "reference.example.com",
username: "ubuntu",
port: 22,
identityFileId: "key-reference",
identityFilePaths: ["/Users/alice/.ssh/stale"],
authMethod: "key",
};
const keyPath = "/Users/alice/.ssh/id_ed25519";
const { csv } = exportHostsToCsvWithStats([host], {
keyPathsById: new Map([["key-reference", keyPath]]),
keyPassphrasesById: new Map([["key-reference", "reference-secret"]]),
keyPassphrases: new Map([[keyPath, "stale-side-store-secret"]]),
});
const imported = importVaultHostsFromText("csv", csv);
assert.deepEqual(imported.hosts[0]?.identityFilePaths, [keyPath]);
assert.deepEqual(imported.keyPassphrases, [{
hostId: imported.hosts[0]?.id,
keyPath,
passphrase: "reference-secret",
}]);
});
test("CSV never falls back to path storage for a referenced key", () => {
const host: Host = {
id: "host-reference-key",
label: "Reference key host",
hostname: "reference.example.com",
username: "ubuntu",
port: 22,
identityFileId: "key-reference",
authMethod: "key",
};
const keyPath = "/Users/alice/.ssh/id_ed25519";
const { csv } = exportHostsToCsvWithStats([host], {
keyPathsById: new Map([["key-reference", keyPath]]),
keyPassphrases: new Map([[keyPath, "stale-side-store-secret"]]),
});
const imported = importVaultHostsFromText("csv", csv);
assert.deepEqual(imported.hosts[0]?.identityFilePaths, [keyPath]);
assert.deepEqual(imported.keyPassphrases, []);
assert.equal(csv.includes("stale-side-store-secret"), false);
});
test("CSV reversibly guards key paths that spreadsheets treat as formulas", () => {
const hosts: Host[] = [
"-relative-key",
"'-literal-key",
"__netcatty_csv_keypath_v1__:literal",
].map((keyPath, index) => ({
id: `host-${index}`,
label: `Host ${index}`,
hostname: `host-${index}.example.com`,
username: "root",
port: 22,
identityFilePaths: [keyPath],
authMethod: "key",
}));
const { csv } = exportHostsToCsvWithStats(hosts);
const imported = importVaultHostsFromText("csv", csv);
assert.deepEqual(imported.hosts.map((host) => host.identityFilePaths?.[0]), [
"-relative-key",
"'-literal-key",
"__netcatty_csv_keypath_v1__:literal",
]);
});
test("CSV export never writes credentials from skipped serial hosts", () => {
const serialHost: Host = {
id: "serial-with-stale-key",
label: "Serial with stale key",
hostname: "ttyUSB0",
protocol: "serial",
port: 22,
identityFilePaths: ["~/.ssh/id_stale"],
authMethod: "key",
};
const sshHost: Host = {
id: "ssh-host",
label: "SSH host",
hostname: "ssh.example.com",
protocol: "ssh",
port: 22,
};
const result = exportHostsToCsvWithStats([serialHost, sshHost], {
keyPassphrases: new Map([["~/.ssh/id_stale", "must-not-leak"]]),
});
assert.equal(result.exportedCount, 1);
assert.equal(result.skippedCount, 1);
assert.equal(result.csv.includes(serialHost.label), false);
assert.equal(result.csv.includes("id_stale"), false);
assert.equal(result.csv.includes("must-not-leak"), false);
assert.equal(result.csv.includes(sshHost.hostname), true);
});
test("CSV export skips plugin hosts instead of discarding opaque provider configuration", () => {
const pluginHost: Host = {
id: "plugin-host",
label: "Plugin host",
hostname: "com.example.transport.connection",
username: "alice",
protocol: "plugin:com.example.transport.connection",
pluginConnection: {
providerId: "com.example.transport.connection",
configuration: { endpoint: "opaque://target" },
},
};
const result = exportHostsToCsvWithStats([pluginHost]);
assert.equal(result.exportedCount, 0);
assert.equal(result.skippedCount, 1);
assert.equal(result.csv.includes("opaque://target"), false);
});