33 */
44import { describe , it , expect , vi , beforeEach , afterEach } from "vitest" ;
55
6- // Create mock functions
7- const mockMkdir = vi . fn ( ) . mockResolvedValue ( undefined ) ;
8- const mockWriteFile = vi . fn ( ) . mockResolvedValue ( undefined ) ;
9- const mockReadFile = vi . fn ( ) . mockResolvedValue ( "" ) ;
10- const mockReaddir = vi . fn ( ) . mockResolvedValue ( [ ] ) ;
11- const mockStat = vi . fn ( ) . mockResolvedValue ( { isFile : ( ) => true } ) ;
6+ // Define mock functions using vi.hoisted() so they're available when vi.mock runs
7+ const { mockMkdir, mockWriteFile, mockReadFile, mockReaddir, mockStat, mockExec, mockPromisify } =
8+ vi . hoisted ( ( ) => ( {
9+ mockMkdir : vi . fn ( ) ,
10+ mockWriteFile : vi . fn ( ) ,
11+ mockReadFile : vi . fn ( ) ,
12+ mockReaddir : vi . fn ( ) ,
13+ mockStat : vi . fn ( ) ,
14+ mockExec : vi . fn ( ) ,
15+ mockPromisify : vi . fn ( ( ) => vi . fn ( ) ) ,
16+ } ) ) ;
1217
1318// Mock modules before importing
1419vi . mock ( "fs/promises" , ( ) => ( {
@@ -26,15 +31,14 @@ vi.mock("fs/promises", () => ({
2631 stat : mockStat ,
2732} ) ) ;
2833vi . mock ( "child_process" , ( ) => ( {
29- exec : vi . fn ( ) ,
34+ default : { exec : mockExec } ,
35+ exec : mockExec ,
3036} ) ) ;
3137vi . mock ( "util" , ( ) => ( {
32- promisify : vi . fn ( ( ) => vi . fn ( ) ) ,
38+ default : { promisify : mockPromisify } ,
39+ promisify : mockPromisify ,
3340} ) ) ;
3441
35- // Import fs after mocking
36- import fs from "fs/promises" ;
37-
3842// Mock fetch globally
3943const mockFetch = vi . fn ( ) ;
4044vi . stubGlobal ( "fetch" , mockFetch ) ;
@@ -142,8 +146,8 @@ describe("research-mcp", () => {
142146 ] ,
143147 } ) ;
144148
145- expect ( fs . mkdir ) . toHaveBeenCalled ( ) ;
146- expect ( fs . writeFile ) . toHaveBeenCalled ( ) ;
149+ expect ( mockMkdir ) . toHaveBeenCalled ( ) ;
150+ expect ( mockWriteFile ) . toHaveBeenCalled ( ) ;
147151 expect ( result . content [ 0 ] . text ) . toContain ( "Created notebook" ) ;
148152 expect ( result . content [ 0 ] . text ) . toContain ( "2 cells added" ) ;
149153 } ) ;
@@ -187,7 +191,7 @@ describe("research-mcp", () => {
187191 } ;
188192
189193 beforeEach ( ( ) => {
190- vi . mocked ( fs . readFile ) . mockResolvedValue ( JSON . stringify ( sampleNotebook ) ) ;
194+ mockReadFile . mockResolvedValue ( JSON . stringify ( sampleNotebook ) ) ;
191195 } ) ;
192196
193197 it ( "should read notebook contents" , async ( ) => {
@@ -248,7 +252,7 @@ describe("research-mcp", () => {
248252 } ;
249253
250254 beforeEach ( ( ) => {
251- vi . mocked ( fs . readFile ) . mockResolvedValue ( JSON . stringify ( sampleNotebook ) ) ;
255+ mockReadFile . mockResolvedValue ( JSON . stringify ( sampleNotebook ) ) ;
252256 } ) ;
253257
254258 it ( "should provide notebook summary" , async ( ) => {
@@ -292,12 +296,12 @@ describe("research-mcp", () => {
292296 } ;
293297
294298 beforeEach ( ( ) => {
295- vi . mocked ( fs . readFile ) . mockResolvedValue ( JSON . stringify ( existingNotebook ) ) ;
299+ mockReadFile . mockResolvedValue ( JSON . stringify ( existingNotebook ) ) ;
296300 } ) ;
297301
298302 it ( "should add cells at end by default" , async ( ) => {
299303 let writtenContent = "" ;
300- vi . mocked ( fs . writeFile ) . mockImplementation ( async ( _path , content ) => {
304+ mockWriteFile . mockImplementation ( async ( _path , content ) => {
301305 writtenContent = content as string ;
302306 } ) ;
303307
@@ -314,7 +318,7 @@ describe("research-mcp", () => {
314318
315319 it ( "should add cells at start when specified" , async ( ) => {
316320 let writtenContent = "" ;
317- vi . mocked ( fs . writeFile ) . mockImplementation ( async ( _path , content ) => {
321+ mockWriteFile . mockImplementation ( async ( _path , content ) => {
318322 writtenContent = content as string ;
319323 } ) ;
320324
@@ -335,7 +339,7 @@ describe("research-mcp", () => {
335339
336340 describe ( "error handling" , ( ) => {
337341 it ( "should handle file not found" , async ( ) => {
338- vi . mocked ( fs . readFile ) . mockRejectedValue ( new Error ( "ENOENT: file not found" ) ) ;
342+ mockReadFile . mockRejectedValue ( new Error ( "ENOENT: file not found" ) ) ;
339343
340344 const result = await notebookHandler ( {
341345 action : "read" ,
@@ -347,7 +351,7 @@ describe("research-mcp", () => {
347351 } ) ;
348352
349353 it ( "should handle invalid JSON" , async ( ) => {
350- vi . mocked ( fs . readFile ) . mockResolvedValue ( "not valid json" ) ;
354+ mockReadFile . mockResolvedValue ( "not valid json" ) ;
351355
352356 const result = await notebookHandler ( {
353357 action : "read" ,
@@ -511,7 +515,7 @@ describe("research-mcp", () => {
511515 outputPath : "paper.pdf" ,
512516 } ) ;
513517
514- expect ( fs . writeFile ) . toHaveBeenCalled ( ) ;
518+ expect ( mockWriteFile ) . toHaveBeenCalled ( ) ;
515519 expect ( result . content [ 0 ] . text ) . toContain ( "Downloaded" ) ;
516520 expect ( result . content [ 0 ] . text ) . toContain ( "paper.pdf" ) ;
517521 } ) ;
@@ -656,8 +660,8 @@ describe("research-mcp", () => {
656660 notebookPath : "review.ipynb" ,
657661 } ) ;
658662
659- expect ( fs . writeFile ) . toHaveBeenCalled ( ) ;
660- const writeCall = vi . mocked ( fs . writeFile ) . mock . calls [ 0 ] ;
663+ expect ( mockWriteFile ) . toHaveBeenCalled ( ) ;
664+ const writeCall = mockWriteFile . mock . calls [ 0 ] ;
661665 expect ( writeCall [ 0 ] ) . toContain ( "review.ipynb" ) ;
662666 } ) ;
663667
@@ -714,18 +718,23 @@ describe("research-mcp", () => {
714718
715719 beforeEach ( async ( ) => {
716720 // Setup exec mock
717- const { exec } = await import ( "child_process" ) ;
718721 mockExecAsync = vi . fn ( ) . mockResolvedValue ( { stdout : "" , stderr : "" } ) ;
719- vi . mocked ( exec ) . mockImplementation ( ( _cmd , _opts , callback ) => {
720- if ( callback ) {
721- callback ( null , "" , "" ) ;
722+ mockExec . mockImplementation (
723+ (
724+ _cmd : string ,
725+ _opts : unknown ,
726+ callback ?: ( err : Error | null , stdout : string , stderr : string ) => void
727+ ) => {
728+ if ( callback ) {
729+ callback ( null , "" , "" ) ;
730+ }
731+ return { } as unknown ;
722732 }
723- return { } as ReturnType < typeof exec > ;
724- } ) ;
733+ ) ;
725734
726735 // Re-mock promisify to return our mock
727- const { promisify } = await import ( "util" ) ;
728- vi . mocked ( promisify ) . mockReturnValue ( mockExecAsync ) ;
736+ // @ts -expect-error - Mock type complexity with promisify
737+ mockPromisify . mockReturnValue ( mockExecAsync ) ;
729738
730739 // Need to re-import to get fresh module with new mocks
731740 vi . resetModules ( ) ;
0 commit comments