Skip to content

Commit ae39423

Browse files
committed
test(coverage): defineToolSpawn factory + helpers to 100%
Adds 12 unit tests covering every branch of the dlx tool-spawn factory: - defineVfsSpawn forwards args to spawnToolVfs with the configured VFS name - defineGitHubReleaseSpawn downloads + spawns, threads stdio + env, and throws on resolver-contract violations - defineAutoDispatch picks Vfs in SEA mode, Dlx otherwise (and Dlx when external tools are unavailable in SEA mode) - defineToolSpawn returns the full Dlx + Vfs + auto triple wired correctly define-tool-spawn.mts: 42.86% → 100% statements/branches/functions/lines.
1 parent 1415c67 commit ae39423

1 file changed

Lines changed: 239 additions & 0 deletions

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
/**
2+
* Unit tests for `defineToolSpawn` and its helpers.
3+
*
4+
* Locks in the contract that the factory builds the same Dlx/Vfs/auto triple
5+
* the per-tool spawn-* files used to assemble by hand.
6+
*/
7+
8+
import { beforeEach, describe, expect, it, vi } from 'vitest'
9+
10+
const mockSpawn = vi.hoisted(() => vi.fn())
11+
const mockDownloadGitHubReleaseBinary = vi.hoisted(() => vi.fn())
12+
const mockSpawnToolVfs = vi.hoisted(() => vi.fn())
13+
const mockAreExternalToolsAvailable = vi.hoisted(() => vi.fn())
14+
const mockIsSeaBinary = vi.hoisted(() => vi.fn())
15+
16+
vi.mock('@socketsecurity/lib/spawn', () => ({
17+
spawn: mockSpawn,
18+
}))
19+
20+
vi.mock('../../../../src/utils/dlx/spawn.mts', () => ({
21+
downloadGitHubReleaseBinary: mockDownloadGitHubReleaseBinary,
22+
spawnToolVfs: mockSpawnToolVfs,
23+
}))
24+
25+
vi.mock('../../../../src/utils/dlx/vfs-extract.mts', () => ({
26+
areExternalToolsAvailable: mockAreExternalToolsAvailable,
27+
}))
28+
29+
vi.mock('../../../../src/utils/sea/detect.mts', () => ({
30+
isSeaBinary: mockIsSeaBinary,
31+
}))
32+
33+
import {
34+
defineAutoDispatch,
35+
defineGitHubReleaseSpawn,
36+
defineToolSpawn,
37+
defineVfsSpawn,
38+
} from '../../../../src/utils/dlx/define-tool-spawn.mts'
39+
40+
describe('defineToolSpawn helpers', () => {
41+
beforeEach(() => {
42+
vi.clearAllMocks()
43+
})
44+
45+
describe('defineVfsSpawn', () => {
46+
it('forwards to spawnToolVfs with the configured tool name', async () => {
47+
mockSpawnToolVfs.mockResolvedValue({ spawnPromise: Promise.resolve() })
48+
const fn = defineVfsSpawn('trufflehog' as any)
49+
await fn(['scan', '/path'], undefined, undefined)
50+
expect(mockSpawnToolVfs).toHaveBeenCalledWith(
51+
'trufflehog',
52+
['scan', '/path'],
53+
undefined,
54+
undefined,
55+
)
56+
})
57+
58+
it('passes options + spawnExtra through unchanged', async () => {
59+
mockSpawnToolVfs.mockResolvedValue({ spawnPromise: Promise.resolve() })
60+
const fn = defineVfsSpawn('trivy' as any)
61+
const opts = { env: { FOO: 'bar' } } as any
62+
const extra = { stdio: 'pipe' as any }
63+
await fn(['fs'], opts, extra)
64+
expect(mockSpawnToolVfs).toHaveBeenCalledWith(
65+
'trivy',
66+
['fs'],
67+
opts,
68+
extra,
69+
)
70+
})
71+
})
72+
73+
describe('defineGitHubReleaseSpawn', () => {
74+
it('downloads + spawns a GitHub-release tool', async () => {
75+
mockDownloadGitHubReleaseBinary.mockResolvedValue('/cache/trufflehog')
76+
mockSpawn.mockReturnValue('mock-spawn-promise')
77+
const fn = defineGitHubReleaseSpawn({
78+
toolName: 'trufflehog',
79+
resolve: () => ({
80+
type: 'github-release',
81+
details: { name: 'trufflehog', version: '3.0.0' } as any,
82+
}),
83+
})
84+
const result = await fn(['scan'], undefined, undefined)
85+
expect(mockDownloadGitHubReleaseBinary).toHaveBeenCalled()
86+
expect(mockSpawn).toHaveBeenCalledWith(
87+
'/cache/trufflehog',
88+
['scan'],
89+
expect.objectContaining({
90+
stdio: 'inherit',
91+
env: expect.any(Object),
92+
}),
93+
)
94+
expect(result).toEqual({ spawnPromise: 'mock-spawn-promise' })
95+
})
96+
97+
it('honors a custom stdio passed via spawnExtra', async () => {
98+
mockDownloadGitHubReleaseBinary.mockResolvedValue('/cache/trivy')
99+
mockSpawn.mockReturnValue('p')
100+
const fn = defineGitHubReleaseSpawn({
101+
toolName: 'trivy',
102+
resolve: () => ({
103+
type: 'github-release',
104+
details: { name: 'trivy', version: '1.0.0' } as any,
105+
}),
106+
})
107+
await fn(['fs', '/'], undefined, { stdio: 'pipe' } as any)
108+
expect(mockSpawn).toHaveBeenCalledWith(
109+
'/cache/trivy',
110+
['fs', '/'],
111+
expect.objectContaining({ stdio: 'pipe' }),
112+
)
113+
})
114+
115+
it('merges options.env into the child env', async () => {
116+
mockDownloadGitHubReleaseBinary.mockResolvedValue('/cache/opengrep')
117+
mockSpawn.mockReturnValue('p')
118+
const fn = defineGitHubReleaseSpawn({
119+
toolName: 'opengrep',
120+
resolve: () => ({
121+
type: 'github-release',
122+
details: { name: 'opengrep', version: '1.0.0' } as any,
123+
}),
124+
})
125+
await fn([], { env: { FOO: 'bar' } } as any, undefined)
126+
const callEnv = mockSpawn.mock.calls[0][2].env
127+
expect(callEnv.FOO).toBe('bar')
128+
})
129+
130+
it('throws an internal error when the resolver returns the wrong type', async () => {
131+
const fn = defineGitHubReleaseSpawn({
132+
toolName: 'trufflehog',
133+
// Resolver contract bug: type='dlx' instead of 'github-release'.
134+
resolve: () =>
135+
({
136+
type: 'dlx',
137+
details: { name: 'trufflehog', version: '3.0.0' },
138+
}) as any,
139+
})
140+
await expect(fn([], undefined, undefined)).rejects.toThrow(
141+
/resolveTrufflehog returned resolution\.type="dlx"/,
142+
)
143+
})
144+
})
145+
146+
describe('defineAutoDispatch', () => {
147+
it('uses Vfs in SEA mode with external tools available', async () => {
148+
mockIsSeaBinary.mockReturnValue(true)
149+
mockAreExternalToolsAvailable.mockReturnValue(true)
150+
const vfs = vi.fn().mockResolvedValue({ spawnPromise: 'vfs-result' })
151+
const dlx = vi.fn().mockResolvedValue({ spawnPromise: 'dlx-result' })
152+
const auto = defineAutoDispatch({ vfs, dlx })
153+
const result = await auto([], undefined, undefined)
154+
expect(vfs).toHaveBeenCalled()
155+
expect(dlx).not.toHaveBeenCalled()
156+
expect(result).toEqual({ spawnPromise: 'vfs-result' })
157+
})
158+
159+
it('uses Dlx when not in SEA mode', async () => {
160+
mockIsSeaBinary.mockReturnValue(false)
161+
mockAreExternalToolsAvailable.mockReturnValue(true)
162+
const vfs = vi.fn().mockResolvedValue({ spawnPromise: 'vfs' })
163+
const dlx = vi.fn().mockResolvedValue({ spawnPromise: 'dlx' })
164+
const auto = defineAutoDispatch({ vfs, dlx })
165+
const result = await auto([], undefined, undefined)
166+
expect(dlx).toHaveBeenCalled()
167+
expect(vfs).not.toHaveBeenCalled()
168+
expect(result).toEqual({ spawnPromise: 'dlx' })
169+
})
170+
171+
it('uses Dlx when in SEA but external tools missing', async () => {
172+
mockIsSeaBinary.mockReturnValue(true)
173+
mockAreExternalToolsAvailable.mockReturnValue(false)
174+
const vfs = vi.fn()
175+
const dlx = vi.fn().mockResolvedValue({ spawnPromise: 'dlx' })
176+
const auto = defineAutoDispatch({ vfs, dlx })
177+
await auto([], undefined, undefined)
178+
expect(dlx).toHaveBeenCalled()
179+
expect(vfs).not.toHaveBeenCalled()
180+
})
181+
})
182+
183+
describe('defineToolSpawn (full triple)', () => {
184+
it('returns Dlx + Vfs + auto together', () => {
185+
const triple = defineToolSpawn({
186+
toolName: 'trufflehog',
187+
vfsName: 'trufflehog' as any,
188+
resolve: () =>
189+
({
190+
type: 'github-release',
191+
details: { name: 'trufflehog', version: '3.0.0' },
192+
}) as any,
193+
})
194+
expect(typeof triple.Dlx).toBe('function')
195+
expect(typeof triple.Vfs).toBe('function')
196+
expect(typeof triple.auto).toBe('function')
197+
})
198+
199+
it('auto routes to Vfs in SEA mode', async () => {
200+
mockIsSeaBinary.mockReturnValue(true)
201+
mockAreExternalToolsAvailable.mockReturnValue(true)
202+
mockSpawnToolVfs.mockResolvedValue({ spawnPromise: 'vfs' })
203+
const triple = defineToolSpawn({
204+
toolName: 'trivy',
205+
vfsName: 'trivy' as any,
206+
resolve: () =>
207+
({
208+
type: 'github-release',
209+
details: { name: 'trivy', version: '1.0.0' },
210+
}) as any,
211+
})
212+
await triple.auto([], undefined, undefined)
213+
expect(mockSpawnToolVfs).toHaveBeenCalledWith(
214+
'trivy',
215+
[],
216+
undefined,
217+
undefined,
218+
)
219+
})
220+
221+
it('auto routes to Dlx outside SEA mode', async () => {
222+
mockIsSeaBinary.mockReturnValue(false)
223+
mockDownloadGitHubReleaseBinary.mockResolvedValue('/cache/opengrep')
224+
mockSpawn.mockReturnValue('p')
225+
const triple = defineToolSpawn({
226+
toolName: 'opengrep',
227+
vfsName: 'opengrep' as any,
228+
resolve: () =>
229+
({
230+
type: 'github-release',
231+
details: { name: 'opengrep', version: '1.0.0' },
232+
}) as any,
233+
})
234+
await triple.auto([], undefined, undefined)
235+
expect(mockDownloadGitHubReleaseBinary).toHaveBeenCalled()
236+
expect(mockSpawnToolVfs).not.toHaveBeenCalled()
237+
})
238+
})
239+
})

0 commit comments

Comments
 (0)