{"version":3,"file":"powershell.test.d.ts","sourceRoot":"","sources":["../../../src/core/tools/powershell.test.ts"],"names":[],"mappings":"","sourcesContent":["import { beforeEach, describe, expect, it } from \"vitest\";\nimport { parseArgs } from \"../../cli/args.js\";\nimport { getPowerShellConfig, resetShellConfigCache } from \"../../utils/shell.js\";\nimport {\n\tallTools,\n\tcreateLocalPowerShellOperations,\n\tcreatePowerShellTool,\n\ttype PowerShellOperations,\n\tresetPowerShellHealthCheck,\n} from \"./index.js\";\nimport { PowerStreamDecoder } from \"./powershell.js\";\n\nfunction shouldRunWindowsPowerShellFixture(platform: NodeJS.Platform, powerShellAvailable: boolean): boolean {\n\treturn platform === \"win32\" && powerShellAvailable;\n}\n\nfunction parseFixturePid(output: string): number {\n\tconst match = output.match(/BG_PID:(\\d+)/);\n\tif (!match) throw new Error(`Windows Start-Process fixture did not emit BG_PID marker. Output: ${output}`);\n\n\tconst pid = Number(match[1]);\n\tif (!Number.isSafeInteger(pid) || pid <= 0) {\n\t\tthrow new Error(`Windows Start-Process fixture emitted invalid PID: ${match[1]}`);\n\t}\n\treturn pid;\n}\n\n// ---------------------------------------------------------------------------\n// PowerStreamDecoder direct unit tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"PowerStreamDecoder\", () => {\n\tit(\"decodes normal UTF-8 stdout\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\tconst result = d.feed(Buffer.from(\"hello \", \"utf-8\")) + d.feed(Buffer.from(\"world\\n\", \"utf-8\")) + d.flush();\n\t\texpect(result).toBe(\"hello world\\n\");\n\t});\n\n\tit(\"decodes UTF-16LE with BOM on first chunk\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\tconst bom = Buffer.from([0xff, 0xfe]);\n\t\tconst payload = Buffer.from(\"hello from utf16le\\n\", \"utf-16le\");\n\t\tconst result = d.feed(Buffer.concat([bom, payload])) + d.feed(Buffer.from(\"more\\n\", \"utf-16le\")) + d.flush();\n\t\texpect(result).toBe(\"hello from utf16le\\nmore\\n\");\n\t});\n\n\tit(\"decodes UTF-16LE without BOM (NUL-pattern detection)\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\tconst asciiText = \"ERROR: something failed\\r\\n\";\n\t\tconst rawUtf16le = Buffer.from(asciiText, \"utf-16le\");\n\t\t// Verify NUL alternation pattern exists\n\t\texpect(rawUtf16le[1]).toBe(0x00);\n\n\t\tconst result = d.feed(rawUtf16le) + d.flush();\n\t\texpect(result).toBe(asciiText);\n\t});\n\n\tit(\"handles UTF-16LE chunk split at odd byte boundary\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\tconst text = \"ABCDEFGH\";\n\t\tconst bom = Buffer.from([0xff, 0xfe]);\n\t\tconst rawUtf16le = Buffer.from(text, \"utf-16le\");\n\t\tconst fullBuf = Buffer.concat([bom, rawUtf16le]); // 2 + 16 = 18 bytes\n\n\t\t// Split at byte 7 (odd boundary, cuts through BOM/payload)\n\t\tconst firstChunk = fullBuf.subarray(0, 7);\n\t\tconst secondChunk = fullBuf.subarray(7);\n\n\t\tconst result = d.feed(firstChunk) + d.feed(secondChunk) + d.flush();\n\t\texpect(result).toBe(text);\n\t});\n\n\tit(\"flushes remaining decoder state\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\tconst text = \"AB\"; // 2 chars = 4 bytes + 2 BOM = 6 bytes\n\t\tconst bom = Buffer.from([0xff, 0xfe]);\n\t\tconst rawUtf16le = Buffer.from(text, \"utf-16le\");\n\t\tconst fullBuf = Buffer.concat([bom, rawUtf16le]); // 2 + 4 = 6 bytes\n\n\t\t// Split at byte 5: BOM(2) + 'A'(2) + first byte of 'B'(1) = 5 bytes\n\t\t// Remaining: 1 byte (second byte of 'B')\n\t\tconst firstChunk = fullBuf.subarray(0, 5);\n\t\tconst secondChunk = fullBuf.subarray(5);\n\t\tconst result = d.feed(firstChunk) + d.feed(secondChunk) + d.flush();\n\t\texpect(result).toBe(text);\n\t});\n\n\tit(\"does not corrupt UTF-8 without BOM or NUL pattern\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\t// Pure ASCII UTF-8: no BOM, no NUL alternation\n\t\tconst text = \"normal ascii output\\n\";\n\t\tconst result = d.feed(Buffer.from(text, \"utf-8\")) + d.flush();\n\t\texpect(result).toBe(text);\n\t});\n\n\tit(\"hasData reflects whether any chunk was fed\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\texpect(d.hasData).toBe(false);\n\t\td.feed(Buffer.from(\"x\"));\n\t\texpect(d.hasData).toBe(true);\n\t});\n\n\tit(\"empty chunks produce empty string\", () => {\n\t\tconst d = new PowerStreamDecoder();\n\t\texpect(d.feed(Buffer.alloc(0))).toBe(\"\");\n\t\texpect(d.hasData).toBe(false);\n\t\texpect(d.flush()).toBe(\"\");\n\t});\n});\n\n// ---------------------------------------------------------------------------\n// powershell tool tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"powershell tool\", () => {\n\tbeforeEach(() => {\n\t\tresetPowerShellHealthCheck();\n\t});\n\n\tit(\"registers through the built-in tool path and CLI tool parsing\", () => {\n\t\texpect(allTools.powershell.name).toBe(\"powershell\");\n\t\texpect(parseArgs([\"--tools\", \"powershell\"]).tools).toEqual([\"powershell\"]);\n\t});\n\n\tit(\"sanitizes streamed and final output with powershell-tool parity\", async () => {\n\t\tconst updates: string[] = [];\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"\\u001b[36mcyan\\u001b[0m\\x00\\u0007text\\r\\nnext\\n\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\t\tconst result = await tool.execute(\"call_1\", { command: \"Write-Output test\" }, undefined, (partialResult) => {\n\t\t\tupdates.push(partialResult.content[0]?.type === \"text\" ? partialResult.content[0].text : \"\");\n\t\t});\n\n\t\texpect(updates).toEqual([\"cyantext\\nnext\\n\"]);\n\t\texpect(result.content).toEqual([{ type: \"text\", text: \"cyantext\\nnext\\n\" }]);\n\t\texpect(result.details).toBeUndefined();\n\t});\n\n\tit(\"returns structured cancellation instead of rejecting on abort\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"partial output\"));\n\t\t\t\tthrow new Error(\"aborted\");\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\t\tconst result = await tool.execute(\"call_2\", { command: \"Start-Sleep 1\" });\n\n\t\texpect(result.content).toEqual([{ type: \"text\", text: \"partial output\\n\\nCommand aborted\" }]);\n\t\texpect(result.details).toEqual({\n\t\t\ttruncation: undefined,\n\t\t\tfullOutputPath: undefined,\n\t\t\tcancelled: true,\n\t\t});\n\t});\n\n\tit(\"surfaces non-zero exit codes clearly\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"failure output\"));\n\t\t\t\treturn { exitCode: 7 };\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\n\t\tawait expect(tool.execute(\"call_3\", { command: \"throw 'boom'\" })).rejects.toThrow(\n\t\t\t\"failure output\\n\\nCommand exited with code 7\",\n\t\t);\n\t});\n\n\tit(\"fails honestly when PowerShell is unavailable on the host\", async () => {\n\t\tconst operations = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => {\n\t\t\t\tthrow new Error(\"PowerShell is not available on this system.\");\n\t\t\t},\n\t\t});\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\n\t\tawait expect(tool.execute(\"call_4\", { command: \"Get-Location\" })).rejects.toThrow(\n\t\t\t\"PowerShell is not available on this system.\",\n\t\t);\n\t});\n\n\tit(\"does not run health check when operations have no validate method (mock ops)\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"ok mock\\n\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t\t// No validate method - should skip health check\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\t\tconst result = await tool.execute(\"call_5\", { command: \"Get-Date\" });\n\n\t\texpect(result.content).toEqual([{ type: \"text\", text: \"ok mock\\n\" }]);\n\t});\n\n\tit(\"wraps the command with UTF-8 encoding preamble via exec helper\", async () => {\n\t\tlet receivedCommand = \"\";\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (command, _cwd, { onData }) => {\n\t\t\t\treceivedCommand = command;\n\t\t\t\tonData(Buffer.from(\"ok\\n\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\t\tawait tool.execute(\"call_6\", { command: \"Get-Date\" });\n\n\t\texpect(receivedCommand).toBe(\"Get-Date\");\n\t});\n\n\tit(\"returns (no output) only when truly empty, not when transport drops data\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, _opts) => {\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t\tvalidate: async () => ({ valid: true }),\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\t\tconst result = await tool.execute(\"call_7\", { command: \"$null\" });\n\n\t\texpect(result.content).toEqual([{ type: \"text\", text: \"(no output)\" }]);\n\t});\n\n\tit(\"rejects transport-broken health check before execution\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"should not reach\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t\tvalidate: async () => ({\n\t\t\t\tvalid: false,\n\t\t\t\terror: \"JENSEN_POWERSHELL_TRANSPORT_BROKEN\",\n\t\t\t}),\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\n\t\tawait expect(tool.execute(\"call_8\", { command: \"Write-Output test\" })).rejects.toThrow(\n\t\t\t\"JENSEN_POWERSHELL_TRANSPORT_BROKEN\",\n\t\t);\n\t});\n\n\tit(\"reports valid when validate probe produces the expected marker\", async () => {\n\t\tconst ops: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"JENSEN_PS_HEALTH_abcd1234\\n\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t\tvalidate: async (_cwd, _opts) => {\n\t\t\t\treturn new Promise((resolve) => {\n\t\t\t\t\tops.exec(`Write-Output 'JENSEN_PS_HEALTH_abcd1234'`, process.cwd(), {\n\t\t\t\t\t\tonData: () => {},\n\t\t\t\t\t}).then((_result) => {\n\t\t\t\t\t\tresolve({ valid: true });\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations: ops });\n\t\tconst result = await tool.execute(\"call_9\", { command: \"Get-Date\" });\n\t\texpect(result.content).toBeDefined();\n\t});\n\n\tit(\"validate method reports broken when probe exit 0 but no marker\", async () => {\n\t\tconst ops = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => ({\n\t\t\t\tshell: \"pwsh-test\",\n\t\t\t\targs: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\"],\n\t\t\t\tflavor: \"pwsh\",\n\t\t\t\twindowsHide: false,\n\t\t\t}),\n\t\t});\n\n\t\tops.exec = async (_command, _cwd, _opts) => {\n\t\t\treturn { exitCode: 0 };\n\t\t};\n\n\t\tif (ops.validate) {\n\t\t\tconst result = await ops.validate(process.cwd(), { timeout: 1 });\n\t\t\texpect(result.valid).toBe(false);\n\t\t\texpect(result.error).toContain(\"JENSEN_POWERSHELL_TRANSPORT_BROKEN\");\n\t\t}\n\t});\n\n\tit(\"validate method handles non-zero exit code from probe\", async () => {\n\t\tconst ops = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => ({\n\t\t\t\tshell: \"pwsh-test\",\n\t\t\t\targs: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\"],\n\t\t\t\tflavor: \"pwsh\",\n\t\t\t\twindowsHide: false,\n\t\t\t}),\n\t\t});\n\n\t\tops.exec = async (_command, _cwd, _opts) => {\n\t\t\treturn { exitCode: 1 };\n\t\t};\n\n\t\tif (ops.validate) {\n\t\t\tconst result = await ops.validate(process.cwd(), { timeout: 1 });\n\t\t\texpect(result.valid).toBe(false);\n\t\t\texpect(result.error).toContain(\"exit code 1\");\n\t\t}\n\t});\n\n\tit(\"health check is idempotent (cached after first call)\", async () => {\n\t\tlet validateCalls = 0;\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, { onData }) => {\n\t\t\t\tonData(Buffer.from(\"output\\n\"));\n\t\t\t\treturn { exitCode: 0 };\n\t\t\t},\n\t\t\tvalidate: async () => {\n\t\t\t\tvalidateCalls++;\n\t\t\t\treturn { valid: true };\n\t\t\t},\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\n\t\tawait tool.execute(\"call_a\", { command: \"Write-Output a\" });\n\t\texpect(validateCalls).toBe(1);\n\n\t\tawait tool.execute(\"call_b\", { command: \"Write-Output b\" });\n\t\texpect(validateCalls).toBe(1);\n\t});\n\n\tit(\"timeout is enforced with clear error message\", async () => {\n\t\tconst operations: PowerShellOperations = {\n\t\t\texec: async (_command, _cwd, _opts) => {\n\t\t\t\tthrow new Error(\"timeout:5\");\n\t\t\t},\n\t\t\tvalidate: async () => ({ valid: true }),\n\t\t};\n\n\t\tconst tool = createPowerShellTool(process.cwd(), { operations });\n\n\t\tawait expect(tool.execute(\"call_10\", { command: \"Start-Sleep 30\", timeout: 1 })).rejects.toThrow(\n\t\t\t\"timed out after 5 seconds\",\n\t\t);\n\t});\n});\n\n// ---------------------------------------------------------------------------\n// Health probe with real decoder path (mock exec that calls decoder internally)\n// ---------------------------------------------------------------------------\n\ndescribe(\"health probe with decoder\", () => {\n\tbeforeEach(() => {\n\t\tresetPowerShellHealthCheck();\n\t});\n\n\tit(\"health probe detects marker after normalization (UTF-8 input via decoder)\", async () => {\n\t\tconst ops = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => ({\n\t\t\t\tshell: \"test-shell\",\n\t\t\t\targs: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\"],\n\t\t\t\tflavor: \"pwsh\",\n\t\t\t\twindowsHide: false,\n\t\t\t}),\n\t\t});\n\n\t\t// The validate method generates a random marker and passes it in the command.\n\t\t// The mock must extract it from the command and emit it via onData.\n\t\tops.exec = async (command, _cwd, { onData }) => {\n\t\t\t// Command is like: \"[Console]::OutputEncoding=...;Write-Output 'JENSEN_PS_HEALTH_abcdef01'\"\n\t\t\tconst match = command.match(/JENSEN_PS_HEALTH_[a-f0-9]+/);\n\t\t\tif (match) {\n\t\t\t\tonData(Buffer.from(`${match[0]}\\r\\n`, \"utf-8\"));\n\t\t\t}\n\t\t\treturn { exitCode: 0 };\n\t\t};\n\n\t\tif (!ops.validate) throw new Error(\"validate not set\");\n\t\tconst result = await ops.validate(process.cwd(), { timeout: 1 });\n\t\texpect(result.valid).toBe(true);\n\t});\n\n\tit(\"health probe detects marker after normalization (UTF-16LE normalized to UTF-8)\", async () => {\n\t\tconst ops = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => ({\n\t\t\t\tshell: \"test-shell\",\n\t\t\t\targs: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\"],\n\t\t\t\tflavor: \"pwsh\",\n\t\t\t\twindowsHide: false,\n\t\t\t}),\n\t\t});\n\n\t\t// Simulate what the decoder emits after normalizing UTF-16LE → UTF-8:\n\t\t// the validate method reads from onData, which already received normalized UTF-8.\n\t\tops.exec = async (command, _cwd, { onData }) => {\n\t\t\tconst match = command.match(/JENSEN_PS_HEALTH_[a-f0-9]+/);\n\t\t\tif (match) {\n\t\t\t\tonData(Buffer.from(`${match[0]}\\r\\n`, \"utf-8\"));\n\t\t\t}\n\t\t\treturn { exitCode: 0 };\n\t\t};\n\n\t\tif (!ops.validate) throw new Error(\"validate not set\");\n\t\tconst result = await ops.validate(process.cwd(), { timeout: 1 });\n\t\texpect(result.valid).toBe(true);\n\t});\n\n\tit(\"validate rejects when exec returns empty output\", async () => {\n\t\tconst ops = createLocalPowerShellOperations({\n\t\t\tresolveConfig: () => ({\n\t\t\t\tshell: \"test-shell\",\n\t\t\t\targs: [\"-NoLogo\", \"-NoProfile\", \"-NonInteractive\", \"-Command\"],\n\t\t\t\tflavor: \"pwsh\",\n\t\t\t\twindowsHide: false,\n\t\t\t}),\n\t\t});\n\n\t\tops.exec = async (_command, _cwd, _opts) => {\n\t\t\treturn { exitCode: 0 };\n\t\t};\n\n\t\tif (!ops.validate) throw new Error(\"validate not set\");\n\t\tconst result = await ops.validate(process.cwd(), { timeout: 1 });\n\t\texpect(result.valid).toBe(false);\n\t\texpect(result.error).toContain(\"JENSEN_POWERSHELL_TRANSPORT_BROKEN\");\n\t});\n});\n\n// ---------------------------------------------------------------------------\n// Real PowerShell integration tests. Portable host behavior runs anywhere\n// PowerShell is installed; Windows Start-Process semantics run only on Windows.\n// ---------------------------------------------------------------------------\n\ndescribe(\"powershell windows integration\", () => {\n\tbeforeEach(() => {\n\t\tresetPowerShellHealthCheck();\n\t});\n\n\t// Run portable tests when pwsh is available (Windows, Linux, or macOS).\n\tlet pwshAvailable = false;\n\ttry {\n\t\tresetShellConfigCache();\n\t\tgetPowerShellConfig();\n\t\tpwshAvailable = true;\n\t} catch {\n\t\t// pwsh not available — skip real-process integration tests\n\t}\n\n\tconst itPwsh = pwshAvailable ? it : it.skip;\n\tconst itWindowsPowerShell = shouldRunWindowsPowerShellFixture(process.platform, pwshAvailable) ? it : it.skip;\n\n\tit(\"classifies Windows Start-Process fixtures by platform and host availability\", () => {\n\t\texpect(shouldRunWindowsPowerShellFixture(\"linux\", true)).toBe(false);\n\t\texpect(shouldRunWindowsPowerShellFixture(\"darwin\", true)).toBe(false);\n\t\texpect(shouldRunWindowsPowerShellFixture(\"win32\", false)).toBe(false);\n\t\texpect(shouldRunWindowsPowerShellFixture(\"win32\", true)).toBe(true);\n\t});\n\n\tit(\"validates fixture PID markers before process cleanup\", () => {\n\t\texpect(parseFixturePid(\"BG_PID:1234\\n\")).toBe(1234);\n\t\texpect(() => parseFixturePid(\"BEFORE_WRAPPER_EXIT\\n\")).toThrow(\n\t\t\t\"Windows Start-Process fixture did not emit BG_PID marker\",\n\t\t);\n\t\texpect(() => parseFixturePid(\"BG_PID:0\\n\")).toThrow(\"Windows Start-Process fixture emitted invalid PID\");\n\t});\n\n\titPwsh(\"real pwsh produces stdout marker\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tconst chunks: string[] = [];\n\n\t\tconst result = await ops.exec(\"Write-Output '**JENSEN_IT_STDOUT**'\", process.cwd(), {\n\t\t\tonData: (data) => chunks.push(data.toString(\"utf-8\")),\n\t\t\ttimeout: 10,\n\t\t});\n\n\t\texpect(result.exitCode).toBe(0);\n\t\tconst output = chunks.join(\"\");\n\t\texpect(output).toContain(\"**JENSEN_IT_STDOUT**\");\n\t});\n\n\titPwsh(\"real pwsh produces stderr marker\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tconst chunks: string[] = [];\n\n\t\tconst result = await ops.exec(\"[Console]::Error.WriteLine('**JENSEN_IT_STDERR**')\", process.cwd(), {\n\t\t\tonData: (data) => chunks.push(data.toString(\"utf-8\")),\n\t\t\ttimeout: 10,\n\t\t});\n\n\t\texpect(result.exitCode).toBe(0);\n\t\tconst output = chunks.join(\"\");\n\t\texpect(output).toContain(\"**JENSEN_IT_STDERR**\");\n\t});\n\n\titPwsh(\"real pwsh handles Unicode\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tconst chunks: string[] = [];\n\n\t\tconst result = await ops.exec(\"Write-Output 'áéíóú ñ Ñ € — ✓'\", process.cwd(), {\n\t\t\tonData: (data) => chunks.push(data.toString(\"utf-8\")),\n\t\t\ttimeout: 10,\n\t\t});\n\n\t\texpect(result.exitCode).toBe(0);\n\t\tconst output = chunks.join(\"\");\n\t\texpect(output).toContain(\"áéíóú\");\n\t\texpect(output).toContain(\"ñ\");\n\t\texpect(output).toContain(\"€\");\n\t\texpect(output).toContain(\"✓\");\n\t});\n\n\titPwsh(\"suppresses progress serialization without changing ordinary output\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tconst chunks: string[] = [];\n\n\t\tconst result = await ops.exec(\n\t\t\t\"Write-Progress -Activity 'fixture' -Status 'running' -PercentComplete 50; Write-Output '**JENSEN_IT_PROGRESS**'\",\n\t\t\tprocess.cwd(),\n\t\t\t{\n\t\t\t\tonData: (data) => chunks.push(data.toString(\"utf-8\")),\n\t\t\t\ttimeout: 10,\n\t\t\t},\n\t\t);\n\n\t\texpect(result.exitCode).toBe(0);\n\t\tconst output = chunks.join(\"\");\n\t\texpect(output).toContain(\"**JENSEN_IT_PROGRESS**\");\n\t\texpect(output).not.toContain(\"#< CLIXML\");\n\t});\n\n\titWindowsPowerShell(\"returns after Start-Process wrapper exit while child stays alive\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tconst chunks: string[] = [];\n\t\tconst startedAt = Date.now();\n\t\tlet pid: number | undefined;\n\t\ttry {\n\t\t\tconst result = await ops.exec(\n\t\t\t\t\"$out = Join-Path $env:TEMP 'jensen lifecycle path with spaces.out.log'; $err = Join-Path $env:TEMP 'jensen lifecycle path with spaces.err.log'; Remove-Item $out,$err -Force -ErrorAction SilentlyContinue; $child = Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoLogo','-NoProfile','-NonInteractive','-Command','Start-Sleep -Seconds 5') -WindowStyle Hidden -RedirectStandardOutput $out -RedirectStandardError $err -PassThru; Write-Output \\\"BG_PID:$($child.Id)\\\"; Write-Output 'BEFORE_WRAPPER_EXIT_✓'\",\n\t\t\t\tprocess.cwd(),\n\t\t\t\t{\n\t\t\t\t\tonData: (data) => chunks.push(data.toString(\"utf-8\")),\n\t\t\t\t\ttimeout: 10,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\texpect(result.exitCode).toBe(0);\n\t\t\texpect(Date.now() - startedAt).toBeLessThan(2000);\n\t\t\tconst output = chunks.join(\"\");\n\t\t\texpect(output).toContain(\"BEFORE_WRAPPER_EXIT_✓\");\n\t\t\texpect(output).not.toContain(\"#< CLIXML\");\n\t\t\tpid = parseFixturePid(output);\n\n\t\t\tconst followUpChunks: string[] = [];\n\t\t\tconst followUp = await ops.exec(\n\t\t\t\t`if (Get-Process -Id ${pid} -ErrorAction SilentlyContinue) { Write-Output 'CHILD_ALIVE' }`,\n\t\t\t\tprocess.cwd(),\n\t\t\t\t{ onData: (data) => followUpChunks.push(data.toString(\"utf-8\")), timeout: 10 },\n\t\t\t);\n\t\t\texpect(followUp.exitCode).toBe(0);\n\t\t\texpect(followUpChunks.join(\"\")).toContain(\"CHILD_ALIVE\");\n\t\t} finally {\n\t\t\tif (pid !== undefined) {\n\t\t\t\tawait ops.exec(`Stop-Process -Id ${pid} -Force -ErrorAction SilentlyContinue`, process.cwd(), {\n\t\t\t\t\tonData: () => {},\n\t\t\t\t\ttimeout: 10,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\titPwsh(\"real pwsh health probe validates\", async () => {\n\t\tconst ops = createLocalPowerShellOperations();\n\t\tif (!ops.validate) throw new Error(\"validate not set\");\n\n\t\tconst result = await ops.validate(process.cwd(), { timeout: 10 });\n\t\texpect(result.valid).toBe(true);\n\t});\n\n\titPwsh(\n\t\t\"real pwsh timeout is enforced\",\n\t\tasync () => {\n\t\t\tconst ops = createLocalPowerShellOperations();\n\n\t\t\tawait expect(\n\t\t\t\tops.exec(\"Start-Sleep -Seconds 20\", process.cwd(), {\n\t\t\t\t\tonData: () => {},\n\t\t\t\t\ttimeout: 3,\n\t\t\t\t}),\n\t\t\t).rejects.toThrow(\"timeout:3\");\n\t\t},\n\t\t15000,\n\t);\n});\n"]}