@@ -2,20 +2,35 @@ import {
22 KnowledgeBaseFileStatus ,
33 type KnowledgeBaseFile ,
44} from "@budibase/types"
5+
6+ import { features } from "@budibase/backend-core"
7+ import sdk from "../../../../sdk"
8+ import {
9+ createKnowledgeFilesTool ,
10+ createKnowledgeSearchTool ,
11+ } from "../knowledgeFiles"
12+
13+ jest . mock ( "@budibase/backend-core" , ( ) => ( {
14+ features : {
15+ isEnabled : jest . fn ( ) ,
16+ } ,
17+ } ) )
18+
519jest . mock ( "../../../../sdk" , ( ) => ( {
620 __esModule : true ,
721 default : {
822 ai : {
23+ agents : {
24+ getOrThrow : jest . fn ( ) ,
25+ } ,
926 rag : {
1027 listFilesForAgent : jest . fn ( ) ,
28+ retrieveContextForAgent : jest . fn ( ) ,
1129 } ,
1230 } ,
1331 } ,
1432} ) )
1533
16- import sdk from "../../../../sdk"
17- import { createKnowledgeFilesTool } from "../knowledgeFiles"
18-
1934const executeTool = async (
2035 agentId : string ,
2136 input : {
@@ -35,6 +50,21 @@ const executeTool = async (
3550 } )
3651}
3752
53+ const executeSearchTool = async (
54+ agentId : string ,
55+ input : { question : string }
56+ ) => {
57+ const toolDef = createKnowledgeSearchTool ( agentId )
58+ if ( ! toolDef . tool . execute ) {
59+ throw new Error ( "tool.execute is not a function" )
60+ }
61+
62+ return await toolDef . tool . execute ( input , {
63+ toolCallId : "test-tool-call" ,
64+ messages : [ ] ,
65+ } )
66+ }
67+
3868describe ( "AI Tools - Knowledge files" , ( ) => {
3969 beforeEach ( ( ) => {
4070 jest . restoreAllMocks ( )
@@ -319,3 +349,38 @@ describe("AI Tools - Knowledge files", () => {
319349 expect ( result . files [ 0 ] . matchedBy ) . toBe ( "filename-contains" )
320350 } )
321351} )
352+
353+ describe ( "AI Tools - Knowledge search" , ( ) => {
354+ beforeEach ( ( ) => {
355+ jest . restoreAllMocks ( )
356+ jest . mocked ( features . isEnabled ) . mockResolvedValue ( true )
357+ } )
358+
359+ it ( "hard-fails with a clear message when Gemini retrieval is unavailable" , async ( ) => {
360+ jest
361+ . spyOn ( sdk . ai . agents , "getOrThrow" )
362+ . mockResolvedValue ( { _id : "agent_1" } as any )
363+ jest
364+ . spyOn ( sdk . ai . rag , "retrieveContextForAgent" )
365+ . mockRejectedValue ( { status : 503 , message : "upstream unavailable" } )
366+
367+ await expect (
368+ executeSearchTool ( "agent_1" , { question : "What is policy?" } )
369+ ) . rejects . toThrow (
370+ "Gemini knowledge retrieval is temporarily unavailable (upstream 503). Budibase is operating normally; please retry shortly."
371+ )
372+ } )
373+
374+ it ( "preserves non-provider retrieval errors" , async ( ) => {
375+ jest
376+ . spyOn ( sdk . ai . agents , "getOrThrow" )
377+ . mockResolvedValue ( { _id : "agent_1" } as any )
378+ jest
379+ . spyOn ( sdk . ai . rag , "retrieveContextForAgent" )
380+ . mockRejectedValue ( new Error ( "Failed to map source metadata" ) )
381+
382+ await expect (
383+ executeSearchTool ( "agent_1" , { question : "What is policy?" } )
384+ ) . rejects . toThrow ( "Failed to map source metadata" )
385+ } )
386+ } )
0 commit comments