|
2 | 2 | import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; |
3 | 3 | import { EventEmitter } from "node:events"; |
4 | 4 | import { beforeAll, describe, expect, it, vi } from "vitest"; |
| 5 | +import { resolveSystemBin } from "./resolve-system-bin.js"; |
5 | 6 |
|
6 | 7 | type MockSpawnChild = EventEmitter & { |
7 | 8 | stdout?: EventEmitter & { setEncoding?: (enc: string) => void }; |
@@ -45,7 +46,12 @@ vi.mock("node:child_process", async () => { |
45 | 46 | ); |
46 | 47 | }); |
47 | 48 |
|
| 49 | +vi.mock("./resolve-system-bin.js", () => ({ |
| 50 | + resolveSystemBin: vi.fn(() => "/usr/bin/ssh"), |
| 51 | +})); |
| 52 | + |
48 | 53 | const spawnMock = vi.mocked(spawn); |
| 54 | +const resolveSystemBinMock = vi.mocked(resolveSystemBin); |
49 | 55 |
|
50 | 56 | function requireSpawnArgs(index: number): string[] { |
51 | 57 | const args = spawnMock.mock.calls[index]?.[1] as string[] | undefined; |
@@ -98,6 +104,8 @@ describe("ssh-config", () => { |
98 | 104 | expect(config?.port).toBe(2222); |
99 | 105 | expect(config?.identityFiles).toEqual(["/tmp/id_ed25519"]); |
100 | 106 | expect(requireSpawnArgs(0).slice(-2)).toEqual(["--", "me@alias"]); |
| 107 | + expect(resolveSystemBinMock).toHaveBeenCalledWith("ssh", { trust: "strict" }); |
| 108 | + expect(spawnMock.mock.calls[0]?.[0]).toBe("/usr/bin/ssh"); |
101 | 109 | }); |
102 | 110 |
|
103 | 111 | it("adds non-default port and trimmed identity arguments", async () => { |
@@ -139,6 +147,16 @@ describe("ssh-config", () => { |
139 | 147 | await expect(resolveSshConfig({ user: "me", host: "bad-host", port: 22 })).resolves.toBeNull(); |
140 | 148 | }); |
141 | 149 |
|
| 150 | + it("returns null without spawning when no trusted ssh client is found", async () => { |
| 151 | + resolveSystemBinMock.mockReturnValueOnce(null); |
| 152 | + spawnMock.mockClear(); |
| 153 | + |
| 154 | + const config = await resolveSshConfig({ user: "me", host: "alias", port: 22 }); |
| 155 | + |
| 156 | + expect(config).toBeNull(); |
| 157 | + expect(spawnMock).not.toHaveBeenCalled(); |
| 158 | + }); |
| 159 | + |
142 | 160 | it("rejects oversized ssh -G output while preserving the parser contract", () => { |
143 | 161 | expect(appendSshConfigOutput("user bob", "\nhostname example.com", 128)).toEqual({ |
144 | 162 | ok: true, |
|
0 commit comments