@@ -70,6 +70,26 @@ const BILLING_ATTRIBUTION = {
7070 payerSubscription : null ,
7171} satisfies BillingAttributionSnapshot
7272
73+ const SEARCH_RESULT = {
74+ results : [ { fileId : 'file-1' , lineNumber : 2 , text : 'needle' } ] ,
75+ count : 1 ,
76+ truncated : false ,
77+ complete : true ,
78+ indexStatus : {
79+ readyFiles : 1 ,
80+ pendingFiles : 0 ,
81+ failedFiles : 0 ,
82+ skippedFiles : 0 ,
83+ partialFiles : 0 ,
84+ } ,
85+ sources : [
86+ {
87+ identity : { fileId : 'file-1' , key : 'workspace/workspace-1/file.txt' } ,
88+ ownerUserId : 'user-1' ,
89+ } ,
90+ ] ,
91+ }
92+
7393function request (
7494 toolId : string ,
7595 input : unknown ,
@@ -109,25 +129,7 @@ describe('executeFileTool', () => {
109129 } )
110130 mocks . executeManage . mockResolvedValue ( Response . json ( { success : true } ) )
111131 mocks . executeParser . mockResolvedValue ( Response . json ( { success : true } ) )
112- mocks . searchContent . mockResolvedValue ( {
113- results : [ { fileId : 'file-1' , lineNumber : 2 , text : 'needle' } ] ,
114- count : 1 ,
115- truncated : false ,
116- complete : true ,
117- indexStatus : {
118- readyFiles : 1 ,
119- pendingFiles : 0 ,
120- failedFiles : 0 ,
121- skippedFiles : 0 ,
122- partialFiles : 0 ,
123- } ,
124- sources : [
125- {
126- identity : { fileId : 'file-1' , key : 'workspace/workspace-1/file.txt' } ,
127- ownerUserId : 'user-1' ,
128- } ,
129- ] ,
130- } )
132+ mocks . searchContent . mockResolvedValue ( SEARCH_RESULT )
131133 mocks . getProvenance . mockResolvedValue ( { version : 1 , complete : true , entries : [ ] } )
132134 } )
133135
@@ -146,7 +148,7 @@ describe('executeFileTool', () => {
146148 } )
147149 expect ( mocks . searchContent ) . toHaveBeenCalledWith ( {
148150 principal : expect . objectContaining ( { serviceId : 'executor' } ) ,
149- input : { workspaceId : 'workspace-1' , query : 'needle' , maxResults : 25 } ,
151+ input : { workspaceId : 'workspace-1' , query : 'needle' , maxResults : 25 , signal : undefined } ,
150152 } )
151153 expect ( mocks . executeManage ) . not . toHaveBeenCalled ( )
152154 } )
@@ -176,7 +178,8 @@ describe('executeFileTool', () => {
176178 expect . objectContaining ( {
177179 identity : expect . objectContaining ( { fileId : 'file-1' } ) ,
178180 } ) ,
179- ] )
181+ ] ) ,
182+ undefined
180183 )
181184 const body = await response . json ( )
182185 expect ( body . data . sources ) . toBeUndefined ( )
@@ -207,6 +210,47 @@ describe('executeFileTool', () => {
207210 } )
208211 } )
209212
213+ it ( 'propagates cancellation that arrives while search work is running' , async ( ) => {
214+ const controller = new AbortController ( )
215+ mocks . searchContent . mockImplementationOnce ( async ( ) => {
216+ controller . abort ( new DOMException ( 'cancelled' , 'AbortError' ) )
217+ return SEARCH_RESULT
218+ } )
219+
220+ await expect (
221+ executeFileTool ( request ( 'file_search' , { query : 'needle' } , { signal : controller . signal } ) )
222+ ) . rejects . toMatchObject ( { name : 'AbortError' } )
223+ expect ( mocks . searchContent ) . toHaveBeenCalledWith (
224+ expect . objectContaining ( {
225+ input : expect . objectContaining ( { signal : controller . signal } ) ,
226+ } )
227+ )
228+ expect ( mocks . getProvenance ) . not . toHaveBeenCalled ( )
229+ } )
230+
231+ it ( 'propagates cancellation that arrives while search provenance is loading' , async ( ) => {
232+ const controller = new AbortController ( )
233+ mocks . getProvenance . mockImplementationOnce ( async ( ) => {
234+ controller . abort ( new DOMException ( 'cancelled' , 'AbortError' ) )
235+ return { version : 1 , complete : true , entries : [ ] }
236+ } )
237+
238+ await expect (
239+ executeFileTool (
240+ request (
241+ 'file_search' ,
242+ { query : 'needle' } ,
243+ {
244+ signal : controller . signal ,
245+ headers : new Headers ( {
246+ 'x-sim-request-private-tool-metadata' : 'resolved-secret-provenance-v1' ,
247+ } ) ,
248+ }
249+ )
250+ )
251+ ) . rejects . toMatchObject ( { name : 'AbortError' } )
252+ } )
253+
210254 it . each ( Object . entries ( MANAGE_INPUTS ) ) ( 'validates and dispatches %s' , async ( toolId , input ) => {
211255 const response = await executeFileTool ( request ( toolId , input ) )
212256
0 commit comments