Skip to content

Commit d6c973f

Browse files
committed
feat: add fetch example tool demonstrating caching
1 parent 64332dd commit d6c973f

File tree

2 files changed

+523
-0
lines changed

2 files changed

+523
-0
lines changed

src/tools/fetch-example.test.ts

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/**
2+
* @fileoverview Tests for the fetch example tools
3+
* @module tools/fetch-example.test
4+
*/
5+
6+
import { beforeEach, describe, expect, it, vi } from 'vitest';
7+
import { FetchBackend, configurableFetch } from '../utils/fetch.js';
8+
import {
9+
ConfigureFetchSchema,
10+
FetchExampleSchema,
11+
configureFetchTool,
12+
fetchExampleTool,
13+
} from './fetch-example.js';
14+
15+
// Mock the fetch utility
16+
vi.mock('../utils/fetch.js', () => ({
17+
FetchBackend: {
18+
BUILT_IN: 'built-in',
19+
CACHE_MEMORY: 'cache-memory',
20+
CACHE_DISK: 'cache-disk',
21+
},
22+
configurableFetch: {
23+
fetch: vi.fn(),
24+
getConfig: vi.fn(),
25+
updateConfig: vi.fn(),
26+
clearCaches: vi.fn(),
27+
getCacheStats: vi.fn(),
28+
},
29+
}));
30+
31+
describe('FetchExampleSchema', () => {
32+
it('should validate valid fetch example parameters', () => {
33+
const validParams = {
34+
url: 'https://httpbin.org/json',
35+
backend: FetchBackend.CACHE_MEMORY,
36+
no_cache: false,
37+
user_agent: 'Test-Agent/1.0',
38+
};
39+
40+
const result = FetchExampleSchema.parse(validParams);
41+
expect(result).toEqual(validParams);
42+
});
43+
44+
it('should apply default values', () => {
45+
const minimalParams = { url: 'https://example.com' };
46+
const result = FetchExampleSchema.parse(minimalParams);
47+
48+
expect(result.no_cache).toBe(false);
49+
expect(result.url).toBe('https://example.com');
50+
});
51+
52+
it('should reject invalid URL', () => {
53+
expect(() =>
54+
FetchExampleSchema.parse({
55+
url: 'not-a-url',
56+
}),
57+
).toThrow();
58+
});
59+
60+
it('should reject invalid backend', () => {
61+
expect(() =>
62+
FetchExampleSchema.parse({
63+
url: 'https://example.com',
64+
backend: 'invalid-backend',
65+
}),
66+
).toThrow();
67+
});
68+
});
69+
70+
describe('ConfigureFetchSchema', () => {
71+
it('should validate valid configuration parameters', () => {
72+
const validParams = {
73+
backend: FetchBackend.CACHE_DISK,
74+
cache_ttl: 30000,
75+
cache_dir: '/tmp/cache',
76+
user_agent: 'Custom-Agent/1.0',
77+
clear_cache: true,
78+
};
79+
80+
const result = ConfigureFetchSchema.parse(validParams);
81+
expect(result).toEqual(validParams);
82+
});
83+
84+
it('should apply default values', () => {
85+
const result = ConfigureFetchSchema.parse({});
86+
expect(result.clear_cache).toBe(false);
87+
});
88+
89+
it('should reject negative cache_ttl', () => {
90+
expect(() =>
91+
ConfigureFetchSchema.parse({
92+
cache_ttl: -1000,
93+
}),
94+
).toThrow();
95+
});
96+
});
97+
98+
describe('fetchExampleTool', () => {
99+
beforeEach(() => {
100+
vi.clearAllMocks();
101+
102+
// Mock default config
103+
vi.mocked(configurableFetch.getConfig).mockReturnValue({
104+
backend: FetchBackend.BUILT_IN,
105+
cacheTtl: 300000,
106+
cacheDir: '.cache',
107+
maxCacheSize: 104857600,
108+
userAgent: undefined,
109+
defaultHeaders: {},
110+
});
111+
});
112+
113+
it('should fetch and display JSON data successfully', async () => {
114+
const mockData = { message: 'Hello, World!' };
115+
const mockResponse = {
116+
status: 200,
117+
statusText: 'OK',
118+
headers: {
119+
get: vi.fn().mockReturnValue('application/json'),
120+
},
121+
text: vi.fn().mockResolvedValue(JSON.stringify(mockData)),
122+
};
123+
124+
// biome-ignore lint/suspicious/noExplicitAny: Test mocks
125+
vi.mocked(configurableFetch.fetch).mockResolvedValue(mockResponse as any);
126+
127+
const result = await fetchExampleTool({
128+
url: 'https://httpbin.org/json',
129+
backend: FetchBackend.CACHE_MEMORY,
130+
});
131+
132+
expect(configurableFetch.fetch).toHaveBeenCalledWith('https://httpbin.org/json', {
133+
backend: FetchBackend.CACHE_MEMORY,
134+
noCache: false,
135+
});
136+
137+
expect(result.content[0].text).toContain('Fetch Example Results');
138+
expect(result.content[0].text).toContain('https://httpbin.org/json');
139+
expect(result.content[0].text).toContain('cache-memory');
140+
expect(result.content[0].text).toContain('200 OK');
141+
expect(result.content[0].text).toContain(JSON.stringify(mockData, null, 2));
142+
});
143+
144+
it('should handle text data', async () => {
145+
const mockText = 'Hello, World!';
146+
const mockResponse = {
147+
status: 200,
148+
statusText: 'OK',
149+
headers: {
150+
get: vi.fn().mockReturnValue('text/plain'),
151+
},
152+
text: vi.fn().mockResolvedValue(mockText),
153+
};
154+
155+
vi.mocked(configurableFetch.fetch)
156+
// biome-ignore lint/suspicious/noExplicitAny: Test mocks
157+
.mockResolvedValueOnce(mockResponse as any)
158+
// biome-ignore lint/suspicious/noExplicitAny: Test mocks
159+
.mockResolvedValueOnce(mockResponse as any);
160+
161+
const result = await fetchExampleTool({
162+
url: 'https://example.com',
163+
});
164+
165+
expect(result.content[0].text).toContain('**Parsed As**: text');
166+
expect(result.content[0].text).toContain(mockText);
167+
});
168+
169+
it('should handle custom headers', async () => {
170+
const mockResponse = {
171+
status: 200,
172+
statusText: 'OK',
173+
headers: {
174+
get: vi.fn().mockReturnValue('text/plain'),
175+
},
176+
text: vi.fn().mockResolvedValue('test'),
177+
};
178+
179+
// biome-ignore lint/suspicious/noExplicitAny: Test mocks
180+
vi.mocked(configurableFetch.fetch).mockResolvedValue(mockResponse as any);
181+
182+
await fetchExampleTool({
183+
url: 'https://example.com',
184+
user_agent: 'Custom-Agent/1.0',
185+
no_cache: true,
186+
});
187+
188+
expect(configurableFetch.fetch).toHaveBeenCalledWith('https://example.com', {
189+
backend: undefined,
190+
noCache: true,
191+
headers: {
192+
'User-Agent': 'Custom-Agent/1.0',
193+
},
194+
});
195+
});
196+
197+
it('should handle fetch errors', async () => {
198+
vi.mocked(configurableFetch.fetch).mockRejectedValue(new Error('Network error'));
199+
200+
const result = await fetchExampleTool({
201+
url: 'https://example.com',
202+
});
203+
204+
expect(result.isError).toBe(true);
205+
expect(result.content[0].text).toContain('Fetch Example Error');
206+
expect(result.content[0].text).toContain('Network error');
207+
});
208+
209+
it('should handle validation errors', async () => {
210+
const result = await fetchExampleTool({
211+
url: 'invalid-url',
212+
});
213+
214+
expect(result.isError).toBe(true);
215+
expect(result.content[0].text).toContain('Fetch Example Error');
216+
});
217+
});
218+
219+
describe('configureFetchTool', () => {
220+
beforeEach(() => {
221+
vi.clearAllMocks();
222+
223+
// Mock config and stats
224+
vi.mocked(configurableFetch.getConfig).mockReturnValue({
225+
backend: FetchBackend.CACHE_MEMORY,
226+
cacheTtl: 60000,
227+
cacheDir: '.cache',
228+
maxCacheSize: 104857600,
229+
userAgent: 'Test-Agent/1.0',
230+
defaultHeaders: {},
231+
});
232+
233+
vi.mocked(configurableFetch.getCacheStats).mockReturnValue({
234+
memory: { enabled: true },
235+
disk: { enabled: true },
236+
});
237+
});
238+
239+
it('should update configuration successfully', async () => {
240+
const result = await configureFetchTool({
241+
backend: FetchBackend.CACHE_DISK,
242+
cache_ttl: 120000,
243+
user_agent: 'Updated-Agent/2.0',
244+
});
245+
246+
expect(configurableFetch.updateConfig).toHaveBeenCalledWith({
247+
backend: FetchBackend.CACHE_DISK,
248+
cacheTtl: 120000,
249+
userAgent: 'Updated-Agent/2.0',
250+
});
251+
252+
expect(result.content[0].text).toContain('Fetch Configuration Updated');
253+
expect(result.content[0].text).toContain('cache-memory');
254+
expect(result.content[0].text).toContain('Test-Agent/1.0');
255+
});
256+
257+
it('should clear caches when requested', async () => {
258+
const result = await configureFetchTool({
259+
clear_cache: true,
260+
});
261+
262+
expect(configurableFetch.clearCaches).toHaveBeenCalled();
263+
expect(result.content[0].text).toContain('Caches cleared');
264+
});
265+
266+
it('should handle empty configuration', async () => {
267+
const result = await configureFetchTool({});
268+
269+
// Should not call updateConfig with empty object
270+
expect(configurableFetch.updateConfig).not.toHaveBeenCalled();
271+
expect(result.content[0].text).toContain('Current Configuration');
272+
});
273+
274+
it('should handle configuration errors', async () => {
275+
vi.mocked(configurableFetch.updateConfig).mockImplementation(() => {
276+
throw new Error('Configuration error');
277+
});
278+
279+
const result = await configureFetchTool({
280+
backend: FetchBackend.CACHE_DISK,
281+
});
282+
283+
expect(result.isError).toBe(true);
284+
expect(result.content[0].text).toContain('Configuration Error');
285+
expect(result.content[0].text).toContain('Configuration error');
286+
});
287+
288+
it('should handle validation errors', async () => {
289+
const result = await configureFetchTool({
290+
cache_ttl: -1000, // Invalid negative value
291+
});
292+
293+
expect(result.isError).toBe(true);
294+
expect(result.content[0].text).toContain('Configuration Error');
295+
});
296+
});

0 commit comments

Comments
 (0)