11import { describe , it , expect , vi , beforeEach , afterEach } from 'vitest'
2+ import { EventEmitter } from 'node:events'
3+ import { vol } from 'memfs'
24import { spawn } from 'node:child_process'
3- import { existsSync } from 'node:fs'
45import net from 'node:net'
56import { app } from 'electron'
6- import { EventEmitter } from 'node:events'
77import {
88 startToolhive ,
99 getToolhivePort ,
1010 getToolhiveMcpPort ,
1111 isToolhiveRunning ,
1212 stopToolhive ,
1313 restartToolhive ,
14+ binPath ,
1415} from '../toolhive-manager'
1516import { updateTrayStatus } from '../system-tray'
1617import log from '../logger'
1718import * as Sentry from '@sentry/electron/main'
1819import { getQuittingState } from '../app-state'
1920
20- // Mock dependencies
21- vi . mock ( 'node:child_process' )
21+ vi . mock ( 'node:child_process' , async ( importOriginal ) => {
22+ const actual = await importOriginal < typeof import ( 'node:child_process' ) > ( )
23+ const mockSpawnFn = vi . fn ( )
24+ return {
25+ ...actual ,
26+ spawn : mockSpawnFn ,
27+ default : {
28+ ...actual ,
29+ spawn : mockSpawnFn ,
30+ } ,
31+ }
32+ } )
2233vi . mock ( 'node:fs' )
23- vi . mock ( 'node:net' )
34+ vi . mock ( 'node:net' , async ( importOriginal ) => {
35+ const actual = await importOriginal < typeof import ( 'node:net' ) > ( )
36+ const mockCreateServerFn = vi . fn ( )
37+ return {
38+ ...actual ,
39+ createServer : mockCreateServerFn ,
40+ default : {
41+ ...actual ,
42+ createServer : mockCreateServerFn ,
43+ } ,
44+ }
45+ } )
2446vi . mock ( 'electron' , ( ) => ( {
2547 app : {
2648 isPackaged : false ,
@@ -53,12 +75,14 @@ vi.mock('electron-store', () => {
5375 }
5476
5577 return {
56- default : vi . fn ( ) . mockImplementation ( ( ) => mockStoreInstance ) ,
78+ default : vi . fn ( function ElectronStore ( ) {
79+ return mockStoreInstance
80+ } ) ,
5781 }
5882} )
5983
84+ // Get mocked functions
6085const mockSpawn = vi . mocked ( spawn )
61- const mockExistsSync = vi . mocked ( existsSync )
6286const mockNet = vi . mocked ( net )
6387const mockApp = vi . mocked ( app )
6488const mockUpdateTrayStatus = vi . mocked ( updateTrayStatus )
@@ -112,26 +136,35 @@ describe('toolhive-manager', () => {
112136 vi . clearAllMocks ( )
113137 vi . useFakeTimers ( )
114138
139+ // Reset the in-memory file system
140+ vol . reset ( )
141+
142+ // Setup the file system state - make binary exist by default
143+ vol . fromJSON ( {
144+ [ binPath ] : '' , // Mock the binary file exists
145+ } )
146+
115147 // Reset module state
116148 stopToolhive ( )
117149
118150 // Setup mocks
119151 mockProcess = new MockProcess ( )
120152
121- mockSpawn . mockReturnValue (
122- mockProcess as unknown as ReturnType < typeof spawn >
123- )
124- mockExistsSync . mockReturnValue ( true )
153+ mockSpawn . mockImplementation ( function spawn ( ) {
154+ return mockProcess as unknown as ReturnType <
155+ typeof import ( 'node:child_process' ) . spawn
156+ >
157+ } )
125158 Object . defineProperty ( mockApp , 'isPackaged' , {
126159 value : false ,
127160 configurable : true ,
128161 } )
129162 vi . mocked ( mockApp . getPath ) . mockReturnValue ( '/test/userData' )
130163
131164 // Mock net.createServer to return a new MockServer instance each time
132- mockNet . createServer . mockImplementation (
133- ( ) => new MockServer ( ) as unknown as net . Server
134- )
165+ mockNet . createServer . mockImplementation ( function createServer ( ) {
166+ return new MockServer ( ) as unknown as net . Server
167+ } )
135168
136169 // Mock logger methods
137170 mockLog . info = vi . fn ( )
@@ -147,7 +180,8 @@ describe('toolhive-manager', () => {
147180
148181 describe ( 'startToolhive' , ( ) => {
149182 it ( 'returns early if binary does not exist' , async ( ) => {
150- mockExistsSync . mockReturnValue ( false )
183+ // Remove the binary file from the in-memory file system
184+ vol . reset ( )
151185
152186 await startToolhive ( )
153187
@@ -382,28 +416,30 @@ describe('toolhive-manager', () => {
382416 describe ( 'port finding with fallback' , ( ) => {
383417 it ( 'falls back to random port when preferred range is unavailable' , async ( ) => {
384418 // Mock all ports in range to be unavailable, then allow random port
385- mockNet . createServer . mockImplementation ( ( ) => {
419+ mockNet . createServer . mockImplementation ( function createServer ( ) {
386420 const server = new MockServer ( ) as unknown as net . Server
387421 const originalListen = server . listen . bind ( server )
388422
389- server . listen = vi
390- . fn ( )
391- . mockImplementation ( ( port : number , callback ?: ( ) => void ) => {
392- if ( port >= 50000 && port <= 50100 ) {
393- // Simulate all ports in range being unavailable
394- setTimeout ( ( ) => {
395- server . emit ( 'error' , { code : 'EADDRINUSE' } )
396- } , 5 )
397- } else if ( port === 0 ) {
398- // Allow OS assignment (fallback)
399- setTimeout ( ( ) => {
400- originalListen ( port , callback )
401- } , 5 )
402- } else {
403- // Any other specific port
423+ server . listen = vi . fn ( function listen (
424+ port : number ,
425+ callback ?: ( ) => void
426+ ) {
427+ if ( port >= 50000 && port <= 50100 ) {
428+ // Simulate all ports in range being unavailable
429+ setTimeout ( ( ) => {
430+ server . emit ( 'error' , { code : 'EADDRINUSE' } )
431+ } , 5 )
432+ } else if ( port === 0 ) {
433+ // Allow OS assignment (fallback)
434+ setTimeout ( ( ) => {
404435 originalListen ( port , callback )
405- }
406- } )
436+ } , 5 )
437+ } else {
438+ // Any other specific port
439+ originalListen ( port , callback )
440+ }
441+ return server
442+ } ) as unknown as typeof server . listen
407443
408444 return server
409445 } )
@@ -566,10 +602,12 @@ describe('toolhive-manager', () => {
566602
567603 it ( 'handles kill errors and attempts force kill as fallback' , ( ) => {
568604 const killSpy = vi . spyOn ( mockProcess , 'kill' )
569- killSpy . mockImplementationOnce ( ( ) => {
605+ killSpy . mockImplementationOnce ( function killImpl ( ) {
570606 throw new Error ( 'Kill failed' )
571607 } )
572- killSpy . mockImplementationOnce ( ( ) => true )
608+ killSpy . mockImplementationOnce ( function killImpl ( ) {
609+ return true
610+ } )
573611
574612 stopToolhive ( )
575613
0 commit comments