import { test } from "node:test" import assert from "node:assert" import lib from "../tailscalectl/lib.js" const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings } = lib test("parsePeers extracts exitNode from ExitNodeOption", () => { const peerMap = { "peer-1": { HostName: "router", TailscaleIPs: ["100.64.0.1"], Online: true, ExitNodeOption: true }, "peer-2": { HostName: "laptop", TailscaleIPs: ["100.64.0.2"], Online: true, ExitNodeOption: false } } const peers = parsePeers(peerMap) assert.strictEqual(peers[0].exitNode, true) assert.strictEqual(peers[1].exitNode, false) }) test("makeExitNodeCommand returns tailscale set command for hostname", () => { const cmd = makeExitNodeCommand("router") assert.deepStrictEqual(cmd, ["tailscale", "set", "--exit-node=router"]) }) test("makeExitNodeCommand with empty string clears exit node", () => { const cmd = makeExitNodeCommand("") assert.deepStrictEqual(cmd, ["tailscale", "set", "--exit-node="]) }) test("findActiveExitNode returns hostname of peer with ExitNode=true", () => { const peerMap = { "peer-1": { HostName: "gluetun-sjc", ExitNode: true, ExitNodeOption: true }, "peer-2": { HostName: "gluetun-den", ExitNode: false, ExitNodeOption: true } } assert.strictEqual(findActiveExitNode(peerMap), "gluetun-sjc") }) test("findActiveExitNode returns empty string when no exit node", () => { const peerMap = { "peer-1": { HostName: "laptop", ExitNode: false, ExitNodeOption: false } } assert.strictEqual(findActiveExitNode(peerMap), "") }) test("errorMessage returns user-friendly message for tailscale up failure", () => { const msg = errorMessage("up", 1) assert.strictEqual(msg, "Failed to connect to Tailscale") }) test("errorMessage returns user-friendly message for tailscale down failure", () => { const msg = errorMessage("down", 1) assert.strictEqual(msg, "Failed to disconnect from Tailscale") }) test("errorMessage returns user-friendly message for tailscale set failure", () => { const msg = errorMessage("set", 1) assert.strictEqual(msg, "Failed to set exit node") }) test("errorMessage returns user-friendly message for tailscale status failure", () => { const msg = errorMessage("status", 1) assert.strictEqual(msg, "Failed to read Tailscale status") }) test("errorMessage returns generic message for unknown command", () => { const msg = errorMessage("unknown", 1) assert.strictEqual(msg, "Tailscale command failed") }) // --- validateClipboardTool --- test("validateClipboardTool accepts whitelisted tool names", () => { assert.strictEqual(validateClipboardTool("dms"), true) assert.strictEqual(validateClipboardTool("wl-copy"), true) // clipmanctl intentionally dropped (see #49) }) test("validateClipboardTool rejects malicious inputs", () => { assert.strictEqual(validateClipboardTool("rm -rf /"), false) assert.strictEqual(validateClipboardTool("echo hi; malicious"), false) assert.strictEqual(validateClipboardTool("$(whoami)"), false) assert.strictEqual(validateClipboardTool("wl-copy || rm -rf /"), false) }) test("validateClipboardTool rejects empty and falsy inputs", () => { assert.strictEqual(validateClipboardTool(""), false) assert.strictEqual(validateClipboardTool(null), false) assert.strictEqual(validateClipboardTool(undefined), false) }) // --- buildCopyCommand --- test("buildCopyCommand returns safe command for whitelisted clipboard tool", () => { const cmd = buildCopyCommand("hello", "wl-copy") assert.ok(Array.isArray(cmd)) assert.deepStrictEqual(cmd, ["wl-copy", "hello"]) // direct argv, no sh -c (see #49) }) test("buildCopyCommand returns null for invalid clipboard tool", () => { assert.strictEqual(buildCopyCommand("hello", "rm -rf /"), null) assert.strictEqual(buildCopyCommand("hello", ""), null) assert.strictEqual(buildCopyCommand("hello", null), null) }) test("buildCopyCommand safely handles text with special characters", () => { const cmd = buildCopyCommand("it's a 'test' with \"quotes\" and\nnewlines", "wl-copy") assert.ok(Array.isArray(cmd)) }) // --- nextClipboardTool --- test("nextClipboardTool cycles through tools", () => { assert.strictEqual(nextClipboardTool("dms"), "wl-copy") assert.strictEqual(nextClipboardTool("wl-copy"), "dms") // clipmanctl dropped (#49) }) test("nextClipboardTool falls back to first tool for unknown input", () => { assert.strictEqual(nextClipboardTool(""), "dms") assert.strictEqual(nextClipboardTool("unknown"), "dms") }) // --- allClipboardTools --- test("allClipboardTools returns the list of safe tools", () => { const tools = allClipboardTools() assert.ok(Array.isArray(tools)) assert.strictEqual(tools.length, 2) assert.ok(tools.includes("dms")) assert.ok(tools.includes("wl-copy")) // clipmanctl intentionally removed (#49) }) // --- getStrings (for #34 i18n prep) --- test("getStrings returns canonical UI strings for the widget", () => { const s = getStrings() assert.ok(s.header) assert.ok(s.connected) assert.ok(s.disconnected) assert.ok(s.exitNodePrefix) assert.ok(s.copied) assert.ok(s.noClipboardTool) assert.ok(s.invalidClipboardCommand) }) test("getStrings.copied interpolates the text", () => { const s = getStrings() const msg = s.copied("100.64.0.5") assert.ok(msg.includes("100.64.0.5")) }) // --- buildToggleCommand --- test("buildToggleCommand returns down command when connected", () => { assert.deepStrictEqual(buildToggleCommand(true), ["tailscale", "down"]) }) test("buildToggleCommand returns up command when disconnected", () => { assert.deepStrictEqual(buildToggleCommand(false), ["tailscale", "up"]) }) test("buildToggleCommand treats null and undefined as disconnected", () => { assert.deepStrictEqual(buildToggleCommand(null), ["tailscale", "up"]) assert.deepStrictEqual(buildToggleCommand(undefined), ["tailscale", "up"]) }) // --- parseStatusResult --- test("parseStatusResult produces correct state from valid JSON", () => { const json = JSON.stringify({ BackendState: "Running", Self: { TailscaleIPs: ["100.64.0.5"] }, Peer: { "key-1": { HostName: "router", TailscaleIPs: ["100.64.0.1"], Online: true, ExitNode: true, ExitNodeOption: true } } }) const state = parseStatusResult(json) assert.strictEqual(state.isConnected, true) assert.strictEqual(state.tailscaleIP, "100.64.0.5") assert.strictEqual(state.currentExitNode, "router") assert.strictEqual(state.peers.length, 1) }) test("parseStatusResult returns safe defaults for invalid JSON", () => { const state = parseStatusResult("not json at all") assert.strictEqual(state.isConnected, false) assert.strictEqual(state.tailscaleIP, "") assert.strictEqual(state.currentExitNode, "") assert.strictEqual(state.peers.length, 0) }) test("parseStatusResult handles missing Self gracefully", () => { const json = JSON.stringify({ BackendState: "Running", Peer: {} }) const state = parseStatusResult(json) assert.strictEqual(state.isConnected, true) assert.strictEqual(state.tailscaleIP, "") }) test("parseStatusResult handles missing and empty Peer gracefully", () => { const json = JSON.stringify({ BackendState: "Running", Self: { TailscaleIPs: ["100.64.0.5"] } }) const state = parseStatusResult(json) assert.strictEqual(state.peers.length, 0) assert.strictEqual(state.currentExitNode, "") }) test("parseStatusResult sets isConnected false for non-Running BackendState", () => { const json = JSON.stringify({ BackendState: "NeedsLogin", Self: { TailscaleIPs: ["100.64.0.5"] }, Peer: {} }) const state = parseStatusResult(json) assert.strictEqual(state.isConnected, false) })