import { createHash } from "node:crypto"; import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { describe, expect, test } from "bun:test"; import { readFileTransferConcurrency, runSshFileTransferOperation, type SshFileTransferCommandBuilders, type SshRemoteCommandExecutor, } from "./ssh-file-transfer"; import { parseSshInvocation } from "./ssh"; function fakeTransfer(content: Buffer): { executor: SshRemoteCommandExecutor; builders: SshFileTransferCommandBuilders; calls: string[] } { const calls: string[] = []; const sha256 = createHash("sha256").update(content).digest("hex"); return { calls, builders: { buildRouteCommand(_route, command) { return JSON.stringify(command); }, buildWindowsPowerShellCommand(script) { return script; }, }, executor: { streamInactivityTimeoutMs: 60_000, async runRemoteCommand(remoteCommand) { const command = JSON.parse(remoteCommand) as string[]; const marker = command.indexOf("unidesk-file-transfer"); const operation = command[marker + 1] ?? "unknown"; calls.push(operation); return operation === "stat" ? { exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" } : { exitCode: 0, stdout: "", stderr: "" }; }, }, }; } describe("ssh text transfer preflight", () => { test("reads transfer concurrency from YAML", () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-transfer-config-")); const configPath = join(dir, "unidesk-cli.yaml"); writeFileSync(configPath, "trans:\n fileTransfer:\n concurrency: 3\n transportOpenRetries: 2\n transportRetryDelayMs: 0\n"); try { expect(readFileTransferConcurrency(configPath)).toBe(3); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("rejects a known text download before remote work or local target creation", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-text-transfer-")); const target = join(dir, "result.md"); const fake = fakeTransfer(Buffer.from("text\n")); const invocation = parseSshInvocation("D601:/tmp", ["download", "/tmp/result.md", target]); try { await runSshFileTransferOperation(invocation, ["download", "/tmp/result.md", target], fake.executor, fake.builders); throw new Error("expected text download preflight to fail"); } catch (error) { const typed = error as Error & { details?: Record }; expect(typed.name).toBe("SshFileTransferError"); expect(typed.details?.code).toBe("text-transfer-discouraged"); expect(typed.details?.targetCreated).toBe(false); expect(typed.details?.remoteOperationStarted).toBe(false); expect(JSON.stringify(typed.details)).toContain("trans D601:/tmp cat"); expect(fake.calls).toEqual([]); expect(existsSync(target)).toBe(false); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("rejects a known text upload before reading the local source or starting remote work", async () => { const fake = fakeTransfer(Buffer.from("text\n")); const missingSource = "/tmp/unidesk-text-transfer-source-does-not-exist.md"; const invocation = parseSshInvocation("D601:/tmp", ["upload", missingSource, "/tmp/result.md"]); try { await runSshFileTransferOperation(invocation, ["upload", missingSource, "/tmp/result.md"], fake.executor, fake.builders); throw new Error("expected text upload preflight to fail"); } catch (error) { const typed = error as Error & { details?: Record }; expect(typed.name).toBe("SshFileTransferError"); expect(typed.details?.code).toBe("text-transfer-discouraged"); expect(JSON.stringify(typed.details)).toContain("apply-patch <<'PATCH'"); expect(fake.calls).toEqual([]); } }); test("keeps verified binary upload as the default transfer workflow", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-binary-transfer-")); const source = join(dir, "firmware.bin"); const content = Buffer.from([0, 1, 2, 3, 255]); writeFileSync(source, content); const fake = fakeTransfer(content); const invocation = parseSshInvocation("D601:/tmp", ["upload", source, "/tmp/firmware.bin"]); try { expect(await runSshFileTransferOperation(invocation, ["upload", source, "/tmp/firmware.bin"], fake.executor, fake.builders)).toBe(0); expect(fake.calls).toEqual(["write-b64-argv", "stat"]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("retries only pre-dispatch transport failures before running the transfer helper", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-transfer-open-retry-")); const source = join(dir, "firmware.bin"); const content = Buffer.from([0, 1, 2, 3]); const sha256 = createHash("sha256").update(content).digest("hex"); writeFileSync(source, content); let attempts = 0; const fake = fakeTransfer(content); fake.executor.runRemoteCommand = async (remoteCommand) => { const command = JSON.parse(remoteCommand) as string[]; const operation = command[command.indexOf("unidesk-file-transfer") + 1] ?? "unknown"; fake.calls.push(operation); if (operation === "write-b64-argv" && attempts++ === 0) { return { exitCode: 255, stdout: "", stderr: "unidesk remote frontend ssh bridge timed out waiting for provider session\n" }; } return operation === "stat" ? { exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" } : { exitCode: 0, stdout: "", stderr: "" }; }; const invocation = parseSshInvocation("D601:/tmp", ["upload", source, "/tmp/firmware.bin"]); try { expect(await runSshFileTransferOperation(invocation, ["upload", source, "/tmp/firmware.bin"], fake.executor, fake.builders)).toBe(0); expect(fake.calls).toEqual(["write-b64-argv", "write-b64-argv", "stat"]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("uploads files above one chunk with positional base64 blocks", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-chunked-transfer-")); const source = join(dir, "payload.bin"); const content = Buffer.alloc((3 * 1024 * 1024) + 17, 0xa5); writeFileSync(source, content); const fake = fakeTransfer(content); const invocation = parseSshInvocation("D601:/tmp", ["upload", source, "/tmp/payload.bin"]); try { expect(await runSshFileTransferOperation( invocation, ["upload", source, "/tmp/payload.bin"], fake.executor, fake.builders, )).toBe(0); expect(fake.calls).toEqual([ "write-b64-begin", "write-b64-block-stdin", "write-b64-block-stdin", "write-b64-commit", "stat", ]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("limits parallel upload blocks to the configured concurrency", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-parallel-upload-")); const source = join(dir, "payload.bin"); const content = Buffer.alloc((8 * 3 * 1024 * 1024) + 17, 0x5a); const sha256 = createHash("sha256").update(content).digest("hex"); writeFileSync(source, content); let active = 0; let maxActive = 0; let blocks = 0; const builders: SshFileTransferCommandBuilders = { buildRouteCommand(_route, command) { return JSON.stringify(command); }, buildWindowsPowerShellCommand(script) { return script; }, }; const executor: SshRemoteCommandExecutor = { streamInactivityTimeoutMs: 60_000, async runRemoteCommand(remoteCommand) { const command = JSON.parse(remoteCommand) as string[]; const operation = command[command.indexOf("unidesk-file-transfer") + 1]; if (operation === "stat") return { exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" }; if (operation === "write-b64-block-stdin") { blocks += 1; active += 1; maxActive = Math.max(maxActive, active); await Bun.sleep(10); active -= 1; } return { exitCode: 0, stdout: "", stderr: "" }; }, }; const invocation = parseSshInvocation("D601:/tmp", ["upload", source, "/tmp/payload.bin"]); try { expect(await runSshFileTransferOperation( invocation, ["upload", source, "/tmp/payload.bin"], executor, builders, )).toBe(0); expect(blocks).toBe(9); expect(maxActive).toBe(Math.min(readFileTransferConcurrency(), blocks)); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("allows an explicit generated-text override without bypassing verification", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-generated-transfer-")); const source = join(dir, "result.json"); const content = Buffer.from("{\"ok\":true}\n", "utf8"); writeFileSync(source, content); const fake = fakeTransfer(content); const invocation = parseSshInvocation("D601:/tmp", ["upload", "--allow-text-transfer", source, "/tmp/result.json"]); try { expect(await runSshFileTransferOperation( invocation, ["upload", "--allow-text-transfer", source, "/tmp/result.json"], fake.executor, fake.builders, )).toBe(0); expect(fake.calls).toEqual(["write-b64-argv", "stat"]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("streams Windows downloads through native PowerShell without a WSL mount path", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-win-download-")); const target = join(dir, "capture.png"); const content = Buffer.from([137, 80, 78, 71, 0, 255]); const sha256 = createHash("sha256").update(content).digest("hex"); const commands: string[] = []; const invocation = parseSshInvocation("G14-win:win/c/Users/lyon/.unidesk", [ "download", "capture.png", target, ]); const builders: SshFileTransferCommandBuilders = { buildRouteCommand(_route, command) { return JSON.stringify(command); }, buildWindowsPowerShellCommand(script) { return script; }, }; const executor: SshRemoteCommandExecutor = { streamInactivityTimeoutMs: 60_000, async runRemoteCommand(remoteCommand) { commands.push(remoteCommand); return { exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" }; }, async streamRemoteCommand(remoteCommand, handlers, _input, options) { commands.push(remoteCommand); expect(options?.inactivityTimeoutMs).toBe(60_000); await handlers.onStdout(content); return { exitCode: 0, stdout: "", stderr: "" }; }, }; try { expect(await runSshFileTransferOperation( invocation, ["download", "capture.png", target], executor, builders, )).toBe(0); expect(commands.some((command) => command.includes("[Console]::OpenStandardOutput"))).toBe(true); expect(commands.every((command) => !command.includes("/mnt/"))).toBe(true); expect(Buffer.from(await Bun.file(target).arrayBuffer())).toEqual(content); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("downloads configured ranges concurrently and reconstructs the original file", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-parallel-download-")); const target = join(dir, "payload.bin"); const content = Buffer.alloc(16 * 1024 * 1024, 0x3c); const sha256 = createHash("sha256").update(content).digest("hex"); let active = 0; let maxActive = 0; let ranges = 0; const invocation = parseSshInvocation("G14-win:win/c/Users/lyon", [ "download", ".unidesk/stress/payload.bin", target, ]); const builders: SshFileTransferCommandBuilders = { buildRouteCommand(_route, command) { return JSON.stringify(command); }, buildWindowsPowerShellCommand(script) { return script; }, }; const executor: SshRemoteCommandExecutor = { streamInactivityTimeoutMs: 60_000, async runRemoteCommand() { return { exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" }; }, async streamRemoteCommand(remoteCommand, handlers) { const offset = Number(remoteCommand.match(/\$offset = \[Int64\](\d+);/u)?.[1]); const length = Number(remoteCommand.match(/\$length = \[Int64\](\d+);/u)?.[1]); const firstLength = Math.floor(length / 2); ranges += 1; active += 1; maxActive = Math.max(maxActive, active); await Bun.sleep(10); await Promise.all([ handlers.onStdout(content.subarray(offset, offset + firstLength)), handlers.onStdout(content.subarray(offset + firstLength, offset + length)), ]); active -= 1; return { exitCode: 0, stdout: "", stderr: "" }; }, }; try { expect(await runSshFileTransferOperation( invocation, ["download", ".unidesk/stress/payload.bin", target], executor, builders, )).toBe(0); const expectedConcurrency = Math.min(readFileTransferConcurrency(), content.length / (1024 * 1024)); expect(ranges).toBe(expectedConcurrency); expect(maxActive).toBe(expectedConcurrency); expect(Buffer.from(await Bun.file(target).arrayBuffer())).toEqual(content); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("retries a download range only when transport fails before the first byte", async () => { const dir = mkdtempSync(join(tmpdir(), "unidesk-download-open-retry-")); const target = join(dir, "payload.bin"); const content = Buffer.alloc(1024 * 1024, 0x7c); const sha256 = createHash("sha256").update(content).digest("hex"); let streamAttempts = 0; const invocation = parseSshInvocation("D601:/tmp", ["download", "/tmp/payload.bin", target]); const fake = fakeTransfer(content); fake.executor.runRemoteCommand = async () => ({ exitCode: 0, stdout: `${content.length} ${sha256}\n`, stderr: "" }); fake.executor.streamRemoteCommand = async (_command, handlers) => { streamAttempts += 1; if (streamAttempts === 1) { return { exitCode: 255, stdout: "", stderr: "unidesk remote frontend ssh bridge timed out waiting for provider session\n" }; } await handlers.onStdout(content); return { exitCode: 0, stdout: "", stderr: "" }; }; try { expect(await runSshFileTransferOperation(invocation, ["download", "/tmp/payload.bin", target], fake.executor, fake.builders)).toBe(0); expect(streamAttempts).toBe(2); expect(Buffer.from(await Bun.file(target).arrayBuffer())).toEqual(content); } finally { rmSync(dir, { recursive: true, force: true }); } }); });