summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/web-search.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/tools/web-search.test.ts')
-rw-r--r--packages/core/src/tools/web-search.test.ts175
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);
+ });
+ });
+});