diff options
Diffstat (limited to 'packages/core/src/tools/web-search.test.ts')
| -rw-r--r-- | packages/core/src/tools/web-search.test.ts | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/packages/core/src/tools/web-search.test.ts b/packages/core/src/tools/web-search.test.ts new file mode 100644 index 00000000..c0620c08 --- /dev/null +++ b/packages/core/src/tools/web-search.test.ts @@ -0,0 +1,175 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest'; +import { WebSearchTool, WebSearchToolParams } from './web-search.js'; +import { Config } from '../config/config.js'; +import { GeminiClient } from '../core/client.js'; + +// Mock GeminiClient and Config constructor +vi.mock('../core/client.js'); +vi.mock('../config/config.js'); + +describe('WebSearchTool', () => { + const abortSignal = new AbortController().signal; + let mockGeminiClient: GeminiClient; + let tool: WebSearchTool; + + beforeEach(() => { + const mockConfigInstance = { + getGeminiClient: () => mockGeminiClient, + getProxy: () => undefined, + } as unknown as Config; + mockGeminiClient = new GeminiClient(mockConfigInstance); + tool = new WebSearchTool(mockConfigInstance); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('build', () => { + it('should return an invocation for a valid query', () => { + const params: WebSearchToolParams = { query: 'test query' }; + const invocation = tool.build(params); + expect(invocation).toBeDefined(); + expect(invocation.params).toEqual(params); + }); + + it('should throw an error for an empty query', () => { + const params: WebSearchToolParams = { query: '' }; + expect(() => tool.build(params)).toThrow( + "The 'query' parameter cannot be empty.", + ); + }); + + it('should throw an error for a query with only whitespace', () => { + const params: WebSearchToolParams = { query: ' ' }; + expect(() => tool.build(params)).toThrow( + "The 'query' parameter cannot be empty.", + ); + }); + }); + + describe('getDescription', () => { + it('should return a description of the search', () => { + const params: WebSearchToolParams = { query: 'test query' }; + const invocation = tool.build(params); + expect(invocation.getDescription()).toBe( + 'Searching the web for: "test query"', + ); + }); + }); + + describe('execute', () => { + it('should return search results for a successful query', async () => { + const params: WebSearchToolParams = { query: 'successful query' }; + (mockGeminiClient.generateContent as Mock).mockResolvedValue({ + candidates: [ + { + content: { + role: 'model', + parts: [{ text: 'Here are your results.' }], + }, + }, + ], + }); + + const invocation = tool.build(params); + const result = await invocation.execute(abortSignal); + + expect(result.llmContent).toBe( + 'Web search results for "successful query":\n\nHere are your results.', + ); + expect(result.returnDisplay).toBe( + 'Search results for "successful query" returned.', + ); + expect(result.sources).toBeUndefined(); + }); + + it('should handle no search results found', async () => { + const params: WebSearchToolParams = { query: 'no results query' }; + (mockGeminiClient.generateContent as Mock).mockResolvedValue({ + candidates: [ + { + content: { + role: 'model', + parts: [{ text: '' }], + }, + }, + ], + }); + + const invocation = tool.build(params); + const result = await invocation.execute(abortSignal); + + expect(result.llmContent).toBe( + 'No search results or information found for query: "no results query"', + ); + expect(result.returnDisplay).toBe('No information found.'); + }); + + it('should handle API errors gracefully', async () => { + const params: WebSearchToolParams = { query: 'error query' }; + const testError = new Error('API Failure'); + (mockGeminiClient.generateContent as Mock).mockRejectedValue(testError); + + const invocation = tool.build(params); + const result = await invocation.execute(abortSignal); + + expect(result.llmContent).toContain('Error:'); + expect(result.llmContent).toContain('API Failure'); + expect(result.returnDisplay).toBe('Error performing web search.'); + }); + + it('should correctly format results with sources and citations', async () => { + const params: WebSearchToolParams = { query: 'grounding query' }; + (mockGeminiClient.generateContent as Mock).mockResolvedValue({ + candidates: [ + { + content: { + role: 'model', + parts: [{ text: 'This is a test response.' }], + }, + groundingMetadata: { + groundingChunks: [ + { web: { uri: 'https://example.com', title: 'Example Site' } }, + { web: { uri: 'https://google.com', title: 'Google' } }, + ], + groundingSupports: [ + { + segment: { startIndex: 5, endIndex: 14 }, + groundingChunkIndices: [0], + }, + { + segment: { startIndex: 15, endIndex: 24 }, + groundingChunkIndices: [0, 1], + }, + ], + }, + }, + ], + }); + + const invocation = tool.build(params); + const result = await invocation.execute(abortSignal); + + const expectedLlmContent = `Web search results for "grounding query": + +This is a test[1] response.[1][2] + +Sources: +[1] Example Site (https://example.com) +[2] Google (https://google.com)`; + + expect(result.llmContent).toBe(expectedLlmContent); + expect(result.returnDisplay).toBe( + 'Search results for "grounding query" returned.', + ); + expect(result.sources).toHaveLength(2); + }); + }); +}); |
