@@ -6,6 +6,7 @@ import { cbinsightsChatTool } from '@/tools/cbinsights/chat'
66import { cbinsightsGetOrgFundingsTool } from '@/tools/cbinsights/get_org_fundings'
77import { cbinsightsGetOrgOutlookTool } from '@/tools/cbinsights/get_org_outlook'
88import { cbinsightsGetScoutingReportTool } from '@/tools/cbinsights/get_scouting_report'
9+ import { cbinsightsListBusinessRelationshipsTool } from '@/tools/cbinsights/list_business_relationships'
910import { cbinsightsListFundingsTool } from '@/tools/cbinsights/list_fundings'
1011import { cbinsightsLookupOrganizationsTool } from '@/tools/cbinsights/lookup_organizations'
1112import { cbinsightsRagTool } from '@/tools/cbinsights/rag'
@@ -268,6 +269,110 @@ describe('cbinsights request building', () => {
268269 expect ( JSON . parse ( String ( calls [ 2 ] . init . body ) ) . sectorIds ) . toEqual ( [ 1 , 2 ] )
269270 } )
270271
272+ /*
273+ * The sibling of the numeric bound. `parseBooleanParam` used to return
274+ * undefined for anything it did not recognize, so a model answering "yes"
275+ * dropped the restriction entirely and widened the search — the same failure,
276+ * on a filter the caller explicitly set.
277+ */
278+ it . each ( [ 'yes' , '1' , 'TRUE ish' , 0 ] ) (
279+ 'rejects the unrecognized boolean %j rather than dropping the filter' ,
280+ async ( entry ) => {
281+ mockFetch ( [ AUTH_OK ] )
282+ await expect (
283+ cbinsightsSearchFirmographicsTool . directExecution ! ( {
284+ ...CREDS ,
285+ keyword : 'fintech' ,
286+ vcBacked : entry ,
287+ } as never )
288+ ) . rejects . toThrow ( / " v c B a c k e d " m u s t b e t r u e o r f a l s e / )
289+ expect ( calls ) . toHaveLength ( 0 )
290+ }
291+ )
292+
293+ it . each ( [
294+ [ 'true' , true ] ,
295+ [ 'FALSE' , false ] ,
296+ [ true , true ] ,
297+ [ false , false ] ,
298+ ] ) ( 'still accepts the boolean form %j a dropdown emits' , async ( entry , expected ) => {
299+ mockFetch ( [ AUTH_OK , { body : { orgs : [ ] } } ] )
300+ await cbinsightsSearchFirmographicsTool . directExecution ! ( {
301+ ...CREDS ,
302+ keyword : 'fintech' ,
303+ vcBacked : entry ,
304+ } as never )
305+ expect ( JSON . parse ( String ( calls [ 1 ] . init . body ) ) . vcBacked ) . toBe ( expected )
306+ } )
307+
308+ it ( 'treats the dropdown\'s "Any" option as no filter at all' , async ( ) => {
309+ mockFetch ( [ AUTH_OK , { body : { orgs : [ ] } } ] )
310+ await cbinsightsSearchFirmographicsTool . directExecution ! ( {
311+ ...CREDS ,
312+ keyword : 'fintech' ,
313+ vcBacked : '' ,
314+ } as never )
315+ expect ( JSON . parse ( String ( calls [ 1 ] . init . body ) ) ) . toEqual ( { keyword : 'fintech' } )
316+ } )
317+
318+ /*
319+ * A mistyped direction used to fold into `desc`, handing back the bottom of a
320+ * metered result set as though it were the top.
321+ */
322+ it ( 'rejects a sort direction that is neither asc nor desc' , async ( ) => {
323+ mockFetch ( [ AUTH_OK ] )
324+ await expect (
325+ cbinsightsSearchFirmographicsTool . directExecution ! ( {
326+ ...CREDS ,
327+ keyword : 'fintech' ,
328+ sortField : 'mosaicOverall' ,
329+ sortDirection : 'ascending' ,
330+ } as never )
331+ ) . rejects . toThrow ( / " s o r t D i r e c t i o n " m u s t b e " a s c " o r " d e s c " / )
332+ expect ( calls ) . toHaveLength ( 0 )
333+ } )
334+
335+ /*
336+ * `limit` is the last silent-drop path: falling back to the endpoint default
337+ * returns a different page than the caller asked for, and still bills for it.
338+ */
339+ it ( 'rejects a mistyped limit rather than falling back to the endpoint default' , async ( ) => {
340+ mockFetch ( [ AUTH_OK ] )
341+ await expect (
342+ cbinsightsLookupOrganizationsTool . directExecution ! ( {
343+ ...CREDS ,
344+ names : 'a' ,
345+ limit : 'twenty' ,
346+ } as never )
347+ ) . rejects . toThrow ( / " l i m i t " m u s t b e a n u m b e r / )
348+ expect ( calls ) . toHaveLength ( 0 )
349+ } )
350+
351+ /*
352+ * A block-to-block reference can hand over an object. Stringifying it searched
353+ * for the literal "[object Object]" and reported success on zero matches.
354+ */
355+ it ( 'rejects a non-text entry in a free-text filter rather than stringifying it' , async ( ) => {
356+ mockFetch ( [ AUTH_OK ] )
357+ await expect (
358+ cbinsightsLookupOrganizationsTool . directExecution ! ( {
359+ ...CREDS ,
360+ names : [ { name : 'CB Insights' } ] ,
361+ } as never )
362+ ) . rejects . toThrow ( / " n a m e s " m u s t c o n t a i n o n l y t e x t v a l u e s / )
363+ expect ( calls ) . toHaveLength ( 0 )
364+ } )
365+
366+ it ( 'treats a whitespace-only numeric bound as unset, not as zero' , async ( ) => {
367+ mockFetch ( [ AUTH_OK , { body : { orgs : [ ] } } ] )
368+ await cbinsightsSearchFirmographicsTool . directExecution ! ( {
369+ ...CREDS ,
370+ keyword : 'fintech' ,
371+ minCurrentHeadcount : ' ' ,
372+ } as never )
373+ expect ( JSON . parse ( String ( calls [ 1 ] . init . body ) ) ) . toEqual ( { keyword : 'fintech' } )
374+ } )
375+
271376 it ( 'rejects a mistyped numeric bound rather than dropping it' , async ( ) => {
272377 mockFetch ( [ AUTH_OK ] )
273378 await expect (
@@ -469,6 +574,24 @@ describe('cbinsights response mapping', () => {
469574 } )
470575 } )
471576
577+ /*
578+ * Business relationships is the one paged endpoint whose documented response
579+ * carries no total, so the tool must not manufacture a permanently-null
580+ * `totalHits` alongside the real token.
581+ */
582+ it ( 'reports only the fields the business-relationships endpoint documents' , async ( ) => {
583+ mockFetch ( [ AUTH_OK , { body : { orgs : [ { orgId : 1 } ] , nextPageToken : 'tok' } } ] )
584+
585+ const result = await cbinsightsListBusinessRelationshipsTool . directExecution ! ( {
586+ ...CREDS ,
587+ orgIds : '1' ,
588+ } as never )
589+
590+ expect ( result . output ) . toEqual ( { orgs : [ { orgId : 1 } ] , nextPageToken : 'tok' } )
591+ expect ( cbinsightsListBusinessRelationshipsTool . outputs ) . not . toHaveProperty ( 'totalHits' )
592+ expect ( cbinsightsListBusinessRelationshipsTool . outputs ) . not . toHaveProperty ( 'totalHitsRelation' )
593+ } )
594+
472595 it ( 'renames the API chatID to the block-facing chatId' , async ( ) => {
473596 mockFetch ( [
474597 AUTH_OK ,
0 commit comments