@@ -189,18 +189,6 @@ describe('buildIntegrationToolSchemas', () => {
189189 expect ( gmailTool ?. description ) . toBe ( 'Send emails using Gmail' )
190190 } )
191191
192- it ( 'still builds integration tools when subscription lookup fails' , async ( ) => {
193- mockGetHighestPrioritySubscription . mockRejectedValue ( new Error ( 'db unavailable' ) )
194-
195- const toolSchemas = await buildIntegrationToolSchemas ( 'user-error' )
196- const gmailTool = toolSchemas . find ( ( tool ) => tool . name === 'gmail_send' )
197- const brandfetchTool = toolSchemas . find ( ( tool ) => tool . name === 'brandfetch_search' )
198-
199- expect ( mockGetHighestPrioritySubscription ) . toHaveBeenCalledWith ( 'user-error' )
200- expect ( gmailTool ?. description ) . toBe ( 'Send emails using Gmail' )
201- expect ( brandfetchTool ?. description ) . toBe ( 'Search for brands by company name' )
202- } )
203-
204192 it ( 'emits executeLocally for dynamic client tools only' , async ( ) => {
205193 mockGetHighestPrioritySubscription . mockResolvedValue ( { plan : 'pro' , status : 'active' } )
206194
@@ -264,7 +252,6 @@ describe('buildIntegrationToolSchemas', () => {
264252
265253 const toolSchemas = await buildIntegrationToolSchemas (
266254 'user-intersection' ,
267- undefined ,
268255 { schemaSurface : 'copilot' } ,
269256 'workspace-1'
270257 )
@@ -292,7 +279,6 @@ describe('buildIntegrationToolSchemas', () => {
292279 await expect (
293280 buildIntegrationToolSchemas (
294281 'user-permission-error' ,
295- undefined ,
296282 { schemaSurface : 'copilot' } ,
297283 'workspace-1'
298284 )
@@ -314,13 +300,72 @@ describe('buildIntegrationToolSchemas', () => {
314300 expect ( second [ 0 ] . outputs ) . not . toHaveProperty ( 'mutated' )
315301 } )
316302
303+ it ( 'isolates nested schemas and required fields between requests' , async ( ) => {
304+ mockCreateUserToolSchema . mockReturnValueOnce ( {
305+ type : 'object' ,
306+ properties : { recipients : { type : 'array' , items : { type : 'string' } } } ,
307+ required : [ 'recipients' ] ,
308+ } )
309+ const first = await buildIntegrationToolSchemas ( 'user-nested-schema' )
310+ const properties = first [ 0 ] . input_schema . properties as Record < string , unknown >
311+ properties . recipients = { type : 'number' }
312+ ; ( first [ 0 ] . input_schema . required as string [ ] ) . push ( 'forged' )
313+
314+ const second = await buildIntegrationToolSchemas ( 'user-nested-schema' )
315+ expect ( second [ 0 ] . input_schema . properties ) . toEqual ( {
316+ recipients : { type : 'array' , items : { type : 'string' } } ,
317+ } )
318+ expect ( second [ 0 ] . input_schema . required ) . toEqual ( [ 'recipients' ] )
319+ } )
320+
321+ it ( 'coalesces simultaneous catalog builds' , async ( ) => {
322+ const catalogs = await Promise . all (
323+ Array . from ( { length : 20 } , ( ) => buildIntegrationToolSchemas ( 'concurrent-user' ) )
324+ )
325+ expect ( mockGetHighestPrioritySubscription ) . toHaveBeenCalledTimes ( 1 )
326+ expect ( mockCreateUserToolSchema ) . toHaveBeenCalledTimes ( 3 )
327+ expect ( catalogs . every ( ( catalog ) => catalog . length === 3 ) ) . toBe ( true )
328+ } )
329+
330+ it ( 'propagates schema failures without caching a partial catalog' , async ( ) => {
331+ mockCreateUserToolSchema . mockImplementationOnce ( ( ) => {
332+ throw new Error ( 'invalid tool schema' )
333+ } )
334+ await expect ( buildIntegrationToolSchemas ( 'schema-failure' ) ) . rejects . toThrow (
335+ 'invalid tool schema'
336+ )
337+ const recovered = await buildIntegrationToolSchemas ( 'schema-failure' )
338+ expect ( recovered ) . toHaveLength ( 3 )
339+ expect ( mockGetHighestPrioritySubscription ) . toHaveBeenCalledTimes ( 2 )
340+ } )
341+
342+ it ( 'propagates subscription failures without caching a guessed catalog' , async ( ) => {
343+ mockGetHighestPrioritySubscription . mockRejectedValueOnce ( new Error ( 'billing unavailable' ) )
344+ await expect ( buildIntegrationToolSchemas ( 'subscription-failure' ) ) . rejects . toThrow (
345+ 'billing unavailable'
346+ )
347+ expect ( mockCreateUserToolSchema ) . not . toHaveBeenCalled ( )
348+ expect ( await buildIntegrationToolSchemas ( 'subscription-failure' ) ) . toHaveLength ( 3 )
349+ } )
350+
351+ it ( 'evicts catalogs by their byte size before reaching the entry cap' , async ( ) => {
352+ mockCreateUserToolSchema . mockImplementation ( ( ) => ( {
353+ type : 'object' ,
354+ properties : { large : { type : 'string' , description : 'x' . repeat ( 1024 * 1024 ) } } ,
355+ } ) )
356+ for ( let i = 0 ; i < 12 ; i ++ ) await buildIntegrationToolSchemas ( `sized-user-${ i } ` )
357+ expect ( mockGetHighestPrioritySubscription ) . toHaveBeenCalledTimes ( 12 )
358+ await buildIntegrationToolSchemas ( 'sized-user-0' )
359+ expect ( mockGetHighestPrioritySubscription ) . toHaveBeenCalledTimes ( 13 )
360+ mockCreateUserToolSchema . mockImplementation ( ( ) => ( { type : 'object' , properties : { } } ) )
361+ } )
362+
317363 it ( 'rebuilds instead of serving a cache entry from the previous policy' , async ( ) => {
318364 mockGetHighestPrioritySubscription . mockResolvedValue ( { plan : 'pro' , status : 'active' } )
319365 mockGetUserPermissionConfig . mockResolvedValue ( { allowedIntegrations : null , deniedTools : [ ] } )
320366
321367 const before = await buildIntegrationToolSchemas (
322368 'user-policy' ,
323- undefined ,
324369 { schemaSurface : 'copilot' } ,
325370 'workspace-policy'
326371 )
@@ -335,7 +380,6 @@ describe('buildIntegrationToolSchemas', () => {
335380
336381 const after = await buildIntegrationToolSchemas (
337382 'user-policy' ,
338- undefined ,
339383 { schemaSurface : 'copilot' } ,
340384 'workspace-policy'
341385 )
0 commit comments