{"version":3,"file":"shell-remote-ps.test.d.ts","sourceRoot":"","sources":["../../src/utils/shell-remote-ps.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n\tbuildRemotePowerShellArgs,\n\tbuildSimpleRemotePowerShellArgs,\n\tencodePowerShellCommand,\n\tgetPowerShellConfig,\n\tREMOTE_POWERSHELL_PREAMBLE,\n\tvalidateSshHost,\n} from \"./shell.js\";\n\n// ============================================================================\n// encodePowerShellCommand tests\n// ============================================================================\n\ndescribe(\"encodePowerShellCommand\", () => {\n\tit(\"produces a non-empty Base64 string\", () => {\n\t\tconst result = encodePowerShellCommand(\"Write-Output 'hello'\");\n\t\texpect(result.length).toBeGreaterThan(0);\n\t\t// Valid Base64\n\t\texpect(result).toMatch(/^[A-Za-z0-9+/]+=*$/);\n\t});\n\n\tit(\"encodes simple ASCII command deterministically\", () => {\n\t\tconst a = encodePowerShellCommand(\"exit 0\");\n\t\tconst b = encodePowerShellCommand(\"exit 0\");\n\t\texpect(a).toBe(b);\n\t});\n\n\tit(\"handles empty string\", () => {\n\t\tconst result = encodePowerShellCommand(\"\");\n\t\texpect(result).toBe(\"\"); // No BOM, empty source → empty buffer\n\t});\n\n\tit(\"handles Unicode characters (cafe, Chinese, emoji)\", () => {\n\t\t// café ñ 你好 🚀\n\t\tconst source = \"Write-Output 'caf\\u00e9 \\u00f1 \\u4f60\\u597d \\uD83D\\uDE80'\";\n\t\tconst result = encodePowerShellCommand(source);\n\t\texpect(result.length).toBeGreaterThan(0);\n\t\t// Verify we can decode it back\n\t\tconst buf = Buffer.from(result, \"base64\");\n\t\t// No BOM — length is exactly source.length * 2\n\t\texpect(buf.length).toBe(source.length * 2);\n\t});\n\n\tit(\"handles special PowerShell characters ($, {, }, ;)\", () => {\n\t\tconst source = '$x = @{ key = \"value\" }; Write-Output $x.key';\n\t\tconst result = encodePowerShellCommand(source);\n\t\texpect(result.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"handles backslashes in paths\", () => {\n\t\tconst source = \"Test-Path 'C:\\\\\\\\Program Files\\\\\\\\Git'\";\n\t\tconst result = encodePowerShellCommand(source);\n\t\texpect(result.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"handles double quotes inside single-quoted strings\", () => {\n\t\tconst source = \"Write-Output 'he said \\\"hello\\\"'\";\n\t\tconst result = encodePowerShellCommand(source);\n\t\texpect(result.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"handles multiline scripts\", () => {\n\t\tconst source = [\"$sum = 0\", \"for ($i = 1; $i -le 5; $i++) { $sum += $i }\", \"Write-Output $sum\", \"exit 0\"].join(\n\t\t\t\"\\n\",\n\t\t);\n\t\tconst result = encodePowerShellCommand(source);\n\t\texpect(result.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"does not include shell quoting in output\", () => {\n\t\tconst result = encodePowerShellCommand(\"echo hello\");\n\t\t// Should be pure Base64, no quotes\n\t\texpect(result).not.toContain(\"'\");\n\t\texpect(result).not.toContain('\"');\n\t});\n\n\tit(\"is pure — does not execute anything\", () => {\n\t\t// Just encoding, no side effects\n\t\tconst result = encodePowerShellCommand(\"Remove-Item -Path / -Recurse -Force\");\n\t\texpect(result.length).toBeGreaterThan(0);\n\t});\n});\n\n// ============================================================================\n// buildRemotePowerShellArgs tests\n// ============================================================================\n\ndescribe(\"buildRemotePowerShellArgs\", () => {\n\tit(\"returns argv array with -- separator and host\", () => {\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", \"Write-Output 'hello'\");\n\t\texpect(Array.isArray(args)).toBe(true);\n\t\texpect(args[0]).toBe(\"--\");\n\t\texpect(args[1]).toBe(\"test-host\");\n\t});\n\n\tit(\"includes powershell.exe with correct flags\", () => {\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", \"exit 0\");\n\t\texpect(args[2]).toBe(\"powershell.exe\");\n\t\texpect(args).toContain(\"-NoProfile\");\n\t\texpect(args).toContain(\"-NonInteractive\");\n\t\texpect(args).toContain(\"-EncodedCommand\");\n\t});\n\n\tit(\"does NOT include -Command flag when using EncodedCommand\", () => {\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", \"exit 0\");\n\t\texpect(args).not.toContain(\"-Command\");\n\t});\n\n\tit(\"prepends the preamble to the command\", () => {\n\t\tconst command = \"Write-Output 'test'\";\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", command);\n\t\t// The encoded payload contains the preamble + command\n\t\tconst encoded = args[args.length - 1];\n\t\texpect(typeof encoded).toBe(\"string\");\n\t\texpect(encoded.length).toBeGreaterThan(0);\n\t});\n\n\tit(\"encoded payload starts with preamble\", () => {\n\t\tconst command = \"exit 0\";\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", command);\n\t\tconst encoded = args[args.length - 1];\n\t\t// Decode and verify exact preamble structure\n\t\tconst buf = Buffer.from(encoded, \"base64\");\n\t\tconst decoded = buf.toString(\"utf16le\");\n\t\texpect(decoded.startsWith(\"$ErrorActionPreference\")).toBe(true);\n\t\texpect(decoded.codePointAt(0)).toBe(\"$\".codePointAt(0));\n\t\texpect(decoded).not.toContain(\"\\uFEFF\");\n\t\texpect(decoded).toContain(\"$ProgressPreference\");\n\t\texpect(decoded).toContain(\"exit 0\");\n\t});\n\n\tit(\"encoded payload fails if $ is lost or shifted\", () => {\n\t\t// Verify that the test correctly detects corruption of the first character\n\t\tconst command = \"exit 0\";\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", command);\n\t\tconst encoded = args[args.length - 1];\n\t\tconst buf = Buffer.from(encoded, \"base64\");\n\n\t\t// Corrupt the first byte to simulate BOM corruption\n\t\tconst corrupted = Buffer.concat([Buffer.from([0xff, 0xfe]), buf.subarray(2)]);\n\t\tconst decodedCorrupt = corrupted.toString(\"utf16le\");\n\t\texpect(decodedCorrupt.startsWith(\"$\")).toBe(false);\n\t\texpect(decodedCorrupt.startsWith(\"\\uFEFF\")).toBe(true);\n\n\t\t// Corrupt by shifting first byte to simulate dropped byte\n\t\tconst shifted = buf.subarray(1);\n\t\t// Adding a trailing zero to keep valid UTF-16LE alignment\n\t\tconst shiftedAligned = Buffer.concat([shifted, Buffer.from([0x00])]);\n\t\tconst decodedShift = shiftedAligned.toString(\"utf16le\");\n\t\texpect(decodedShift.startsWith(\"$\")).toBe(false);\n\t});\n\n\tit(\"encoded payload rejects empty source\", () => {\n\t\tconst encoded = encodePowerShellCommand(\"\");\n\t\texpect(encoded).toBe(\"\");\n\t});\n\n\tit(\"encoded payload handles emoji via surrogate pairs\", () => {\n\t\tconst source = \"Write-Output '\\uD83D\\uDE80'\";\n\t\tconst encoded = encodePowerShellCommand(source);\n\t\tconst buf = Buffer.from(encoded, \"base64\");\n\t\tconst decoded = buf.toString(\"utf16le\");\n\t\texpect(decoded).toContain(\"\\uD83D\\uDE80\");\n\t\texpect(decoded).toBe(source);\n\t});\n\n\tit(\"encoded payload handles Unicode non-BMP\", () => {\n\t\tconst source = \"''\"; // empty string literal\n\t\tconst encoded = encodePowerShellCommand(source);\n\t\tconst buf = Buffer.from(encoded, \"base64\");\n\t\tconst decoded = buf.toString(\"utf16le\");\n\t\texpect(decoded).toBe(source);\n\t});\n\n\tit(\"does not hardcode any hostname\", () => {\n\t\tconst args1 = buildRemotePowerShellArgs(\"host-a\", \"exit 0\");\n\t\tconst args2 = buildRemotePowerShellArgs(\"host-b\", \"exit 0\");\n\t\texpect(args1[1]).toBe(\"host-a\");\n\t\texpect(args2[1]).toBe(\"host-b\");\n\t});\n\n\tit(\"does not contain any LotG paths or hostnames\", () => {\n\t\tconst args = buildRemotePowerShellArgs(\"example-host\", \"exit 0\");\n\t\tconst full = args.join(\" \");\n\t\texpect(full).not.toContain(\"blackpearl\");\n\t\texpect(full).not.toContain(\"mmo-client\");\n\t\texpect(full).not.toContain(\"django-mmo\");\n\t\texpect(full).not.toContain(\"light-of-the-galaxy\");\n\t\texpect(full).not.toMatch(/D:/);\n\t});\n});\n\n// ============================================================================\n// validateSshHost tests\n// ============================================================================\n\ndescribe(\"validateSshHost\", () => {\n\tit(\"accepts legitimate hostname\", () => {\n\t\texpect(() => validateSshHost(\"test-host\")).not.toThrow();\n\t});\n\n\tit(\"accepts user@host\", () => {\n\t\texpect(() => validateSshHost(\"user@host\")).not.toThrow();\n\t});\n\n\tit(\"accepts IPv4 address\", () => {\n\t\texpect(() => validateSshHost(\"192.168.1.1\")).not.toThrow();\n\t});\n\n\tit(\"accepts IPv6 address\", () => {\n\t\texpect(() => validateSshHost(\"[::1]\")).not.toThrow();\n\t});\n\n\tit(\"accepts host-alias\", () => {\n\t\texpect(() => validateSshHost(\"my-server-alias\")).not.toThrow();\n\t});\n\n\tit(\"rejects empty host\", () => {\n\t\texpect(() => validateSshHost(\"\")).toThrow(\"must not be empty\");\n\t});\n\n\tit(\"rejects host starting with - (option injection)\", () => {\n\t\texpect(() => validateSshHost(\"-oProxyCommand=evil\")).toThrow(\"starts with\");\n\t});\n\n\tit(\"rejects host starting with - followed by legitimate name\", () => {\n\t\texpect(() => validateSshHost(\"-host.example.com\")).toThrow(\"starts with\");\n\t});\n\n\tit(\"rejects host with spaces\", () => {\n\t\texpect(() => validateSshHost(\"host with spaces\")).toThrow(\"whitespace\");\n\t});\n\n\tit(\"rejects host with newlines\", () => {\n\t\texpect(() => validateSshHost(\"host\\ninjected\")).toThrow(\"whitespace\");\n\t});\n\n\tit(\"rejects host with tab\", () => {\n\t\texpect(() => validateSshHost(\"host\\tinjected\")).toThrow(\"whitespace\");\n\t});\n\n\tit(\"-- separator prevents option interpretation\", () => {\n\t\tconst args = buildRemotePowerShellArgs(\"test-host\", \"exit 0\");\n\t\texpect(args[0]).toBe(\"--\");\n\t\texpect(args[1]).toBe(\"test-host\");\n\t});\n});\n\n// ============================================================================\n// buildSimpleRemotePowerShellArgs tests\n// ============================================================================\n\ndescribe(\"buildSimpleRemotePowerShellArgs\", () => {\n\tit(\"uses -Command not -EncodedCommand\", () => {\n\t\tconst args = buildSimpleRemotePowerShellArgs(\"test-host\", \"Write-Output 'hello'\");\n\t\texpect(args).toContain(\"-Command\");\n\t\texpect(args).not.toContain(\"-EncodedCommand\");\n\t});\n\n\tit(\"includes the command as the last argument\", () => {\n\t\tconst args = buildSimpleRemotePowerShellArgs(\"test-host\", \"Get-Date\");\n\t\texpect(args[args.length - 1]).toBe(\"Get-Date\");\n\t});\n\n\tit(\"includes non-interactive flags\", () => {\n\t\tconst args = buildSimpleRemotePowerShellArgs(\"test-host\", \"exit 0\");\n\t\texpect(args).toContain(\"-NoProfile\");\n\t\texpect(args).toContain(\"-NonInteractive\");\n\t});\n});\n\n// ============================================================================\n// REMOTE_POWERSHELL_PREAMBLE tests\n// ============================================================================\n\ndescribe(\"REMOTE_POWERSHELL_PREAMBLE\", () => {\n\tit(\"contains ErrorActionPreference Stop\", () => {\n\t\texpect(REMOTE_POWERSHELL_PREAMBLE).toContain(\"$ErrorActionPreference = 'Stop'\");\n\t});\n\n\tit(\"contains ProgressPreference SilentlyContinue\", () => {\n\t\texpect(REMOTE_POWERSHELL_PREAMBLE).toContain(\"$ProgressPreference = 'SilentlyContinue'\");\n\t});\n\n\tit(\"contains OutputEncoding UTF8\", () => {\n\t\texpect(REMOTE_POWERSHELL_PREAMBLE).toContain(\"[Console]::OutputEncoding\");\n\t\texpect(REMOTE_POWERSHELL_PREAMBLE).toContain(\"UTF8Encoding\");\n\t});\n\n\tit(\"is a single string joined by semicolons\", () => {\n\t\texpect(REMOTE_POWERSHELL_PREAMBLE).toContain(\"; \");\n\t});\n});\n\n// ============================================================================\n// Cross-platform integration: encoder + existing shell config\n// ============================================================================\n\ndescribe(\"encoder integration with shell config\", () => {\n\tit(\"encoded command works with getPowerShellConfig flags\", () => {\n\t\t// getPowerShellConfig throws on non-Windows without pwsh — skip gracefully\n\t\tif (process.platform !== \"win32\") {\n\t\t\treturn;\n\t\t}\n\t\tconst config = getPowerShellConfig();\n\t\t// The config should exist and have expected flags\n\t\texpect(config.args).toContain(\"-NoLogo\");\n\t\texpect(config.args).toContain(\"-NoProfile\");\n\t\texpect(config.args).toContain(\"-NonInteractive\");\n\t});\n\n\tit(\"encoded strings are valid for both pwsh and powershell.exe\", () => {\n\t\t// Both pwsh and Windows PowerShell accept UTF-16LE Base64 without BOM\n\t\tconst encoded = encodePowerShellCommand(\"Write-Output 'portable'\");\n\t\texpect(encoded).toBeTruthy();\n\t\t// Verify the decoder doesn't throw\n\t\tconst buf = Buffer.from(encoded, \"base64\");\n\t\texpect(buf.length).toBeGreaterThan(0);\n\t\t// No BOM — first byte is part of first character\n\t});\n});\n"]}