@@ -44,16 +44,14 @@ const addLineNumbersMock = jest.fn().mockImplementation((text, startLine = 1) =>
4444 return lines . map ( ( line , i ) => `${ startLine + i } | ${ line } ` ) . join ( "\n" )
4545} )
4646
47- const extractTextFromFileMock = jest . fn ( ) . mockImplementation ( ( _filePath ) => {
48- // Call addLineNumbersMock to register the call
49- addLineNumbersMock ( mockInputContent )
50- return Promise . resolve ( addLineNumbersMock ( mockInputContent ) )
51- } )
47+ const extractTextFromFileMock = jest . fn ( )
48+ const getSupportedBinaryFormatsMock = jest . fn ( ( ) => [ ".pdf" , ".docx" , ".ipynb" ] )
5249
5350// Now assign the mocks to the module
5451const extractTextModule = jest . requireMock ( "../../../integrations/misc/extract-text" )
5552extractTextModule . extractTextFromFile = extractTextFromFileMock
5653extractTextModule . addLineNumbers = addLineNumbersMock
54+ extractTextModule . getSupportedBinaryFormats = getSupportedBinaryFormatsMock
5755
5856jest . mock ( "../../ignore/RooIgnoreController" , ( ) => ( {
5957 RooIgnoreController : class {
@@ -128,6 +126,9 @@ describe("read_file tool with maxReadFileLine setting", () => {
128126 mockCline . say = jest . fn ( ) . mockResolvedValue ( undefined )
129127 mockCline . ask = jest . fn ( ) . mockResolvedValue ( { response : "yesButtonClicked" } )
130128 mockCline . presentAssistantMessage = jest . fn ( )
129+ mockCline . handleError = jest . fn ( ) . mockResolvedValue ( undefined )
130+ mockCline . pushToolResult = jest . fn ( )
131+ mockCline . removeClosingTag = jest . fn ( ( tag , content ) => content )
131132
132133 mockCline . fileContextTracker = {
133134 trackFileContext : jest . fn ( ) . mockResolvedValue ( undefined ) ,
@@ -410,6 +411,13 @@ describe("read_file tool XML output structure", () => {
410411 mockedPathResolve . mockReturnValue ( absoluteFilePath )
411412 mockedIsBinaryFile . mockResolvedValue ( false )
412413
414+ // Set default implementation for extractTextFromFile
415+ mockedExtractTextFromFile . mockImplementation ( ( filePath ) => {
416+ // Call addLineNumbersMock to register the call
417+ addLineNumbersMock ( mockInputContent )
418+ return Promise . resolve ( addLineNumbersMock ( mockInputContent ) )
419+ } )
420+
413421 mockInputContent = fileContent
414422
415423 // Setup mock provider with default maxReadFileLine
@@ -1126,34 +1134,101 @@ describe("read_file tool XML output structure", () => {
11261134 const textPath = "test/text.txt"
11271135 const binaryPath = "test/binary.pdf"
11281136 const numberedContent = "1 | Text file content"
1137+ const pdfContent = "1 | PDF content extracted"
1138+
1139+ // Mock path.resolve to return the expected paths
1140+ mockedPathResolve . mockImplementation ( ( cwd , relPath ) => `/${ relPath } ` )
11291141
11301142 // Mock binary file detection
1131- mockedIsBinaryFile . mockImplementationOnce ( ( ) => Promise . resolve ( false ) )
1132- mockedIsBinaryFile . mockImplementationOnce ( ( ) => Promise . resolve ( true ) )
1143+ mockedIsBinaryFile . mockImplementation ( ( path ) => {
1144+ if ( path . includes ( "text.txt" ) ) return Promise . resolve ( false )
1145+ if ( path . includes ( "binary.pdf" ) ) return Promise . resolve ( true )
1146+ return Promise . resolve ( false )
1147+ } )
1148+
1149+ mockedCountFileLines . mockImplementation ( ( path ) => {
1150+ return Promise . resolve ( 1 )
1151+ } )
11331152
1134- // Mock content based on file type
11351153 mockedExtractTextFromFile . mockImplementation ( ( path ) => {
1136- if ( path . includes ( "binary" ) ) {
1137- return Promise . resolve ( "" )
1154+ if ( path . includes ( "binary.pdf " ) ) {
1155+ return Promise . resolve ( pdfContent )
11381156 }
11391157 return Promise . resolve ( numberedContent )
11401158 } )
1141- mockedCountFileLines . mockImplementation ( ( path ) => {
1142- return Promise . resolve ( path . includes ( "binary" ) ? 0 : 1 )
1143- } )
1159+
1160+ // Configure mocks for the test
11441161 mockProvider . getState . mockResolvedValue ( { maxReadFileLine : - 1 } )
11451162
1146- // Execute
1147- const result = await executeReadFileTool (
1148- {
1163+ // Create standalone mock functions
1164+ const mockAskApproval = jest . fn ( ) . mockResolvedValue ( { response : "yesButtonClicked" } )
1165+ const mockHandleError = jest . fn ( ) . mockResolvedValue ( undefined )
1166+ const mockPushToolResult = jest . fn ( )
1167+ const mockRemoveClosingTag = jest . fn ( ( tag , content ) => content )
1168+
1169+ // Create a tool use object directly
1170+ const toolUse : ReadFileToolUse = {
1171+ type : "tool_use" ,
1172+ name : "read_file" ,
1173+ params : {
11491174 args : `<file><path>${ textPath } </path></file><file><path>${ binaryPath } </path></file>` ,
11501175 } ,
1151- { totalLines : 1 } ,
1176+ partial : false ,
1177+ }
1178+
1179+ // Call readFileTool directly
1180+ await readFileTool (
1181+ mockCline ,
1182+ toolUse ,
1183+ mockAskApproval ,
1184+ mockHandleError ,
1185+ mockPushToolResult ,
1186+ mockRemoveClosingTag ,
11521187 )
11531188
1154- // Verify
1155- expect ( result ) . toBe (
1156- `<files>\n<file><path>${ textPath } </path>\n<content lines="1-1">\n${ numberedContent } </content>\n</file>\n<file><path>${ binaryPath } </path>\n<notice>Binary file</notice>\n</file>\n</files>` ,
1189+ // Check the result
1190+ expect ( mockPushToolResult ) . toHaveBeenCalledWith (
1191+ `<files>\n<file><path>${ textPath } </path>\n<content lines="1-1">\n${ numberedContent } </content>\n</file>\n<file><path>${ binaryPath } </path>\n<content lines="1-1">\n${ pdfContent } </content>\n</file>\n</files>` ,
1192+ )
1193+ } )
1194+
1195+ it ( "should block unsupported binary files" , async ( ) => {
1196+ // Setup
1197+ const unsupportedBinaryPath = "test/binary.exe"
1198+
1199+ mockedIsBinaryFile . mockImplementation ( ( ) => Promise . resolve ( true ) )
1200+ mockedCountFileLines . mockImplementation ( ( ) => Promise . resolve ( 1 ) )
1201+ mockProvider . getState . mockResolvedValue ( { maxReadFileLine : - 1 } )
1202+
1203+ // Create standalone mock functions
1204+ const mockAskApproval = jest . fn ( ) . mockResolvedValue ( { response : "yesButtonClicked" } )
1205+ const mockHandleError = jest . fn ( ) . mockResolvedValue ( undefined )
1206+ const mockPushToolResult = jest . fn ( )
1207+ const mockRemoveClosingTag = jest . fn ( ( tag , content ) => content )
1208+
1209+ // Create a tool use object directly
1210+ const toolUse : ReadFileToolUse = {
1211+ type : "tool_use" ,
1212+ name : "read_file" ,
1213+ params : {
1214+ args : `<file><path>${ unsupportedBinaryPath } </path></file>` ,
1215+ } ,
1216+ partial : false ,
1217+ }
1218+
1219+ // Call readFileTool directly
1220+ await readFileTool (
1221+ mockCline ,
1222+ toolUse ,
1223+ mockAskApproval ,
1224+ mockHandleError ,
1225+ mockPushToolResult ,
1226+ mockRemoveClosingTag ,
1227+ )
1228+
1229+ // Check the result
1230+ expect ( mockPushToolResult ) . toHaveBeenCalledWith (
1231+ `<files>\n<file><path>${ unsupportedBinaryPath } </path>\n<notice>Binary file</notice>\n</file>\n</files>` ,
11571232 )
11581233 } )
11591234 } )
@@ -1165,6 +1240,7 @@ describe("read_file tool XML output structure", () => {
11651240 const maxReadFileLine = - 1
11661241 const totalLines = 0
11671242 mockedCountFileLines . mockResolvedValue ( totalLines )
1243+ mockedIsBinaryFile . mockResolvedValue ( false ) // Ensure empty file is not detected as binary
11681244
11691245 // Execute
11701246 const result = await executeReadFileTool ( { } , { maxReadFileLine, totalLines } )
0 commit comments